omg big commit

This commit is contained in:
galister
2024-02-20 20:54:06 +01:00
parent 65fa9d1fae
commit d31b3ca6c0
15 changed files with 441 additions and 123 deletions

View File

@@ -20,7 +20,7 @@ use crate::{
state::AppState,
};
use super::overlay::{OverlayData, OverlayState};
use super::overlay::{OverlayBackend, OverlayData, OverlayState};
#[derive(Error, Debug)]
pub enum BackendError {
@@ -91,6 +91,22 @@ where
}
}
pub fn drop_by_selector(&mut self, selector: &OverlaySelector) {
match selector {
OverlaySelector::Id(id) => {
self.overlays.remove(id);
}
OverlaySelector::Name(name) => {
let id = self
.overlays
.iter()
.find(|(_, o)| *o.state.name == **name)
.map(|(id, _)| *id);
id.and_then(|id| self.overlays.remove(&id));
}
};
}
pub fn get_by_id<'a>(&'a mut self, id: usize) -> Option<&'a OverlayData<T>> {
self.overlays.get(&id)
}
@@ -115,6 +131,10 @@ where
self.overlays.values_mut()
}
pub fn add(&mut self, overlay: OverlayData<T>) {
self.overlays.insert(overlay.state.id, overlay);
}
pub fn show_hide(&mut self, app: &mut AppState) {
let any_shown = self
.overlays
@@ -170,6 +190,11 @@ pub enum TaskType {
OverlaySelector,
Box<dyn FnOnce(&mut AppState, &mut OverlayState) + Send>,
),
CreateOverlay(
OverlaySelector,
Box<dyn FnOnce(&mut AppState) -> Option<(OverlayState, Box<dyn OverlayBackend>)> + Send>,
),
DropOverlay(OverlaySelector),
Toast(Toast),
}

View File

@@ -64,7 +64,6 @@ impl LinePool {
state: OverlayState {
name: Arc::from(format!("wlx-line{}", id)),
show_hide: true,
size: (0, 0),
..Default::default()
},
backend: Box::new(SplitOverlayBackend {
@@ -181,7 +180,4 @@ impl OverlayRenderer for StaticRenderer {
fn view(&mut self) -> Option<Arc<ImageView>> {
Some(self.view.clone())
}
fn extent(&self) -> [u32; 3] {
self.view.image().extent().clone()
}
}

View File

@@ -27,6 +27,7 @@ use crate::{
manifest::{install_manifest, uninstall_manifest},
overlay::OpenVrOverlayData,
},
overlay::OverlayData,
},
graphics::WlxGraphics,
overlays::watch::{watch_fade, WATCH_NAME},
@@ -157,6 +158,27 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
f(&mut state, &mut o.state);
}
}
TaskType::CreateOverlay(sel, f) => {
let None = overlays.mut_by_selector(&sel) else {
continue;
};
let Some((state, backend)) = f(&mut state) else {
continue;
};
overlays.add(OverlayData {
state,
backend,
..Default::default()
});
}
TaskType::DropOverlay(sel) => {
if let Some(o) = overlays.mut_by_selector(&sel) {
o.destroy(&mut overlay_mngr);
overlays.drop_by_selector(&sel);
}
}
TaskType::Toast(t) => {
// TODO toasts
log::info!("Toast: {} {}", t.title, t.body);
@@ -209,7 +231,7 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
let _ = sender.send_params(&overlays);
};
log::debug!("Rendering frame");
log::trace!("Rendering frame");
for o in overlays.iter_mut() {
if o.state.want_visible {
@@ -217,7 +239,7 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
}
}
log::debug!("Rendering overlays");
log::trace!("Rendering overlays");
overlays
.iter_mut()

View File

@@ -254,4 +254,13 @@ impl OverlayData<OpenVrOverlayData> {
log::error!("{}: Failed to set overlay texture: {}", self.state.name, e);
}
}
pub(super) fn destroy(&mut self, overlay: &mut OverlayManager) {
if let Some(handle) = self.data.handle {
log::debug!("{}: destroy", self.state.name);
if let Err(e) = overlay.destroy_overlay(handle) {
log::error!("{}: Failed to destroy overlay: {}", self.state.name, e);
}
}
}
}

View File

@@ -16,6 +16,7 @@ use crate::{
common::{OverlayContainer, TaskType},
input::interact,
openxr::{input::DoubleClickCounter, lines::LinePool, overlay::OpenXrOverlayData},
overlay::OverlayData,
},
graphics::WlxGraphics,
overlays::watch::{watch_fade, WATCH_NAME},
@@ -187,6 +188,24 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
f(&mut app_state, &mut o.state);
}
}
TaskType::CreateOverlay(sel, f) => {
let None = overlays.mut_by_selector(&sel) else {
continue;
};
let Some((state, backend)) = f(&mut app_state) else {
continue;
};
overlays.add(OverlayData {
state,
backend,
..Default::default()
});
}
TaskType::DropOverlay(sel) => {
overlays.drop_by_selector(&sel);
}
TaskType::Toast(t) => {
// TODO toasts
log::info!("Toast: {} {}", t.title, t.body);

View File

@@ -7,7 +7,7 @@ use std::{
};
use anyhow::Ok;
use glam::{Affine2, Affine3A, Mat3A, Quat, Vec3, Vec3A};
use glam::{Affine2, Affine3A, Mat3A, Quat, Vec2, Vec3, Vec3A};
use vulkano::image::view::ImageView;
use crate::state::AppState;
@@ -21,7 +21,6 @@ pub trait OverlayBackend: OverlayRenderer + InteractionHandler {}
pub struct OverlayState {
pub id: usize,
pub name: Arc<str>,
pub size: (i32, i32),
pub want_visible: bool,
pub show_hide: bool,
pub grabbable: bool,
@@ -44,7 +43,6 @@ impl Default for OverlayState {
OverlayState {
id: AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed),
name: Arc::from(""),
size: (0, 0),
want_visible: false,
show_hide: false,
grabbable: false,
@@ -208,7 +206,6 @@ pub trait OverlayRenderer {
fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()>;
fn render(&mut self, app: &mut AppState) -> anyhow::Result<()>;
fn view(&mut self) -> Option<Arc<ImageView>>;
fn extent(&self) -> [u32; 3];
}
pub struct FallbackRenderer;
@@ -229,9 +226,6 @@ impl OverlayRenderer for FallbackRenderer {
fn view(&mut self) -> Option<Arc<ImageView>> {
None
}
fn extent(&self) -> [u32; 3] {
[0, 0, 0]
}
}
// Boilerplate and dummies
@@ -274,9 +268,6 @@ impl OverlayRenderer for SplitOverlayBackend {
fn view(&mut self) -> Option<Arc<ImageView>> {
self.renderer.view()
}
fn extent(&self) -> [u32; 3] {
self.renderer.extent()
}
}
impl InteractionHandler for SplitOverlayBackend {
fn on_left(&mut self, app: &mut AppState, pointer: usize) {
@@ -292,3 +283,20 @@ impl InteractionHandler for SplitOverlayBackend {
self.interaction.on_pointer(app, hit, pressed);
}
}
pub fn ui_transform(extent: &[u32; 2]) -> Affine2 {
let center = Vec2 { x: 0.5, y: 0.5 };
if extent[1] > extent[0] {
Affine2::from_cols(
Vec2::X * (extent[1] as f32 / extent[0] as f32),
Vec2::NEG_Y,
center,
)
} else {
Affine2::from_cols(
Vec2::X,
Vec2::NEG_Y * (extent[0] as f32 / extent[1] as f32),
center,
)
}
}

View File

@@ -506,10 +506,6 @@ impl<D, S> OverlayRenderer for Canvas<D, S> {
fn view(&mut self) -> Option<Arc<ImageView>> {
Some(self.view_final.clone())
}
fn extent(&self) -> [u32; 3] {
self.view_final.image().extent().clone()
}
}
impl<D, S> OverlayBackend for Canvas<D, S> {}

View File

@@ -59,16 +59,26 @@ fn auto_run(running: Arc<AtomicBool>) {
#[cfg(feature = "openxr")]
{
use crate::backend::openxr::openxr_run;
let Err(BackendError::NotSupported) = openxr_run(running.clone()) else {
return;
match openxr_run(running.clone()) {
Ok(()) => return,
Err(BackendError::NotSupported) => (),
Err(e) => {
log::error!("{}", e);
return;
}
};
}
#[cfg(feature = "openvr")]
{
use crate::backend::openvr::openvr_run;
let Err(BackendError::NotSupported) = openvr_run(running) else {
return;
match openvr_run(running.clone()) {
Ok(()) => return,
Err(BackendError::NotSupported) => (),
Err(e) => {
log::error!("{}", e);
return;
}
};
}

View File

@@ -121,7 +121,6 @@ where
Ok(OverlayData {
state: OverlayState {
name: KEYBOARD_NAME.into(),
size: (size.x as _, size.y as _),
grabbable: true,
recenter: true,
interactable: true,

135
src/overlays/mirror.rs Normal file
View File

@@ -0,0 +1,135 @@
use std::{sync::Arc, thread::JoinHandle};
use futures::executor;
use glam::vec3a;
use wlx_capture::pipewire::{pipewire_select_screen, PipewireCapture, PipewireSelectScreenResult};
use crate::{
backend::{
common::{OverlaySelector, TaskType},
overlay::{
ui_transform, OverlayBackend, OverlayRenderer, OverlayState, SplitOverlayBackend,
},
},
state::{AppSession, AppState},
};
use super::screen::ScreenRenderer;
pub struct MirrorRenderer {
name: Arc<str>,
renderer: Option<ScreenRenderer>,
selector: Option<JoinHandle<Option<PipewireSelectScreenResult>>>,
last_extent: [u32; 3],
}
impl MirrorRenderer {
pub fn new(name: Arc<str>) -> Self {
Self {
name,
renderer: None,
selector: Some(std::thread::spawn(|| {
executor::block_on(pipewire_select_screen(None, false, false, false)).ok()
})),
last_extent: [0; 3],
}
}
}
impl OverlayRenderer for MirrorRenderer {
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> {
if let Some(selector) = self.selector.take() {
if !selector.is_finished() {
self.selector = Some(selector);
return Ok(());
}
// safe unwrap because we know it's finished
if let Some(pw_result) = selector.join().unwrap() {
log::info!(
"{}: PipeWire node selected: {}",
self.name.clone(),
pw_result.node_id
);
let capture = PipewireCapture::new(self.name.clone(), pw_result.node_id, 60);
self.renderer = Some(ScreenRenderer::new_raw(
self.name.clone(),
Box::new(capture),
));
app.tasks.enqueue(TaskType::Overlay(
OverlaySelector::Name(self.name.clone()),
Box::new(|app, o| {
o.grabbable = true;
o.interactable = true;
o.reset(app, false);
}),
));
} else {
log::warn!("Failed to create pipewire mirror");
self.renderer = None;
// drop self
app.tasks
.enqueue(TaskType::DropOverlay(OverlaySelector::Name(
self.name.clone(),
)));
}
}
if let Some(renderer) = self.renderer.as_mut() {
renderer.render(app)?;
if let Some(view) = renderer.view() {
let extent = view.image().extent();
if self.last_extent != extent {
self.last_extent = extent.clone();
// resized
app.tasks.enqueue(TaskType::Overlay(
OverlaySelector::Name(self.name.clone()),
Box::new(move |_app, o| {
o.interaction_transform = ui_transform(&[extent[0], extent[1]]);
}),
));
}
}
}
Ok(())
}
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> {
if let Some(renderer) = self.renderer.as_mut() {
renderer.pause(app)?;
}
Ok(())
}
fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()> {
if let Some(renderer) = self.renderer.as_mut() {
renderer.resume(app)?;
}
Ok(())
}
fn view(&mut self) -> Option<std::sync::Arc<vulkano::image::view::ImageView>> {
self.renderer.as_mut().and_then(|r| r.view())
}
}
pub fn new_mirror(
name: Arc<str>,
show_hide: bool,
session: &AppSession,
) -> Option<(OverlayState, Box<dyn OverlayBackend>)> {
let state = OverlayState {
name: name.clone(),
show_hide,
want_visible: true,
spawn_scale: 0.5 * session.config.desktop_view_scale,
spawn_point: vec3a(0., 0.5, -0.5),
..Default::default()
};
let backend = Box::new(SplitOverlayBackend {
renderer: Box::new(MirrorRenderer::new(name)),
..Default::default()
});
Some((state, backend))
}

View File

@@ -1,4 +1,6 @@
pub mod keyboard;
#[cfg(feature = "wayland")]
pub mod mirror;
pub mod screen;
pub mod toast;
pub mod watch;

View File

@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use std::{
ops::Add,
ptr,
sync::{mpsc::Receiver, Arc},
sync::Arc,
time::{Duration, Instant},
};
use vulkano::{
@@ -237,12 +237,22 @@ pub struct ScreenRenderer {
name: Arc<str>,
capture: Box<dyn WlxCapture>,
pipeline: Option<ScreenPipeline>,
receiver: Option<Receiver<WlxFrame>>,
last_view: Option<Arc<ImageView>>,
extent: [u32; 3],
}
impl ScreenRenderer {
#[cfg(feature = "wayland")]
pub fn new_raw(name: Arc<str>, capture: Box<dyn WlxCapture>) -> ScreenRenderer {
ScreenRenderer {
name,
capture,
pipeline: None,
last_view: None,
extent: [0; 3],
}
}
#[cfg(feature = "wayland")]
pub fn new_wlr(output: &WlxOutput) -> Option<ScreenRenderer> {
let client = WlxClient::new()?;
@@ -252,7 +262,6 @@ impl ScreenRenderer {
name: output.name.clone(),
capture: Box::new(capture),
pipeline: None,
receiver: None,
last_view: None,
extent: extent_from_res(output.size),
})
@@ -268,7 +277,7 @@ impl ScreenRenderer {
)> {
let name = output.name.clone();
let select_screen_result =
futures::executor::block_on(pipewire_select_screen(token)).ok()?;
futures::executor::block_on(pipewire_select_screen(token, true, true, true)).ok()?;
let capture = PipewireCapture::new(name, select_screen_result.node_id, 60);
@@ -277,7 +286,6 @@ impl ScreenRenderer {
name: output.name.clone(),
capture: Box::new(capture),
pipeline: None,
receiver: None,
last_view: None,
extent: extent_from_res(output.size),
},
@@ -293,7 +301,6 @@ impl ScreenRenderer {
name: screen.name.clone(),
capture: Box::new(capture),
pipeline: None,
receiver: None,
last_view: None,
extent: extent_from_res((screen.monitor.width(), screen.monitor.height())),
}
@@ -305,7 +312,7 @@ impl OverlayRenderer for ScreenRenderer {
Ok(())
}
fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> {
let receiver = self.receiver.get_or_insert_with(|| {
if !self.capture.ready() {
let allow_dmabuf = &*app.session.config.capture_method != "pw_fallback";
let drm_formats = DRM_FORMATS.get_or_init({
@@ -349,14 +356,11 @@ impl OverlayRenderer for ScreenRenderer {
}
});
let rx = self.capture.init(&drm_formats);
self.capture.init(&drm_formats);
self.capture.request_new_frame();
rx
});
};
let mut mouse = None;
for frame in receiver.try_iter() {
for frame in self.capture.receive().into_iter() {
match frame {
WlxFrame::Dmabuf(frame) => {
if !frame.is_valid() {
@@ -425,29 +429,29 @@ impl OverlayRenderer for ScreenRenderer {
upload.texture2d(frame.format.width, frame.format.height, format, &data)?;
let mut pipeline = None;
if mouse.is_some() {
let new_pipeline = self.pipeline.get_or_insert_with(|| {
let mut pipeline = ScreenPipeline::new(&self.extent, app).unwrap(); // TODO
self.last_view = Some(pipeline.view.clone());
pipeline.ensure_mouse_initialized(&mut upload).unwrap(); // TODO
pipeline
if frame.mouse.is_some() {
pipeline = Some(match self.pipeline {
Some(ref mut p) => p,
_ => {
let mut pipeline = ScreenPipeline::new(&self.extent, app)?;
self.last_view = Some(pipeline.view.clone());
pipeline.ensure_mouse_initialized(&mut upload)?;
self.pipeline = Some(pipeline);
self.pipeline.as_mut().unwrap() // safe
}
});
pipeline = Some(new_pipeline);
}
upload.build_and_execute_now()?;
if let Some(pipeline) = pipeline {
pipeline.render(image, mouse.as_ref(), app)?;
pipeline.render(image, frame.mouse.as_ref(), app)?;
} else {
let view = ImageView::new_default(image)?;
self.last_view = Some(view);
}
self.capture.request_new_frame();
}
WlxFrame::Mouse(m) => {
mouse = Some(m);
}
};
}
Ok(())
@@ -463,9 +467,6 @@ impl OverlayRenderer for ScreenRenderer {
fn view(&mut self) -> Option<Arc<ImageView>> {
self.last_view.clone()
}
fn extent(&self) -> [u32; 3] {
self.extent.clone()
}
}
#[cfg(feature = "wayland")]
@@ -489,7 +490,6 @@ where
output.logical_pos,
);
let size = (output.size.0, output.size.1);
let mut capture: Option<ScreenRenderer> = None;
if &*session.config.capture_method == "auto" && wl.maybe_wlr_dmabuf_mgr.is_some() {
@@ -576,7 +576,6 @@ where
Some(OverlayData {
state: OverlayState {
name: output.name.clone(),
size,
show_hide: session
.config
.show_screens
@@ -741,7 +740,6 @@ where
OverlayData {
state: OverlayState {
name: s.name.clone(),
size,
show_hide: session
.config
.show_screens

View File

@@ -8,14 +8,14 @@ use std::{
use chrono::Local;
use chrono_tz::Tz;
use glam::{vec2, Affine2, Quat, Vec3, Vec3A};
use glam::{Quat, Vec3, Vec3A};
use serde::Deserialize;
use crate::{
backend::{
common::{OverlaySelector, TaskType},
input::PointerMode,
overlay::{OverlayData, OverlayState, RelativeTo},
overlay::{ui_transform, OverlayData, OverlayState, RelativeTo},
},
config::load_watch,
gui::{color_parse, CanvasBuilder, Control},
@@ -270,24 +270,37 @@ where
};
}
}
#[cfg(feature = "wayland")]
WatchElement::MirrorButton {
rect: [x, y, w, h],
font_size,
bg_color,
fg_color,
text,
name,
show_hide,
} => {
canvas.bg_color = color_parse(&bg_color).unwrap_or(FALLBACK_COLOR);
canvas.fg_color = color_parse(&fg_color).unwrap_or(FALLBACK_COLOR);
canvas.font_size = font_size;
let button = canvas.button(x, y, w, h, text.clone());
button.state = Some(ElemState::Mirror { name, show_hide });
button.on_press = Some(btn_mirror_dn::<O>);
}
}
}
let interaction_transform =
Affine2::from_translation(vec2(0.5, 0.5)) * Affine2::from_scale(vec2(1., -2.0));
let relative_to = RelativeTo::Hand(state.session.watch_hand);
Ok(OverlayData {
state: OverlayState {
name: WATCH_NAME.into(),
size: (400, 200),
want_visible: true,
interactable: true,
spawn_scale: WATCH_SCALE * state.session.config.watch_scale,
spawn_point: state.session.watch_pos.into(),
spawn_rotation: state.session.watch_rot,
interaction_transform,
interaction_transform: ui_transform(&config.watch_size),
relative_to,
..Default::default()
},
@@ -328,6 +341,60 @@ enum ElemState {
func_right: Option<ButtonFunc>,
func_middle: Option<ButtonFunc>,
},
#[cfg(feature = "wayland")]
Mirror { name: Arc<str>, show_hide: bool },
}
#[cfg(feature = "wayland")]
fn btn_mirror_dn<O>(
control: &mut Control<(), ElemState>,
_: &mut (),
app: &mut AppState,
mode: PointerMode,
) where
O: Default,
{
let ElemState::Mirror { name, show_hide } = control.state.as_ref().unwrap()
// want panic
else {
log::error!("Mirror state not found");
return;
};
let selector = OverlaySelector::Name(name.clone());
match mode {
PointerMode::Left => {
app.tasks.enqueue(TaskType::Overlay(
selector.clone(),
Box::new(|_app, o| {
o.want_visible = !o.want_visible;
}),
));
app.tasks.enqueue(TaskType::CreateOverlay(
selector,
Box::new({
let name = name.clone();
let show_hide = *show_hide;
move |app| super::mirror::new_mirror(name.clone(), show_hide, &app.session)
}),
));
}
PointerMode::Right => {
app.tasks.enqueue(TaskType::Overlay(
selector,
Box::new(|_app, o| {
o.grabbable = !o.grabbable;
o.interactable = o.grabbable;
}),
));
}
PointerMode::Middle => {
app.tasks.enqueue(TaskType::DropOverlay(selector));
}
_ => {}
}
}
fn btn_func_dn(
@@ -773,6 +840,16 @@ enum WatchElement {
func_middle: Option<ButtonFunc>,
text: Arc<str>,
},
#[cfg(feature = "wayland")]
MirrorButton {
rect: [f32; 4],
font_size: isize,
bg_color: Arc<str>,
fg_color: Arc<str>,
name: Arc<str>,
text: Arc<str>,
show_hide: bool,
},
}
#[derive(Deserialize)]

View File

@@ -132,14 +132,36 @@ watch_elements:
# exec: ["echo", "customize me! see watch.yaml"]
# interval: 0 # seconds
# volume buttons
- type: ExecButton
### MirrorButton
# Bring an additional PipeWire screen, window, region or virtual screen share into VR.
# These are view-only, and will not respond to pointers by moving your mouse.
# You may have as many as you like, but the `name` must be unique for each.
# Controls:
# - Blue Click: Show/hide. Shows pipewire prompt on first show.
# - Orange Click: Toggle lock in place
# - Purple Click: Stop capture. After doing this, you may Blue-click again to select a different source.
# Warning:
# - Window shares may stop updating if the window goes off-screen or is on an inactive workspace
# - Resizing, minimizing, maximizing windows may break stuff. Complain to your xdg-desktop-portal implementation.
# - Selections are not saved across sessions
- type: MirrorButton
rect: [327, 52, 46, 32]
font_size: 14
fg_color: "#FFFFFF"
bg_color: "#505050"
text: "+"
exec: [ "pactl", "set-sink-volume", "@DEFAULT_SINK@", "+5%" ]
bg_color: "#B05050"
name: "M1"
text: "M"
show_hide: false # should it respond to show/hide binding?
# volume buttons
# - type: ExecButton
#rect: [327, 52, 46, 32]
#font_size: 14
#fg_color: "#FFFFFF"
#bg_color: "#505050"
# text: "+"
# exec: [ "pactl", "set-sink-volume", "@DEFAULT_SINK@", "+5%" ]
- type: ExecButton
rect: [327, 116, 46, 32]
font_size: 14