omg big commit
This commit is contained in:
@@ -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
135
src/overlays/mirror.rs
Normal 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))
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
pub mod keyboard;
|
||||
#[cfg(feature = "wayland")]
|
||||
pub mod mirror;
|
||||
pub mod screen;
|
||||
pub mod toast;
|
||||
pub mod watch;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user