From 4abedacdfa415d5488dae094f1bb72f242be1c92 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 30 Dec 2022 15:21:05 -0800 Subject: [PATCH] Initial toplevel capture --- src/main.rs | 103 ++++++++++++++++++++++++++++++++----------------- src/wayland.rs | 68 ++++++++++++++++++++++++-------- 2 files changed, 119 insertions(+), 52 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0b1f754..f921a98 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,17 @@ use cctk::{ - cosmic_protocols::workspace::v1::client::zcosmic_workspace_handle_v1, + cosmic_protocols::{ + toplevel_info::v1::client::zcosmic_toplevel_handle_v1, + workspace::v1::client::zcosmic_workspace_handle_v1, + }, sctk::shell::layer::{Anchor, KeyboardInteractivity, Layer}, + toplevel_info::ToplevelInfo, wayland_client::protocol::wl_output, }; use iced::{ event::wayland::{Event as WaylandEvent, OutputEvent}, keyboard::KeyCode, sctk_settings::InitialSurface, - Application, Command, Element, Subscription, + widget, Application, Command, Element, Subscription, }; use iced_native::{ command::platform_specific::wayland::layer_surface::{IcedOutput, SctkLayerSurfaceSettings}, @@ -21,12 +25,13 @@ use std::{collections::HashMap, process}; mod wayland; -#[derive(Debug)] +#[derive(Clone, Debug)] enum Msg { WaylandEvent(WaylandEvent), Wayland(wayland::Event), Close, Closed(SurfaceIdWrapper), + ActivateWorkspace(zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1), } #[derive(Debug)] @@ -37,6 +42,13 @@ struct Workspace { output_name: Option, } +#[derive(Debug)] +struct Toplevel { + handle: zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + info: ToplevelInfo, + img: Option, +} + struct LayerSurface { output: wl_output::WlOutput, output_name: Option, @@ -50,6 +62,7 @@ struct App { max_surface_id: usize, layer_surfaces: HashMap, workspaces: Vec, + toplevels: Vec, } impl App { @@ -115,7 +128,6 @@ impl Application for App { _ => {} }, Msg::Wayland(evt) => { - println!("{:?}", evt); match evt { wayland::Event::Workspaces(workspaces) => { // XXX efficiency @@ -128,9 +140,16 @@ impl Application for App { output_name, img: None, }); - println!("add workspace"); } } + wayland::Event::NewToplevel(handle, info) => { + println!("New toplevel"); + self.toplevels.push(Toplevel { + handle, + info, + img: None, + }); + } wayland::Event::WorkspaceCapture(workspace, image) => { // XXX performance for i in &mut self.workspaces { @@ -139,12 +158,23 @@ impl Application for App { } } } + wayland::Event::ToplevelCapture(toplevel, image) => { + println!("Toplevel capture"); + for i in &mut self.toplevels { + if &i.handle == &toplevel { + i.img = Some(image.clone()); + } + } + } } } Msg::Close => { std::process::exit(0); } Msg::Closed(_) => {} + Msg::ActivateWorkspace(workspace) => { + // TODO + } } Command::none() @@ -152,7 +182,6 @@ impl Application for App { fn subscription(&self) -> Subscription { let events = iced::subscription::events_with(|evt, _| { - //println!("{:?}", evt); if let iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(evt)) = evt { Some(Msg::WaylandEvent(evt)) @@ -185,30 +214,28 @@ impl Application for App { } fn layer_surface<'a>(app: &'a App, surface: &'a LayerSurface) -> cosmic::Element<'a, Msg> { - workspaces_sidebar( - app.workspaces - .iter() - .filter(|i| &i.output_name == &surface.output_name), - ) + widget::column![ + workspaces_sidebar( + app.workspaces + .iter() + .filter(|i| &i.output_name == &surface.output_name), + ), + toplevel_previews(app.toplevels.iter()) // XXX + ] + .height(iced::Length::Fill) + .width(iced::Length::Fill) + .into() } fn workspace_sidebar_entry(workspace: &Workspace) -> cosmic::Element { - // x to close - // captured preview - // number name - // - selectable - iced::widget::column![ - iced::widget::Image::new( - workspace - .img - .clone() - .unwrap_or_else(|| iced::widget::image::Handle::from_pixels( - 0, - 0, - vec![0, 0, 0, 255] - )) - ), - iced::widget::text(&workspace.name) + // Indicate active workspace? + widget::column![ + widget::button(widget::text("X")), // TODO close button + widget::button(widget::Image::new(workspace.img.clone().unwrap_or_else( + || widget::image::Handle::from_pixels(0, 0, vec![0, 0, 0, 255]) + ))) + .on_press(Msg::ActivateWorkspace(workspace.handle.clone())), + widget::text(&workspace.name) ] .height(iced::Length::Fill) .width(iced::Length::Fill) @@ -218,21 +245,25 @@ fn workspace_sidebar_entry(workspace: &Workspace) -> cosmic::Element { fn workspaces_sidebar<'a>( workspaces: impl Iterator, ) -> cosmic::Element<'a, Msg> { - iced::widget::column(workspaces.map(workspace_sidebar_entry).collect()).into() + widget::column(workspaces.map(workspace_sidebar_entry).collect()).into() // New workspace } -/* -fn window_preview(&Window) -> cosmic::Element { - // capture of window - // - selectable - // name of window +fn toplevel_preview<'a>(toplevel: &'a Toplevel) -> cosmic::Element<'a, Msg> { + // capture of window + // - selectable + // name of window + widget::button(widget::Image::new(toplevel.img.clone().unwrap_or_else( + || widget::image::Handle::from_pixels(0, 0, vec![0, 0, 0, 255]), + ))) + .into() } -fn window_previews(windows: &[Window]) -> cosmic::Element { - iced::widgets::row(windows.iter().map(window_preview).collect()) +fn toplevel_previews<'a>( + toplevels: impl Iterator, +) -> cosmic::Element<'a, Msg> { + widget::row(toplevels.map(toplevel_preview).collect()).into() } -*/ pub fn main() -> iced::Result { App::run(iced::Settings { diff --git a/src/wayland.rs b/src/wayland.rs index 94211ad..b5a06df 100644 --- a/src/wayland.rs +++ b/src/wayland.rs @@ -4,6 +4,8 @@ // shown on one. // * Need output name to compare? +// TODO: Way to activate workspace, toplevel? Close? Move? + use cctk::{ cosmic_protocols::{ screencopy::v1::client::{zcosmic_screencopy_manager_v1, zcosmic_screencopy_session_v1}, @@ -17,7 +19,7 @@ use cctk::{ registry::{ProvidesRegistryState, RegistryState}, shm::{raw::RawPool, ShmHandler, ShmState}, }, - toplevel_info::{ToplevelInfoHandler, ToplevelInfoState}, + toplevel_info::{ToplevelInfo, ToplevelInfoHandler, ToplevelInfoState}, wayland_client::{ backend::ObjectId, globals::registry_queue_init, @@ -35,7 +37,7 @@ use std::{collections::HashMap, thread}; // TODO define subscription for a particular output/workspace/toplevel (but we want to rate limit?) -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum Event { // XXX Output name rather than `WlOutput` Workspaces(Vec<(Option, cctk::workspace::Workspace)>), @@ -43,6 +45,14 @@ pub enum Event { zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1, image::Handle, ), + NewToplevel( + zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + ToplevelInfo, + ), + ToplevelCapture( + zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + image::Handle, + ), } pub fn subscription() -> iced::Subscription { @@ -50,6 +60,7 @@ pub fn subscription() -> iced::Subscription { } enum CaptureSource { + Toplevel(zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1), Workspace(zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1), } @@ -72,6 +83,12 @@ struct AppData { output_names: HashMap>, } +impl AppData { + fn send_event(&mut self, event: Event) { + let _ = block_on(self.sender.send(event)); + } +} + impl ProvidesRegistryState for AppData { fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state @@ -132,6 +149,27 @@ impl ToplevelInfoHandler for AppData { _qh: &QueueHandle, toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, ) { + let info = self.toplevel_info_state.info(&toplevel).unwrap(); + self.send_event(Event::NewToplevel( + toplevel.clone(), + info.clone(), + )); + + let frame = self.screencopy_state.screencopy_manager.capture_toplevel( + toplevel, + zcosmic_screencopy_manager_v1::CursorMode::Hidden, + &self.qh, + Default::default(), // TODO + ); + // XXX first_frame + self.frames.insert( + frame.id(), + Frame { + buffer: None, + source: CaptureSource::Toplevel(toplevel.clone()), + first_frame: true, + }, + ); } fn update_toplevel( @@ -140,6 +178,7 @@ impl ToplevelInfoHandler for AppData { _qh: &QueueHandle, toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, ) { + // TODO } fn toplevel_closed( @@ -148,6 +187,7 @@ impl ToplevelInfoHandler for AppData { _qh: &QueueHandle, toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, ) { + // TODO } } @@ -165,7 +205,6 @@ impl WorkspaceHandler for AppData { let output_name = self.output_names.get(&output.id()).unwrap().clone(); workspaces.push((output_name, workspace.clone())); - //println!("capture workspace"); let frame = self.screencopy_state.screencopy_manager.capture_workspace( &workspace.handle, output, @@ -179,14 +218,14 @@ impl WorkspaceHandler for AppData { Frame { buffer: None, source: CaptureSource::Workspace(workspace.handle.clone()), - first_frame: false, + first_frame: true, }, ); } } } - let _ = block_on(self.sender.send(Event::Workspaces(workspaces))); + self.send_event(Event::Workspaces(workspaces)); } } @@ -243,19 +282,15 @@ impl ScreencopyHandler for AppData { ) { let frame = self.frames.get_mut(&session.id()).unwrap(); let (mut pool, buffer, buffer_info) = frame.buffer.take().unwrap(); - match &frame.source { + let image = + image::Handle::from_pixels(buffer_info.width, buffer_info.height, pool.mmap().to_vec()); + let event = match &frame.source { + CaptureSource::Toplevel(toplevel) => Event::ToplevelCapture(toplevel.clone(), image), CaptureSource::Workspace(workspace) => { - let image = image::Handle::from_pixels( - buffer_info.width, - buffer_info.height, - pool.mmap().to_vec(), - ); // XXX - let _ = block_on( - self.sender - .send(Event::WorkspaceCapture(workspace.clone(), image)), - ); + Event::WorkspaceCapture(workspace.clone(), image) } - } + }; + self.send_event(event); } fn failed( @@ -266,6 +301,7 @@ impl ScreencopyHandler for AppData { reason: WEnum, ) { // TODO + println!("Failed"); } }