From e5aca0a6b5a94908ed4a8e9efcf0031ea3316f5c Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 6 Dec 2023 10:03:39 -0800 Subject: [PATCH] Allow dragging toplevel --- Cargo.lock | 1 + Cargo.toml | 1 + src/main.rs | 85 ++++++++++++++++++++++++++++++++++++++++++------- src/view/mod.rs | 55 ++++++++++++++++++++++---------- 4 files changed, 114 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d275cb1..7c51a64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -876,6 +876,7 @@ dependencies = [ "gbm", "libcosmic", "memmap2 0.9.0", + "once_cell", "tokio", "wayland-protocols 0.31.0", "zbus", diff --git a/Cargo.toml b/Cargo.toml index dd76218..9f3631e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ memmap2 = "0.9.0" tokio = "1.23.0" wayland-protocols = "0.31.0" zbus = { version = "3.7.0", default-features = false, features = ["tokio"] } +once_cell = "1.18.0" [profile.dev] # Not usable at opt-level 0, at least with software renderer diff --git a/src/main.rs b/src/main.rs index 1791bf3..9444b49 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use cctk::{ sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer}, toplevel_info::ToplevelInfo, wayland_client::{ + backend::ObjectId, protocol::{wl_data_device_manager::DndAction, wl_output, wl_seat}, Connection, Proxy, WEnum, }, @@ -36,6 +37,7 @@ use cosmic::{ iced_sctk::commands::layer_surface::{destroy_layer_surface, get_layer_surface}, }; use cosmic_config::ConfigGet; +use once_cell::sync::Lazy; use std::{ collections::{HashMap, HashSet}, mem, @@ -44,7 +46,13 @@ use std::{ mod view; mod wayland; -static WORKSPACE_MIME: &str = "text/x.cosmic-workspace-id"; +// Include `pid` in mime. Want to drag between our surfaces, but not another +// process, if we use Wayland object ids. +static WORKSPACE_MIME: Lazy = + Lazy::new(|| format!("text/x.cosmic-workspace-id-{}", std::process::id())); + +static TOPLEVEL_MIME: Lazy = + Lazy::new(|| format!("text/x.cosmic-toplevel-id-{}", std::process::id())); #[derive(Parser, Debug, Clone)] #[command(author, version, about, long_about = None)] @@ -69,11 +77,26 @@ 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()) + } else { + None + } + } +} + struct WorkspaceDndId(String); impl DataFromMimeType for WorkspaceDndId { fn from_mime_type(&self, mime_type: &str) -> Option> { - if mime_type == WORKSPACE_MIME { + if mime_type == *WORKSPACE_MIME { Some(self.0.as_bytes().to_vec()) } else { None @@ -135,6 +158,19 @@ enum DragSurface { name: String, output: wl_output::WlOutput, }, + Toplevel { + handle: zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + output: wl_output::WlOutput, + }, +} + +impl DragSurface { + fn output(&self) -> &wl_output::WlOutput { + match self { + Self::Workspace { output, .. } => output, + Self::Toplevel { output, .. } => output, + } + } } struct Conf { @@ -473,23 +509,38 @@ impl Application for App { } } Msg::StartDrag(size, drag_surface) => { - match &drag_surface { - DragSurface::Workspace { output, name: _ } => { - let id = self.next_surface_id(); - if let Some((parent_id, _)) = self - .layer_surfaces - .iter() - .find(|(_, x)| &x.output == output) - { + let output = drag_surface.output(); + let id = self.next_surface_id(); + if let Some((parent_id, _)) = self + .layer_surfaces + .iter() + .find(|(_, x)| &x.output == output) + { + match &drag_surface { + DragSurface::Workspace { output, name: _ } => { self.drag_surface = Some((id, drag_surface, size)); return start_drag( vec![WORKSPACE_MIME.to_string()], DndAction::Move, *parent_id, - Some(DndIcon::Custom(id)), // TODO store + Some(DndIcon::Custom(id)), Box::new(WorkspaceDndId(String::new())), ); } + DragSurface::Toplevel { handle, .. } => { + let handle = handle.clone(); + self.drag_surface = Some((id, drag_surface, size)); + return start_drag( + vec![TOPLEVEL_MIME.to_string()], + DndAction::Move, + *parent_id, + Some(DndIcon::Custom(id)), + Box::new(WlDndId { + id: handle.id(), + mime_type: &*TOPLEVEL_MIME, + }), + ); + } } } } @@ -500,7 +551,7 @@ impl Application for App { println!("Workspace enter: {:?}", (action, &mimes)); // XXX // if mimes.iter().any(|x| x == WORKSPACE_MIME) && action == DndAction::Move { - if mimes.iter().any(|x| x == WORKSPACE_MIME) { + if mimes.iter().any(|x| x == &*WORKSPACE_MIME) { return Command::batch(vec![ set_actions(DndAction::Move, DndAction::Move), accept_mime_type(Some(WORKSPACE_MIME.to_string())), @@ -576,6 +627,16 @@ impl Application for App { .into(); } } + DragSurface::Toplevel { handle, .. } => { + if let Some(toplevel) = self.toplevels.iter().find(|x| &x.handle == handle) + { + let item = view::toplevel_preview(toplevel); + return widget::container(item) + .height(iced::Length::Fixed(size.height)) + .width(iced::Length::Fixed(size.width)) + .into(); + } + } } } } diff --git a/src/view/mod.rs b/src/view/mod.rs index c18fec6..084976f 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -27,16 +27,19 @@ pub(crate) fn layer_surface<'a>( layout, app.conf.workspace_config.workspace_amount, ); - let toplevels = toplevel_previews(app.toplevels.iter().filter(|i| { - if !i.info.output.contains(&surface.output) { - return false; - } + let toplevels = toplevel_previews( + app.toplevels.iter().filter(|i| { + if !i.info.output.contains(&surface.output) { + return false; + } - i.info.workspace.iter().any(|workspace| { - app.workspace_for_handle(workspace) - .map_or(false, |x| x.is_active) - }) - })); + i.info.workspace.iter().any(|workspace| { + app.workspace_for_handle(workspace) + .map_or(false, |x| x.is_active) + }) + }), + &surface.output, + ); match layout { WorkspaceLayout::Vertical => widget::cosmic_container::container( row![sidebar, toplevels] @@ -126,7 +129,7 @@ fn workspaces_sidebar<'a>( .into() } -fn toplevel_preview(toplevel: &Toplevel) -> cosmic::Element { +pub(crate) fn toplevel_preview(toplevel: &Toplevel) -> cosmic::Element { column![ close_button(Msg::CloseToplevel(toplevel.handle.clone())), widget::button(capture_image(toplevel.img.as_ref())) @@ -145,15 +148,35 @@ fn toplevel_preview(toplevel: &Toplevel) -> cosmic::Element { .into() } +fn toplevel_previews_entry<'a>( + toplevel: &'a Toplevel, + output: &'a wl_output::WlOutput, +) -> cosmic::Element<'a, Msg> { + iced::widget::dnd_source(toplevel_preview(toplevel)) + .on_drag(|size| { + Msg::StartDrag( + size, + DragSurface::Toplevel { + handle: toplevel.handle.clone(), + output: output.clone(), + }, + ) + }) + .into() +} + fn toplevel_previews<'a>( toplevels: impl Iterator, + output: &'a wl_output::WlOutput, ) -> cosmic::Element<'a, Msg> { - row(toplevels.map(toplevel_preview).collect()) - .width(iced::Length::FillPortion(4)) - .height(iced::Length::Fill) - .spacing(16) - .align_items(iced::Alignment::Center) - .into() + row(toplevels + .map(|t| toplevel_previews_entry(t, output)) + .collect()) + .width(iced::Length::FillPortion(4)) + .height(iced::Length::Fill) + .spacing(16) + .align_items(iced::Alignment::Center) + .into() } fn capture_image(image: Option<&CaptureImage>) -> cosmic::Element<'_, Msg> {