diff --git a/Cargo.toml b/Cargo.toml index 2217239..099db12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ wgpu = ["libcosmic/wgpu"] # Debugging features force-shm-screencopy = [] no-subsurfaces = ["force-shm-screencopy"] +mock-backend = [] [profile.dev] # Not usable at opt-level 0, at least with software renderer diff --git a/src/backend/mock.rs b/src/backend/mock.rs new file mode 100644 index 0000000..bf15f1e --- /dev/null +++ b/src/backend/mock.rs @@ -0,0 +1,216 @@ +// Copyright 2024 System76 +// SPDX-License-Identifier: GPL-3.0-only + +use cosmic::{ + cctk::{ + cosmic_protocols::{ + toplevel_info::v1::client::zcosmic_toplevel_handle_v1, + workspace::v1::client::zcosmic_workspace_handle_v1, + }, + wayland_client::{ + protocol::{wl_output, wl_shm}, + Connection, WEnum, + }, + }, + iced::{ + self, + futures::{executor::block_on, FutureExt, SinkExt}, + }, + iced_sctk::subsurface_widget::{Shmbuf, SubsurfaceBuffer}, +}; + +use futures_channel::mpsc; +use std::{ + collections::HashSet, + fs, + io::Write, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, + thread, +}; + +use super::{CaptureImage, Cmd, Event}; +use crate::utils; + +#[derive(Eq, PartialEq, Clone, Debug, Hash)] +struct MockObjectId(usize); + +fn create_solid_capture_image(r: u8, g: u8, b: u8) -> CaptureImage { + let mut file = fs::File::from(utils::create_memfile().unwrap()); + + for i in 0..512 * 512 { + file.write(&[r, g, b, 255]).unwrap(); + } + + CaptureImage { + width: 512, + height: 512, + wl_buffer: SubsurfaceBuffer::new(Arc::new( + Shmbuf { + fd: file.into(), + offset: 0, + width: 512, + height: 512, + stride: 512 * 4, + format: wl_shm::Format::Abgr8888, + } + .into(), + )) + .0, + } +} + +impl MockObjectId { + fn new() -> Self { + static NEXT_MOCK_ID: AtomicUsize = AtomicUsize::new(0); + Self(NEXT_MOCK_ID.fetch_add(1, Ordering::SeqCst)) + } +} + +#[derive(Eq, PartialEq, Clone, Debug, Hash)] +pub struct ZcosmicWorkspaceHandleV1(MockObjectId); + +#[derive(Eq, PartialEq, Clone, Debug, Hash)] +pub struct ZcosmicToplevelHandleV1(MockObjectId); + +#[derive(Clone, Debug, Default)] +pub struct CaptureFilter { + pub workspaces_on_outputs: Vec, + pub toplevels_on_workspaces: Vec, +} + +#[derive(Clone, Debug, Default)] +pub struct ToplevelInfo { + pub title: String, + pub app_id: String, + pub state: HashSet, + pub output: HashSet, + pub workspace: HashSet, +} + +#[derive(Clone, Debug)] +pub struct Workspace { + pub handle: ZcosmicWorkspaceHandleV1, + pub name: String, + // pub coordinates: Vec, + pub state: Vec>, + // pub capabilities: Vec>, + // pub tiling: Option>, +} + +pub fn subscription(conn: Connection) -> iced::Subscription { + iced::subscription::run_with_id("wayland-mock-sub", async { start(conn) }.flatten_stream()) +} + +struct AppData { + sender: mpsc::Sender, + outputs: Vec, + workspaces: Vec<(HashSet, Workspace)>, +} + +impl AppData { + fn send_event(&mut self, event: Event) { + let _ = block_on(self.sender.send(event)); + } + + fn add_output(&mut self, output: &wl_output::WlOutput) { + // Add four workspaces for each output + let mut new_workspaces = Vec::new(); + for i in 0..=4 { + let workspace_handle = ZcosmicWorkspaceHandleV1(MockObjectId::new()); + let workspace = Workspace { + handle: workspace_handle.clone(), + name: format!("Workspace {i}"), + state: if i == 0 { + vec![WEnum::Value(zcosmic_workspace_handle_v1::State::Active)] + } else { + Vec::new() + }, + }; + // Add three toplevels for each workspace + for j in 0..=3 { + let toplevel_handle = ZcosmicToplevelHandleV1(MockObjectId::new()); + let toplevel_info = ToplevelInfo { + title: format!("App {}", j), + app_id: "com.example.app".to_string(), + state: if i == 0 { + HashSet::from([zcosmic_toplevel_handle_v1::State::Activated]) + } else { + HashSet::new() + }, + output: HashSet::from([output.clone()]), + workspace: HashSet::from([workspace_handle.clone()]), + }; + self.send_event(Event::NewToplevel(toplevel_handle.clone(), toplevel_info)); + self.send_event(Event::ToplevelCapture( + toplevel_handle, + create_solid_capture_image(255, 0, 0), + )); + } + self.workspaces + .push((HashSet::from([output.clone()]), workspace)); + new_workspaces.push(workspace_handle); + } + self.send_event(Event::Workspaces(self.workspaces.clone())); + for workspace_handle in new_workspaces { + self.send_event(Event::WorkspaceCapture( + workspace_handle, + output.clone(), + create_solid_capture_image(0, 255, 0), + )); + } + self.outputs.push(output.clone()); + } + + fn handle_cmd(&mut self, cmd: Cmd) { + match cmd { + Cmd::CaptureFilter(filter) => { + for output in &filter.workspaces_on_outputs { + if !self.outputs.contains(output) { + self.add_output(output); + } + } + } + Cmd::ActivateToplevel(toplevel_handle) => { + println!("Activate {:?}", toplevel_handle); + } + Cmd::CloseToplevel(toplevel_handle) => { + println!("Close {:?}", toplevel_handle); + } + Cmd::MoveToplevelToWorkspace(toplevel_handle, workspace_handle, output) => {} + Cmd::ActivateWorkspace(workspace_handle) => { + println!("Activate {:?}", workspace_handle); + } + } + } +} + +fn start(conn: Connection) -> mpsc::Receiver { + let (sender, receiver) = mpsc::channel(20); + thread::spawn(move || { + let mut event_loop = calloop::EventLoop::try_new().unwrap(); + let (cmd_sender, cmd_channel) = calloop::channel::channel(); + event_loop + .handle() + .insert_source(cmd_channel, |cmd, (), app_data: &mut AppData| match cmd { + calloop::channel::Event::Msg(cmd) => app_data.handle_cmd(cmd), + calloop::channel::Event::Closed => {} + }) + .unwrap(); + + let mut app_data = AppData { + sender, + outputs: Vec::new(), + workspaces: Vec::new(), + }; + app_data.send_event(Event::CmdSender(cmd_sender)); + loop { + event_loop.dispatch(None, &mut app_data).unwrap(); + } + }); + receiver +} + +// TODO WorkspaceCapture, ToplevelCapture, NewToplevel, Workspaces diff --git a/src/backend/mod.rs b/src/backend/mod.rs new file mode 100644 index 0000000..0126624 --- /dev/null +++ b/src/backend/mod.rs @@ -0,0 +1,75 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: GPL-3.0-only + +//! The backend of getting workspace/toplevel information and previews, and +//! sending commands to change them. +//! +//! There are two backends: one that uses cosmic-comp protocols, and a mock +//! backend for testing without any special protocols. + +use cosmic::{ + cctk::wayland_client::protocol::wl_output, iced_sctk::subsurface_widget::SubsurfaceBuffer, +}; +use std::collections::HashSet; + +// Wayland backend using cosmic-comp specific protocols +#[cfg(not(feature = "mock-backend"))] +mod wayland; +#[cfg(not(feature = "mock-backend"))] +pub use cosmic::cctk::{ + cosmic_protocols::{ + toplevel_info::v1::client::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + workspace::v1::client::zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1, + }, + toplevel_info::ToplevelInfo, + workspace::Workspace, +}; +#[cfg(not(feature = "mock-backend"))] +pub use wayland::subscription; + +// Mock backend +#[cfg(feature = "mock-backend")] +mod mock; +#[cfg(feature = "mock-backend")] +pub use mock::{ + subscription, ToplevelInfo, Workspace, ZcosmicToplevelHandleV1, ZcosmicWorkspaceHandleV1, +}; + +#[derive(Clone, Debug, Default)] +pub struct CaptureFilter { + pub workspaces_on_outputs: Vec, + pub toplevels_on_workspaces: Vec, +} + +#[derive(Clone, Debug)] +pub struct CaptureImage { + pub width: u32, + pub height: u32, + pub wl_buffer: SubsurfaceBuffer, + #[cfg(feature = "no-subsurfaces")] + pub image: cosmic::widget::image::Handle, +} + +#[derive(Clone, Debug)] +pub enum Event { + CmdSender(calloop::channel::Sender), + Workspaces(Vec<(HashSet, Workspace)>), + WorkspaceCapture(ZcosmicWorkspaceHandleV1, wl_output::WlOutput, CaptureImage), + NewToplevel(ZcosmicToplevelHandleV1, ToplevelInfo), + UpdateToplevel(ZcosmicToplevelHandleV1, ToplevelInfo), + CloseToplevel(ZcosmicToplevelHandleV1), + ToplevelCapture(ZcosmicToplevelHandleV1, CaptureImage), +} + +#[derive(Debug)] +pub enum Cmd { + CaptureFilter(CaptureFilter), + ActivateToplevel(ZcosmicToplevelHandleV1), + CloseToplevel(ZcosmicToplevelHandleV1), + MoveToplevelToWorkspace( + ZcosmicToplevelHandleV1, + ZcosmicWorkspaceHandleV1, + wl_output::WlOutput, + ), + ActivateWorkspace(ZcosmicWorkspaceHandleV1), +} diff --git a/src/wayland/buffer.rs b/src/backend/wayland/buffer.rs similarity index 82% rename from src/wayland/buffer.rs rename to src/backend/wayland/buffer.rs index 7001d07..849b090 100644 --- a/src/wayland/buffer.rs +++ b/src/backend/wayland/buffer.rs @@ -17,52 +17,7 @@ use std::{ use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_buffer_params_v1; use super::AppData; - -#[cfg(target_os = "linux")] -fn create_memfd() -> rustix::io::Result { - let fd = rustix::io::retry_on_intr(|| { - rustix::fs::memfd_create( - "cosmic-workspaces-shm", - rustix::fs::MemfdFlags::CLOEXEC | rustix::fs::MemfdFlags::ALLOW_SEALING, - ) - })?; - let _ = rustix::fs::fcntl_add_seals( - &fd, - rustix::fs::SealFlags::SHRINK | rustix::fs::SealFlags::SEAL, - ); - Ok(fd) -} - -fn create_memfile() -> rustix::io::Result { - #[cfg(target_os = "linux")] - if let Ok(fd) = create_memfd() { - return Ok(fd); - } - - loop { - let flags = ShmOFlags::CREATE | ShmOFlags::EXCL | ShmOFlags::RDWR; - - let time = SystemTime::now(); - let name = format!( - "/cosmic-workspaces-shm-{}", - time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos() - ); - - match rustix::shm::shm_open(&name, flags, 0600.into()) { - Ok(fd) => match rustix::shm::shm_unlink(&name) { - Ok(_) => return Ok(fd), - Err(errno) => { - return Err(errno.into()); - } - }, - #[allow(unreachable_patterns)] - Err(Errno::EXIST | Errno::EXIST) => { - continue; - } - Err(err) => return Err(err.into()), - } - } -} +use crate::utils; pub struct Buffer { pub backing: Arc, @@ -75,7 +30,7 @@ pub struct Buffer { impl AppData { fn create_shm_buffer(&self, format: u32, (width, height): (u32, u32)) -> Buffer { - let fd = create_memfile().unwrap(); // XXX? + let fd = utils::create_memfile().unwrap(); // XXX? rustix::fs::ftruncate(&fd, width as u64 * height as u64 * 4).unwrap(); let pool = self.shm_state.wl_shm().create_pool( diff --git a/src/wayland/capture.rs b/src/backend/wayland/capture.rs similarity index 88% rename from src/wayland/capture.rs rename to src/backend/wayland/capture.rs index a7f171c..bf7aa82 100644 --- a/src/wayland/capture.rs +++ b/src/backend/wayland/capture.rs @@ -22,12 +22,6 @@ pub enum CaptureSource { ), } -#[derive(Clone, Debug, Default)] -pub struct CaptureFilter { - pub workspaces_on_outputs: Vec, - pub toplevels_on_workspaces: Vec, -} - pub struct Capture { pub source: CaptureSource, pub session: Mutex>, diff --git a/src/wayland/dmabuf.rs b/src/backend/wayland/dmabuf.rs similarity index 100% rename from src/wayland/dmabuf.rs rename to src/backend/wayland/dmabuf.rs diff --git a/src/wayland/mod.rs b/src/backend/wayland/mod.rs similarity index 79% rename from src/wayland/mod.rs rename to src/backend/wayland/mod.rs index e996d8c..1bf3c2f 100644 --- a/src/wayland/mod.rs +++ b/src/backend/wayland/mod.rs @@ -3,10 +3,6 @@ use calloop_wayland_source::WaylandSource; use cctk::{ - cosmic_protocols::{ - toplevel_info::v1::client::zcosmic_toplevel_handle_v1, - workspace::v1::client::zcosmic_workspace_handle_v1, - }, screencopy::ScreencopyState, sctk::{ self, @@ -15,12 +11,10 @@ use cctk::{ seat::{SeatHandler, SeatState}, shm::{Shm, ShmHandler}, }, - toplevel_info::{ToplevelInfo, ToplevelInfoState}, + toplevel_info::ToplevelInfoState, toplevel_management::ToplevelManagerState, wayland_client::{ - globals::registry_queue_init, - protocol::{wl_output, wl_seat}, - Connection, Proxy, QueueHandle, + globals::registry_queue_init, protocol::wl_seat, Connection, Proxy, QueueHandle, }, workspace::WorkspaceState, }; @@ -29,16 +23,8 @@ use cosmic::iced::{ self, futures::{executor::block_on, FutureExt, SinkExt}, }; -use cosmic::iced_sctk::subsurface_widget::SubsurfaceBuffer; use futures_channel::mpsc; -use std::{ - cell::RefCell, - collections::{HashMap, HashSet}, - fs, - path::PathBuf, - sync::Arc, - thread, -}; +use std::{cell::RefCell, collections::HashMap, fs, path::PathBuf, sync::Arc, thread}; mod buffer; use buffer::Buffer; @@ -50,60 +36,12 @@ use screencopy::{ScreencopySession, SessionData}; mod toplevel; mod workspace; -pub use capture::CaptureFilter; - -// TODO define subscription for a particular output/workspace/toplevel (but we want to rate limit?) - -#[derive(Clone, Debug)] -pub enum Event { - CmdSender(calloop::channel::Sender), - Workspaces(Vec<(HashSet, cctk::workspace::Workspace)>), - WorkspaceCapture( - zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1, - wl_output::WlOutput, - CaptureImage, - ), - NewToplevel( - zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, - ToplevelInfo, - ), - UpdateToplevel( - zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, - ToplevelInfo, - ), - CloseToplevel(zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1), - ToplevelCapture( - zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, - CaptureImage, - ), -} - -#[derive(Clone, Debug)] -pub struct CaptureImage { - pub width: u32, - pub height: u32, - pub wl_buffer: SubsurfaceBuffer, - #[cfg(feature = "no-subsurfaces")] - pub image: cosmic::widget::image::Handle, -} +use super::{CaptureFilter, CaptureImage, Cmd, Event}; pub fn subscription(conn: Connection) -> iced::Subscription { iced::subscription::run_with_id("wayland-sub", async { start(conn) }.flatten_stream()) } -#[derive(Debug)] -pub enum Cmd { - CaptureFilter(CaptureFilter), - ActivateToplevel(zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1), - CloseToplevel(zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1), - MoveToplevelToWorkspace( - zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, - zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1, - wl_output::WlOutput, - ), - ActivateWorkspace(zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1), -} - pub struct AppData { qh: QueueHandle, dmabuf_state: DmabufState, diff --git a/src/wayland/screencopy.rs b/src/backend/wayland/screencopy.rs similarity index 100% rename from src/wayland/screencopy.rs rename to src/backend/wayland/screencopy.rs diff --git a/src/wayland/toplevel.rs b/src/backend/wayland/toplevel.rs similarity index 100% rename from src/wayland/toplevel.rs rename to src/backend/wayland/toplevel.rs diff --git a/src/wayland/workspace.rs b/src/backend/wayland/workspace.rs similarity index 100% rename from src/wayland/workspace.rs rename to src/backend/wayland/workspace.rs diff --git a/src/main.rs b/src/main.rs index 6391ce5..1b254b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,14 +4,9 @@ #![allow(clippy::single_match)] use cctk::{ - cosmic_protocols::{ - toplevel_info::v1::client::zcosmic_toplevel_handle_v1, - workspace::v1::client::zcosmic_workspace_handle_v1, - }, + cosmic_protocols::workspace::v1::client::zcosmic_workspace_handle_v1, sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer}, - toplevel_info::ToplevelInfo, wayland_client::{ - backend::ObjectId, protocol::{wl_data_device_manager::DndAction, wl_output}, Connection, Proxy, WEnum, }, @@ -52,8 +47,10 @@ use std::{ mod desktop_info; #[macro_use] mod localize; +mod backend; mod view; -mod wayland; +use backend::{ToplevelInfo, ZcosmicToplevelHandleV1, ZcosmicWorkspaceHandleV1}; +mod utils; mod widgets; #[derive(Clone, Debug, Default, PartialEq, CosmicConfigEntry)] @@ -94,14 +91,13 @@ impl CosmicFlags for Args { } struct WlDndId { - id: ObjectId, mime_type: &'static str, } impl DataFromMimeType for WlDndId { fn from_mime_type(&self, mime_type: &str) -> Option> { if mime_type == self.mime_type { - Some(self.id.protocol_id().to_string().into_bytes()) + Some(Vec::new()) } else { None } @@ -111,16 +107,16 @@ impl DataFromMimeType for WlDndId { #[derive(Clone, Debug)] enum Msg { WaylandEvent(WaylandEvent), - Wayland(wayland::Event), + Wayland(backend::Event), Close, - ActivateWorkspace(zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1), + ActivateWorkspace(ZcosmicWorkspaceHandleV1), #[allow(dead_code)] - CloseWorkspace(zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1), - ActivateToplevel(zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1), - CloseToplevel(zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1), + CloseWorkspace(ZcosmicWorkspaceHandleV1), + ActivateToplevel(ZcosmicToplevelHandleV1), + CloseToplevel(ZcosmicToplevelHandleV1), StartDrag(Size, DragSurface), DndWorkspaceEnter( - zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1, + ZcosmicWorkspaceHandleV1, wl_output::WlOutput, DndAction, Vec, @@ -139,17 +135,17 @@ enum Msg { #[derive(Debug)] struct Workspace { name: String, - img_for_output: HashMap, - handle: zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1, + img_for_output: HashMap, + handle: ZcosmicWorkspaceHandleV1, outputs: HashSet, is_active: bool, } #[derive(Debug)] struct Toplevel { - handle: zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + handle: ZcosmicToplevelHandleV1, info: ToplevelInfo, - img: Option, + img: Option, icon: Option, } @@ -171,11 +167,11 @@ struct LayerSurface { enum DragSurface { #[allow(dead_code)] Workspace { - handle: zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1, + handle: ZcosmicWorkspaceHandleV1, output: wl_output::WlOutput, }, Toplevel { - handle: zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + handle: ZcosmicToplevelHandleV1, output: wl_output::WlOutput, }, } @@ -202,34 +198,28 @@ struct App { toplevels: Vec, conn: Option, visible: bool, - wayland_cmd_sender: Option>, + wayland_cmd_sender: Option>, drag_surface: Option<(SurfaceId, DragSurface, Size)>, conf: Conf, core: cosmic::app::Core, - drop_target: Option<( - zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1, - wl_output::WlOutput, - )>, + drop_target: Option<(ZcosmicWorkspaceHandleV1, wl_output::WlOutput)>, } impl App { - fn workspace_for_handle( - &self, - handle: &zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1, - ) -> Option<&Workspace> { + fn workspace_for_handle(&self, handle: &ZcosmicWorkspaceHandleV1) -> Option<&Workspace> { self.workspaces.iter().find(|i| &i.handle == handle) } fn workspace_for_handle_mut( &mut self, - handle: &zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1, + handle: &ZcosmicWorkspaceHandleV1, ) -> Option<&mut Workspace> { self.workspaces.iter_mut().find(|i| &i.handle == handle) } fn toplevel_for_handle_mut( &mut self, - handle: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + handle: &ZcosmicToplevelHandleV1, ) -> Option<&mut Toplevel> { self.toplevels.iter_mut().find(|i| &i.handle == handle) } @@ -311,14 +301,14 @@ impl App { ) } - fn send_wayland_cmd(&self, cmd: wayland::Cmd) { + fn send_wayland_cmd(&self, cmd: backend::Cmd) { if let Some(sender) = self.wayland_cmd_sender.as_ref() { sender.send(cmd).unwrap(); } } fn update_capture_filter(&self) { - let mut capture_filter = wayland::CaptureFilter::default(); + let mut capture_filter = backend::CaptureFilter::default(); if self.visible { capture_filter.workspaces_on_outputs = self.outputs.iter().map(|x| x.handle.clone()).collect(); @@ -329,7 +319,7 @@ impl App { .map(|x| x.handle.clone()) .collect(); } - self.send_wayland_cmd(wayland::Cmd::CaptureFilter(capture_filter)); + self.send_wayland_cmd(backend::Cmd::CaptureFilter(capture_filter)); } } @@ -413,10 +403,10 @@ impl Application for App { }, Msg::Wayland(evt) => { match evt { - wayland::Event::CmdSender(sender) => { + backend::Event::CmdSender(sender) => { self.wayland_cmd_sender = Some(sender); } - wayland::Event::Workspaces(workspaces) => { + backend::Event::Workspaces(workspaces) => { let old_workspaces = mem::take(&mut self.workspaces); self.workspaces = Vec::new(); for (outputs, workspace) in workspaces { @@ -441,7 +431,7 @@ impl Application for App { } self.update_capture_filter(); } - wayland::Event::NewToplevel(handle, info) => { + backend::Event::NewToplevel(handle, info) => { log::debug!("New toplevel: {info:?}"); self.toplevels.push(Toplevel { icon: desktop_info::icon_for_app_id(info.app_id.clone()), @@ -450,7 +440,7 @@ impl Application for App { img: None, }); } - wayland::Event::UpdateToplevel(handle, info) => { + backend::Event::UpdateToplevel(handle, info) => { if let Some(toplevel) = self.toplevels.iter_mut().find(|x| x.handle == handle) { @@ -458,17 +448,17 @@ impl Application for App { toplevel.info = info; } } - wayland::Event::CloseToplevel(handle) => { + backend::Event::CloseToplevel(handle) => { if let Some(idx) = self.toplevels.iter().position(|x| x.handle == handle) { self.toplevels.remove(idx); } } - wayland::Event::WorkspaceCapture(handle, output_name, image) => { + backend::Event::WorkspaceCapture(handle, output_name, image) => { if let Some(workspace) = self.workspace_for_handle_mut(&handle) { workspace.img_for_output.insert(output_name, image); } } - wayland::Event::ToplevelCapture(handle, image) => { + backend::Event::ToplevelCapture(handle, image) => { if let Some(toplevel) = self.toplevel_for_handle_mut(&handle) { //println!("Got toplevel image!"); toplevel.img = Some(image); @@ -480,10 +470,10 @@ impl Application for App { return self.hide(); } Msg::ActivateWorkspace(workspace_handle) => { - self.send_wayland_cmd(wayland::Cmd::ActivateWorkspace(workspace_handle)); + self.send_wayland_cmd(backend::Cmd::ActivateWorkspace(workspace_handle)); } Msg::ActivateToplevel(toplevel_handle) => { - self.send_wayland_cmd(wayland::Cmd::ActivateToplevel(toplevel_handle)); + self.send_wayland_cmd(backend::Cmd::ActivateToplevel(toplevel_handle)); return self.hide(); } Msg::CloseWorkspace(_workspace_handle) => { @@ -502,16 +492,12 @@ impl Application for App { } Msg::CloseToplevel(toplevel_handle) => { // TODO confirmation? - self.send_wayland_cmd(wayland::Cmd::CloseToplevel(toplevel_handle)); + self.send_wayland_cmd(backend::Cmd::CloseToplevel(toplevel_handle)); } Msg::StartDrag(size, drag_surface) => { - let (wl_id, output, mime_type) = match &drag_surface { - DragSurface::Workspace { handle, output } => { - (handle.clone().id(), output, &*WORKSPACE_MIME) - } - DragSurface::Toplevel { handle, output } => { - (handle.clone().id(), output, &*TOPLEVEL_MIME) - } + let (output, mime_type) = match &drag_surface { + DragSurface::Workspace { handle, output } => (output, &*WORKSPACE_MIME), + DragSurface::Toplevel { handle, output } => (output, &*TOPLEVEL_MIME), }; let id = SurfaceId::unique(); if let Some((parent_id, _)) = self @@ -525,10 +511,7 @@ impl Application for App { DndAction::Move, *parent_id, Some(DndIcon::Custom(id)), - Box::new(WlDndId { - id: wl_id, - mime_type, - }), + Box::new(WlDndId { mime_type }), ); } } @@ -559,7 +542,7 @@ impl Application for App { .and_then(|s| u32::from_str(s).ok()); if let Some((_, DragSurface::Toplevel { handle, .. }, _)) = &self.drag_surface { if let Some(drop_target) = &self.drop_target { - self.send_wayland_cmd(wayland::Cmd::MoveToplevelToWorkspace( + self.send_wayland_cmd(backend::Cmd::MoveToplevelToWorkspace( handle.clone(), drop_target.0.clone(), drop_target.1.clone(), @@ -640,7 +623,7 @@ impl Application for App { }); let mut subscriptions = vec![events, config_subscription, comp_config_subscription]; if let Some(conn) = self.conn.clone() { - subscriptions.push(wayland::subscription(conn).map(Msg::Wayland)); + subscriptions.push(backend::subscription(conn).map(Msg::Wayland)); } iced::Subscription::batch(subscriptions) } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..c3c4bbc --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,51 @@ +use rustix::{io::Errno, shm::ShmOFlags}; +use std::{ + os::fd::OwnedFd, + time::{SystemTime, UNIX_EPOCH}, +}; + +#[cfg(target_os = "linux")] +fn create_memfd() -> rustix::io::Result { + let fd = rustix::io::retry_on_intr(|| { + rustix::fs::memfd_create( + "cosmic-workspaces-shm", + rustix::fs::MemfdFlags::CLOEXEC | rustix::fs::MemfdFlags::ALLOW_SEALING, + ) + })?; + let _ = rustix::fs::fcntl_add_seals( + &fd, + rustix::fs::SealFlags::SHRINK | rustix::fs::SealFlags::SEAL, + ); + Ok(fd) +} + +pub fn create_memfile() -> rustix::io::Result { + #[cfg(target_os = "linux")] + if let Ok(fd) = create_memfd() { + return Ok(fd); + } + + loop { + let flags = ShmOFlags::CREATE | ShmOFlags::EXCL | ShmOFlags::RDWR; + + let time = SystemTime::now(); + let name = format!( + "/cosmic-workspaces-shm-{}", + time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos() + ); + + match rustix::shm::shm_open(&name, flags, 0600.into()) { + Ok(fd) => match rustix::shm::shm_unlink(&name) { + Ok(_) => return Ok(fd), + Err(errno) => { + return Err(errno.into()); + } + }, + #[allow(unreachable_patterns)] + Err(Errno::EXIST | Errno::EXIST) => { + continue; + } + Err(err) => return Err(err.into()), + } + } +} diff --git a/src/view/mod.rs b/src/view/mod.rs index 442dab3..95d1ff2 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -1,8 +1,5 @@ use cctk::{ - cosmic_protocols::{ - toplevel_info::v1::client::zcosmic_toplevel_handle_v1, - workspace::v1::client::zcosmic_workspace_handle_v1, - }, + cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1, wayland_client::protocol::wl_output, }; use cosmic::{ @@ -19,7 +16,10 @@ use cosmic::{ }; use cosmic_comp_config::workspace::WorkspaceLayout; -use crate::{wayland::CaptureImage, App, DragSurface, LayerSurface, Msg, Toplevel, Workspace}; +use crate::{ + backend::{self, CaptureImage}, + App, DragSurface, LayerSurface, Msg, Toplevel, Workspace, +}; pub(crate) fn layer_surface<'a>( app: &'a App, @@ -148,7 +148,7 @@ fn workspaces_sidebar<'a>( workspaces: impl Iterator, output: &'a wl_output::WlOutput, layout: WorkspaceLayout, - drop_target: Option<&zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1>, + drop_target: Option<&backend::ZcosmicWorkspaceHandleV1>, ) -> cosmic::Element<'a, Msg> { let sidebar_entries = workspaces .map(|w| workspace_sidebar_entry(w, output, drop_target == Some(&w.handle))) @@ -272,7 +272,7 @@ fn toplevel_previews<'a>( toplevels: impl Iterator, output: &'a wl_output::WlOutput, layout: WorkspaceLayout, - drag_toplevel: Option<&'a zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1>, + drag_toplevel: Option<&'a backend::ZcosmicToplevelHandleV1>, ) -> cosmic::Element<'a, Msg> { let (width, height) = match layout { WorkspaceLayout::Vertical => (iced::Length::FillPortion(4), iced::Length::Fill),