diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 0672a0dd..8d2716c7 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -253,18 +253,29 @@ impl dyn GfxFramebuffer { if let Some(rect) = cursor_rect { let seats = state.globals.lock_seats(); for seat in seats.values() { - if let Some(cursor) = seat.get_cursor() { - let (mut x, mut y) = seat.get_position(); - if let Some(dnd_icon) = seat.dnd_icon() { - let extents = dnd_icon.extents.get().move_( - x.round_down() + dnd_icon.buf_x.get(), - y.round_down() + dnd_icon.buf_y.get(), - ); - if extents.intersects(&rect) { - let (x, y) = rect.translate(extents.x1(), extents.y1()); - renderer.render_surface(&dnd_icon, x, y, None); + let (mut x, mut y) = seat.get_position(); + if let Some(drag) = seat.toplevel_drag() { + if let Some(tl) = drag.toplevel.get() { + if tl.xdg.surface.buffer.get().is_some() { + let (x, y) = rect.translate( + x.round_down() - drag.x_off.get(), + y.round_down() - drag.y_off.get(), + ); + renderer.render_xdg_surface(&tl.xdg, x, y, None) } } + } + if let Some(dnd_icon) = seat.dnd_icon() { + let extents = dnd_icon.extents.get().move_( + x.round_down() + dnd_icon.buf_x.get(), + y.round_down() + dnd_icon.buf_y.get(), + ); + if extents.intersects(&rect) { + let (x, y) = rect.translate(extents.x1(), extents.y1()); + renderer.render_surface(&dnd_icon, x, y, None); + } + } + if let Some(cursor) = seat.get_cursor() { if render_hardware_cursor || !seat.hardware_cursor() { cursor.tick(); x -= Fixed::from_int(rect.x1()); diff --git a/src/globals.rs b/src/globals.rs index 555e4f65..dcc01c2f 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -30,6 +30,7 @@ use { wp_tearing_control_manager_v1::WpTearingControlManagerV1Global, wp_viewporter::WpViewporterGlobal, xdg_activation_v1::XdgActivationV1Global, + xdg_toplevel_drag_manager_v1::XdgToplevelDragManagerV1Global, xdg_wm_base::XdgWmBaseGlobal, zwlr_layer_shell_v1::ZwlrLayerShellV1Global, zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1Global, @@ -169,6 +170,7 @@ impl Globals { add_singleton!(ExtForeignToplevelListV1Global); add_singleton!(ZwpIdleInhibitManagerV1Global); add_singleton!(ExtIdleNotifierV1Global); + add_singleton!(XdgToplevelDragManagerV1Global); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs.rs b/src/ifs.rs index 24fdfd89..6039d639 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -44,6 +44,8 @@ pub mod wp_viewporter; pub mod xdg_activation_token_v1; pub mod xdg_activation_v1; pub mod xdg_positioner; +pub mod xdg_toplevel_drag_manager_v1; +pub mod xdg_toplevel_drag_v1; pub mod xdg_wm_base; pub mod zwlr_layer_shell_v1; pub mod zwlr_screencopy_frame_v1; diff --git a/src/ifs/ipc.rs b/src/ifs/ipc.rs index b0ddd7b1..36993c9c 100644 --- a/src/ifs/ipc.rs +++ b/src/ifs/ipc.rs @@ -56,7 +56,7 @@ pub trait IpcVtable: Sized { data: OfferData, ) -> Result, ClientError>; fn send_selection(dd: &Self::Device, offer: Option<&Rc>); - fn send_cancelled(source: &Rc); + fn send_cancelled(source: &Rc, seat: &Rc); fn get_offer_id(offer: &Self::Offer) -> u64; fn send_offer(dd: &Self::Device, offer: &Rc); fn send_mime_type(offer: &Rc, mime_type: &str); @@ -102,13 +102,16 @@ const OFFER_STATE_DROPPED: u32 = 1 << 2; const SOURCE_STATE_USED: u32 = 1 << 1; const SOURCE_STATE_FINISHED: u32 = 1 << 2; +const SOURCE_STATE_DROPPED: u32 = 1 << 3; +const SOURCE_STATE_CANCELLED: u32 = 1 << 4; +const SOURCE_STATE_DROPPED_OR_CANCELLED: u32 = SOURCE_STATE_DROPPED | SOURCE_STATE_CANCELLED; pub struct SourceData { pub seat: CloneCell>>, offers: SmallMap, 1>, offer_client: Cell, mime_types: RefCell>, - client: Rc, + pub client: Rc, state: NumCell, actions: Cell>, role: Cell, @@ -151,6 +154,16 @@ impl SourceData { is_xwm, } } + + pub fn was_used(&self) -> bool { + self.state.get().contains(SOURCE_STATE_USED) + } + + pub fn was_dropped_or_cancelled(&self) -> bool { + self.state + .get() + .intersects(SOURCE_STATE_DROPPED_OR_CANCELLED) + } } pub fn attach_seat( @@ -188,12 +201,12 @@ pub fn cancel_offers(src: &T::Source) { } } -pub fn detach_seat(src: &Rc) { +pub fn detach_seat(src: &Rc, seat: &Rc) { let data = T::get_source_data(src); data.seat.set(None); cancel_offers::(src); if !data.state.get().contains(SOURCE_STATE_FINISHED) { - T::send_cancelled(src); + T::send_cancelled(src, seat); } // data.client.flush(); } diff --git a/src/ifs/ipc/wl_data_device.rs b/src/ifs/ipc/wl_data_device.rs index 6a8b0b08..820209b2 100644 --- a/src/ifs/ipc/wl_data_device.rs +++ b/src/ifs/ipc/wl_data_device.rs @@ -246,8 +246,8 @@ impl IpcVtable for ClipboardIpc { dd.send_selection(offer); } - fn send_cancelled(source: &Rc) { - source.send_cancelled(); + fn send_cancelled(source: &Rc, seat: &Rc) { + source.send_cancelled(seat); } fn get_offer_id(offer: &Self::Offer) -> u64 { diff --git a/src/ifs/ipc/wl_data_source.rs b/src/ifs/ipc/wl_data_source.rs index 1e1d62c7..ff6d9d6b 100644 --- a/src/ifs/ipc/wl_data_source.rs +++ b/src/ifs/ipc/wl_data_source.rs @@ -1,12 +1,17 @@ use { crate::{ client::{Client, ClientError}, - ifs::ipc::{ - add_data_source_mime_type, break_source_loops, cancel_offers, destroy_data_source, - wl_data_device::ClipboardIpc, - wl_data_device_manager::{DND_ALL, DND_NONE}, - wl_data_offer::WlDataOffer, - SharedState, SourceData, OFFER_STATE_ACCEPTED, OFFER_STATE_DROPPED, + ifs::{ + ipc::{ + add_data_source_mime_type, break_source_loops, cancel_offers, destroy_data_source, + wl_data_device::ClipboardIpc, + wl_data_device_manager::{DND_ALL, DND_NONE}, + wl_data_offer::WlDataOffer, + SharedState, SourceData, OFFER_STATE_ACCEPTED, OFFER_STATE_DROPPED, + SOURCE_STATE_CANCELLED, SOURCE_STATE_DROPPED, + }, + wl_seat::WlSeatGlobal, + xdg_toplevel_drag_v1::XdgToplevelDragV1, }, leaks::Tracker, object::Object, @@ -14,6 +19,7 @@ use { bitflags::BitflagsExt, buffd::{MsgParser, MsgParserError}, cell_ext::CellExt, + clonecell::CloneCell, }, wire::{wl_data_source::*, WlDataSourceId}, xwayland::XWaylandEvent, @@ -33,6 +39,7 @@ pub struct WlDataSource { pub data: SourceData, pub version: u32, pub tracker: Tracker, + pub toplevel_drag: CloneCell>>, } impl WlDataSource { @@ -42,6 +49,7 @@ impl WlDataSource { tracker: Default::default(), data: SourceData::new(client, is_xwm), version, + toplevel_drag: Default::default(), } } @@ -100,13 +108,17 @@ impl WlDataSource { shared.selected_action.get() != 0 && shared.state.get().contains(OFFER_STATE_ACCEPTED) } - pub fn on_drop(&self) { + pub fn on_drop(&self, seat: &Rc) { + self.data.state.or_assign(SOURCE_STATE_DROPPED); + if let Some(drag) = self.toplevel_drag.take() { + drag.finish_drag(seat); + } self.send_dnd_drop_performed(); let shared = self.data.shared.get(); shared.state.or_assign(OFFER_STATE_DROPPED); } - pub fn send_cancelled(self: &Rc) { + pub fn send_cancelled(self: &Rc, seat: &Rc) { if self.data.is_xwm { self.data .client @@ -115,6 +127,10 @@ impl WlDataSource { .queue .push(XWaylandEvent::ClipboardCancelSource(self.clone())); } else { + self.data.state.or_assign(SOURCE_STATE_CANCELLED); + if let Some(drag) = self.toplevel_drag.take() { + drag.finish_drag(seat); + } self.data.client.event(Cancelled { self_id: self.id }) } } @@ -209,6 +225,7 @@ object_base! { impl Object for WlDataSource { fn break_loops(&self) { break_source_loops::(self); + self.toplevel_drag.take(); } } diff --git a/src/ifs/ipc/zwp_primary_selection_device_v1.rs b/src/ifs/ipc/zwp_primary_selection_device_v1.rs index f5e4eb2a..9c470925 100644 --- a/src/ifs/ipc/zwp_primary_selection_device_v1.rs +++ b/src/ifs/ipc/zwp_primary_selection_device_v1.rs @@ -193,7 +193,7 @@ impl IpcVtable for PrimarySelectionIpc { dd.send_selection(offer); } - fn send_cancelled(source: &Rc) { + fn send_cancelled(source: &Rc, _seat: &Rc) { source.send_cancelled(); } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index d5e2f1a3..071ee511 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -38,6 +38,7 @@ use { zwp_relative_pointer_v1::ZwpRelativePointerV1, }, wl_surface::WlSurface, + xdg_toplevel_drag_v1::XdgToplevelDragV1, }, leaks::Tracker, object::Object, @@ -107,7 +108,7 @@ pub struct DroppedDnd { impl Drop for DroppedDnd { fn drop(&mut self) { if let Some(src) = self.dnd.src.take() { - ipc::detach_seat::(&src); + ipc::detach_seat::(&src, &self.dnd.seat); } } } @@ -232,6 +233,10 @@ impl WlSeatGlobal { slf } + pub fn toplevel_drag(&self) -> Option> { + self.pointer_owner.toplevel_drag() + } + pub fn set_hardware_cursor(&self, hardware_cursor: bool) { self.hardware_cursor.set(hardware_cursor); } @@ -397,6 +402,7 @@ impl WlSeatGlobal { tl.tl_data().float_width.get(), tl.tl_data().float_height.get(), ws, + None, ); } else { self.state.map_tiled_on(tl, ws); @@ -519,6 +525,11 @@ impl WlSeatGlobal { if let Some(dnd) = self.pointer_owner.dnd_icon() { dnd.set_output(output); } + if let Some(drag) = self.pointer_owner.toplevel_drag() { + if let Some(tl) = drag.toplevel.get() { + tl.xdg.set_output(output); + } + } } pub fn position(&self) -> (Fixed, Fixed) { @@ -630,7 +641,7 @@ impl WlSeatGlobal { } else if let Some(ws) = data.workspace.get() { cn.cnode_remove_child2(tl.tl_as_node(), true); let (width, height) = data.float_size(&ws); - self.state.map_floating(tl, width, height, &ws); + self.state.map_floating(tl, width, height, &ws, None); } } } @@ -700,7 +711,7 @@ impl WlSeatGlobal { ipc::attach_seat::(new, self, ipc::Role::Selection)?; } if let Some(old) = field.set(src.clone()) { - ipc::detach_seat::(&old); + ipc::detach_seat::(&old, self); } if let Some(client) = self.keyboard_node.get().node_client() { match src { @@ -744,6 +755,11 @@ impl WlSeatGlobal { if let Some(serial) = serial { self.selection_serial.set(serial); } + if let Some(selection) = &selection { + if selection.toplevel_drag.is_some() { + return Err(WlSeatError::OfferHasDrag); + } + } self.set_selection_::(&self.selection, selection) } @@ -1117,6 +1133,8 @@ pub enum WlSeatError { MsgParserError(#[source] Box), #[error(transparent)] WlKeyboardError(Box), + #[error("Data source has a toplevel attached")] + OfferHasDrag, } efrom!(WlSeatError, ClientError); efrom!(WlSeatError, MsgParserError); diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 883db383..8ba365c4 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -808,9 +808,6 @@ impl WlSeatGlobal { dd.send_drop(); }) } - if let Some(src) = &dnd.src { - src.on_drop(); - } // surface.client.flush(); } diff --git a/src/ifs/wl_seat/pointer_owner.rs b/src/ifs/wl_seat/pointer_owner.rs index 3b796fe9..d89c0668 100644 --- a/src/ifs/wl_seat/pointer_owner.rs +++ b/src/ifs/wl_seat/pointer_owner.rs @@ -10,6 +10,7 @@ use { CHANGE_CURSOR_MOVED, }, wl_surface::WlSurface, + xdg_toplevel_drag_v1::XdgToplevelDragV1, }, state::DeviceHandlerData, tree::{FoundNode, Node}, @@ -128,6 +129,10 @@ impl PointerOwnerHolder { self.owner.get().dnd_icon() } + pub fn toplevel_drag(&self) -> Option> { + self.owner.get().toplevel_drag() + } + pub fn remove_dnd_icon(&self) { self.owner.get().remove_dnd_icon() } @@ -158,6 +163,7 @@ trait PointerOwner { fn revert_to_default(&self, seat: &Rc); fn dnd_target_removed(&self, seat: &Rc); fn dnd_icon(&self) -> Option>; + fn toplevel_drag(&self) -> Option>; fn remove_dnd_icon(&self); } @@ -267,14 +273,14 @@ impl PointerOwner for DefaultPointerOwner { fn start_drag( &self, - _seat: &Rc, + seat: &Rc, _origin: &Rc, source: Option>, _icon: Option>, _serial: u32, ) -> Result<(), WlSeatError> { if let Some(src) = source { - src.send_cancelled(); + src.send_cancelled(seat); } Ok(()) } @@ -295,6 +301,10 @@ impl PointerOwner for DefaultPointerOwner { None } + fn toplevel_drag(&self) -> Option> { + None + } + fn remove_dnd_icon(&self) { // nothing } @@ -362,6 +372,9 @@ impl PointerOwner for GrabPointerOwner { } if let Some(new) = &src { ipc::attach_seat::(new, seat, ipc::Role::Dnd)?; + if let Some(drag) = new.toplevel_drag.get() { + drag.start_drag(); + } } *seat.dropped_dnd.borrow_mut() = None; let pointer_owner = Rc::new(DndPointerOwner { @@ -411,6 +424,10 @@ impl PointerOwner for GrabPointerOwner { None } + fn toplevel_drag(&self) -> Option> { + None + } + fn remove_dnd_icon(&self) { // nothing } @@ -421,6 +438,9 @@ impl PointerOwner for DndPointerOwner { if button != self.button || state != KeyState::Released { return; } + if let Some(src) = &self.dnd.src { + src.on_drop(seat); + } let should_drop = match &self.dnd.src { None => true, Some(s) => s.can_drop(), @@ -436,7 +456,7 @@ impl PointerOwner for DndPointerOwner { target.node_seat_state().remove_dnd_target(seat); if !should_drop { if let Some(src) = &self.dnd.src { - ipc::detach_seat::(src); + ipc::detach_seat::(src, seat); } } if let Some(icon) = self.icon.get() { @@ -490,14 +510,14 @@ impl PointerOwner for DndPointerOwner { fn start_drag( &self, - _seat: &Rc, + seat: &Rc, _origin: &Rc, source: Option>, _icon: Option>, _serial: u32, ) -> Result<(), WlSeatError> { if let Some(src) = source { - src.send_cancelled(); + src.send_cancelled(seat); } Ok(()) } @@ -507,7 +527,7 @@ impl PointerOwner for DndPointerOwner { target.node_on_dnd_leave(&self.dnd); target.node_seat_state().remove_dnd_target(seat); if let Some(src) = &self.dnd.src { - ipc::detach_seat::(src); + ipc::detach_seat::(src, seat); } if let Some(icon) = self.icon.get() { icon.dnd_icons.remove(&seat.id()); @@ -530,6 +550,14 @@ impl PointerOwner for DndPointerOwner { self.icon.get() } + fn toplevel_drag(&self) -> Option> { + if let Some(src) = &self.dnd.src { + src.toplevel_drag.get() + } else { + None + } + } + fn remove_dnd_icon(&self) { self.icon.set(None); } diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 59d1476d..fdc14f55 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -972,6 +972,10 @@ impl WlSurface { self.seat_state.set_visible(self, visible); } + pub fn detach_node(&self) { + self.destroy_node(); + } + pub fn destroy_node(&self) { for (_, constraint) in &self.constraints { constraint.deactivate(); diff --git a/src/ifs/wl_surface/x_surface/xwindow.rs b/src/ifs/wl_surface/x_surface/xwindow.rs index d75cef29..bbee02aa 100644 --- a/src/ifs/wl_surface/x_surface/xwindow.rs +++ b/src/ifs/wl_surface/x_surface/xwindow.rs @@ -273,7 +273,7 @@ impl Xwindow { let ext = self.data.info.pending_extents.get(); self.data .state - .map_floating(self.clone(), ext.width(), ext.height(), &ws); + .map_floating(self.clone(), ext.width(), ext.height(), &ws, None); self.data.title_changed(); } Change::Map => { diff --git a/src/ifs/wl_surface/xdg_surface.rs b/src/ifs/wl_surface/xdg_surface.rs index aec5900a..51343c00 100644 --- a/src/ifs/wl_surface/xdg_surface.rs +++ b/src/ifs/wl_surface/xdg_surface.rs @@ -17,7 +17,7 @@ use { leaks::Tracker, object::Object, rect::Rect, - tree::{FindTreeResult, FoundNode, WorkspaceNode}, + tree::{FindTreeResult, FoundNode, OutputNode, WorkspaceNode}, utils::{ buffd::{MsgParser, MsgParserError}, clonecell::CloneCell, @@ -131,6 +131,14 @@ impl XdgSurface { } } + pub fn set_output(&self, output: &Rc) { + self.surface.set_output(output); + let pu = self.popups.lock(); + for pu in pu.values() { + pu.xdg.set_output(output); + } + } + fn set_role(&self, role: XdgSurfaceRole) -> Result<(), XdgSurfaceError> { use XdgSurfaceRole::*; match (self.role.get(), role) { @@ -157,6 +165,15 @@ impl XdgSurface { } } + fn detach_node(&self) { + self.workspace.set(None); + self.surface.detach_node(); + let popups = self.popups.lock(); + for popup in popups.values() { + popup.detach_node(); + } + } + pub fn geometry(&self) -> Option { self.geometry.get() } diff --git a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs index 749e48be..ecc64343 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs @@ -253,6 +253,12 @@ impl XdgPopup { self.xdg.destroy_node(); self.seat_state.destroy_node(self); } + + pub fn detach_node(&self) { + let _v = self.workspace_link.borrow_mut().take(); + self.xdg.detach_node(); + self.seat_state.destroy_node(self); + } } object_base! { diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index 6a026cb5..52beb17c 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -12,6 +12,7 @@ use { xdg_surface::{XdgSurface, XdgSurfaceError, XdgSurfaceExt}, WlSurface, }, + xdg_toplevel_drag_v1::XdgToplevelDragV1, }, leaks::Tracker, object::Object, @@ -19,8 +20,8 @@ use { renderer::Renderer, state::State, tree::{ - Direction, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, ToplevelData, - ToplevelNode, ToplevelNodeBase, ToplevelNodeId, WorkspaceNode, + Direction, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, OutputNode, + ToplevelData, ToplevelNode, ToplevelNodeBase, ToplevelNodeId, WorkspaceNode, }, utils::{ buffd::{MsgParser, MsgParserError}, @@ -99,6 +100,8 @@ pub struct XdgToplevel { max_height: Cell>, pub tracker: Tracker, toplevel_data: ToplevelData, + pub drag: CloneCell>>, + is_mapped: Cell, } impl Debug for XdgToplevel { @@ -135,6 +138,8 @@ impl XdgToplevel { String::new(), Some(surface.surface.client.clone()), ), + drag: Default::default(), + is_mapped: Cell::new(false), } } @@ -351,15 +356,20 @@ impl XdgToplevel { Ok(()) } - fn map_floating(self: &Rc, workspace: &Rc) { + fn map_floating(self: &Rc, workspace: &Rc, abs_pos: Option<(i32, i32)>) { let (width, height) = self.toplevel_data.float_size(workspace); self.state - .map_floating(self.clone(), width, height, workspace); + .map_floating(self.clone(), width, height, workspace, abs_pos); } - fn map_child(self: &Rc, parent: &XdgToplevel) { + fn map_child(self: &Rc, parent: &XdgToplevel, pos: Option<(&Rc, i32, i32)>) { + if let Some((output, x, y)) = pos { + let w = output.ensure_workspace(); + self.map_floating(&w, Some((x, y))); + return; + } match parent.xdg.workspace.get() { - Some(w) => self.map_floating(&w), + Some(w) => self.map_floating(&w, None), _ => self.map_tiled(), } } @@ -367,6 +377,80 @@ impl XdgToplevel { fn map_tiled(self: &Rc) { self.state.map_tiled(self.clone()); } + + pub fn prepare_toplevel_drag(&self) { + if self.toplevel_data.parent.get().is_none() { + return; + } + self.toplevel_data.detach_node(self); + self.xdg.detach_node(); + } + + pub fn after_toplevel_drag(self: &Rc, output: &Rc, x: i32, y: i32) { + assert!(self.toplevel_data.parent.is_none()); + let extents = match self.xdg.geometry.get() { + None => self.xdg.extents.get(), + Some(g) => g, + }; + self.toplevel_data.float_width.set(extents.width()); + self.toplevel_data.float_height.set(extents.height()); + self.clone().after_commit(Some((output, x, y))); + } + + fn after_commit(self: &Rc, pos: Option<(&Rc, i32, i32)>) { + if pos.is_some() { + self.is_mapped.set(false); + } + let surface = &self.xdg.surface; + let should_be_mapped = surface.buffer.is_some(); + if let Some(drag) = self.drag.get() { + if drag.is_ongoing() { + if should_be_mapped { + if !self.is_mapped.replace(true) { + if let Some(seat) = drag.source.data.seat.get() { + self.xdg.set_output(&seat.get_output()); + } + self.toplevel_data.broadcast(self.clone()); + } + self.extents_changed(); + } + return; + } + } + if self.is_mapped.replace(should_be_mapped) == should_be_mapped { + return; + } + if !should_be_mapped { + self.tl_destroy(); + { + let new_parent = self.parent.get(); + let mut children = self.children.borrow_mut(); + for (_, child) in children.drain() { + child.parent.set(new_parent.clone()); + } + } + self.state.tree_changed(); + } else { + if let Some(parent) = self.parent.get() { + self.map_child(&parent, pos); + } else { + self.map_tiled(); + } + self.extents_changed(); + if let Some(workspace) = self.xdg.workspace.get() { + let output = workspace.output.get(); + surface.set_output(&output); + } + // { + // let seats = surface.client.state.globals.lock_seats(); + // for seat in seats.values() { + // seat.focus_toplevel(self.clone()); + // } + // } + self.state.tree_changed(); + self.toplevel_data.broadcast(self.clone()); + } + } } object_base! { @@ -514,6 +598,9 @@ impl ToplevelNodeBase for XdgToplevel { } fn tl_destroy_impl(&self) { + if let Some(drag) = self.drag.take() { + drag.toplevel.take(); + } self.xdg.destroy_node(); } @@ -556,39 +643,7 @@ impl XdgSurfaceExt for XdgToplevel { } fn post_commit(self: Rc) { - let surface = &self.xdg.surface; - if self.toplevel_data.parent.is_some() { - if surface.buffer.is_none() { - self.tl_destroy(); - { - let new_parent = self.parent.get(); - let mut children = self.children.borrow_mut(); - for (_, child) in children.drain() { - child.parent.set(new_parent.clone()); - } - } - self.state.tree_changed(); - } - } else if surface.buffer.is_some() { - if let Some(parent) = self.parent.get() { - self.map_child(&parent); - } else { - self.map_tiled(); - } - self.extents_changed(); - if let Some(workspace) = self.xdg.workspace.get() { - let output = workspace.output.get(); - surface.set_output(&output); - } - // { - // let seats = surface.client.state.globals.lock_seats(); - // for seat in seats.values() { - // seat.focus_toplevel(self.clone()); - // } - // } - self.state.tree_changed(); - self.toplevel_data.broadcast(self.clone()); - } + self.after_commit(None); } fn extents_changed(&self) { diff --git a/src/ifs/xdg_toplevel_drag_manager_v1.rs b/src/ifs/xdg_toplevel_drag_manager_v1.rs new file mode 100644 index 00000000..7ffb5b0d --- /dev/null +++ b/src/ifs/xdg_toplevel_drag_manager_v1.rs @@ -0,0 +1,117 @@ +use { + crate::{ + client::{Client, ClientError}, + globals::{Global, GlobalName}, + ifs::xdg_toplevel_drag_v1::XdgToplevelDragV1, + leaks::Tracker, + object::Object, + utils::buffd::{MsgParser, MsgParserError}, + wire::{xdg_toplevel_drag_manager_v1::*, XdgToplevelDragManagerV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct XdgToplevelDragManagerV1Global { + pub name: GlobalName, +} + +impl XdgToplevelDragManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: XdgToplevelDragManagerV1Id, + client: &Rc, + version: u32, + ) -> Result<(), XdgToplevelDragManagerV1Error> { + let mgr = Rc::new(XdgToplevelDragManagerV1 { + id, + client: client.clone(), + tracker: Default::default(), + version, + }); + track!(client, mgr); + client.add_client_obj(&mgr)?; + Ok(()) + } +} + +global_base!( + XdgToplevelDragManagerV1Global, + XdgToplevelDragManagerV1, + XdgToplevelDragManagerV1Error +); + +simple_add_global!(XdgToplevelDragManagerV1Global); + +impl Global for XdgToplevelDragManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } +} + +pub struct XdgToplevelDragManagerV1 { + pub id: XdgToplevelDragManagerV1Id, + pub client: Rc, + pub tracker: Tracker, + pub version: u32, +} + +impl XdgToplevelDragManagerV1 { + fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), XdgToplevelDragManagerV1Error> { + let _req: Destroy = self.client.parse(self, parser)?; + self.client.remove_obj(self)?; + Ok(()) + } + + fn get_xdg_toplevel_drag( + &self, + parser: MsgParser<'_, '_>, + ) -> Result<(), XdgToplevelDragManagerV1Error> { + let req: GetXdgToplevelDrag = self.client.parse(self, parser)?; + let source = self.client.lookup(req.data_source)?; + if source.data.was_used() { + return Err(XdgToplevelDragManagerV1Error::AlreadyUsed); + } + if source.toplevel_drag.get().is_some() { + return Err(XdgToplevelDragManagerV1Error::HasDrag); + } + let drag = Rc::new(XdgToplevelDragV1::new(req.id, &source)); + track!(&self.client, drag); + self.client.add_client_obj(&drag)?; + source.toplevel_drag.set(Some(drag)); + Ok(()) + } +} + +object_base! { + self = XdgToplevelDragManagerV1; + + DESTROY => destroy, + GET_XDG_TOPLEVEL_DRAG => get_xdg_toplevel_drag, +} + +impl Object for XdgToplevelDragManagerV1 {} + +simple_add_obj!(XdgToplevelDragManagerV1); + +#[derive(Debug, Error)] +pub enum XdgToplevelDragManagerV1Error { + #[error(transparent)] + ClientError(Box), + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error("The data source has already been used")] + AlreadyUsed, + #[error("The source already has a drag object")] + HasDrag, +} +efrom!(XdgToplevelDragManagerV1Error, ClientError); +efrom!(XdgToplevelDragManagerV1Error, MsgParserError); diff --git a/src/ifs/xdg_toplevel_drag_v1.rs b/src/ifs/xdg_toplevel_drag_v1.rs new file mode 100644 index 00000000..083c51fd --- /dev/null +++ b/src/ifs/xdg_toplevel_drag_v1.rs @@ -0,0 +1,144 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::{ + ipc::wl_data_source::WlDataSource, wl_seat::WlSeatGlobal, + wl_surface::xdg_surface::xdg_toplevel::XdgToplevel, + }, + leaks::Tracker, + object::Object, + utils::{ + buffd::{MsgParser, MsgParserError}, + clonecell::CloneCell, + }, + wire::{xdg_toplevel_drag_v1::*, XdgToplevelDragV1Id}, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +pub struct XdgToplevelDragV1 { + pub id: XdgToplevelDragV1Id, + pub client: Rc, + pub source: Rc, + pub tracker: Tracker, + pub toplevel: CloneCell>>, + pub x_off: Cell, + pub y_off: Cell, +} + +impl XdgToplevelDragV1 { + pub fn new(id: XdgToplevelDragV1Id, source: &Rc) -> Self { + Self { + id, + client: source.data.client.clone(), + source: source.clone(), + tracker: Default::default(), + toplevel: Default::default(), + x_off: Cell::new(0), + y_off: Cell::new(0), + } + } + + pub fn is_ongoing(&self) -> bool { + self.source.data.was_used() && !self.source.data.was_dropped_or_cancelled() + } + + fn detach(&self) { + self.source.toplevel_drag.take(); + if let Some(tl) = self.toplevel.take() { + tl.drag.take(); + } + } + + fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), XdgToplevelDragV1Error> { + let _req: Destroy = self.client.parse(self, parser)?; + if self.is_ongoing() { + return Err(XdgToplevelDragV1Error::ActiveDrag); + } + self.detach(); + self.client.remove_obj(self)?; + Ok(()) + } + + fn attach(self: &Rc, parser: MsgParser<'_, '_>) -> Result<(), XdgToplevelDragV1Error> { + let req: Attach = self.client.parse(&**self, parser)?; + if self.source.data.was_dropped_or_cancelled() { + return Ok(()); + } + let toplevel = self.client.lookup(req.toplevel)?; + if toplevel.drag.set(Some(self.clone())).is_some() { + return Err(XdgToplevelDragV1Error::AlreadyDragged); + } + if let Some(prev) = self.toplevel.set(Some(toplevel)) { + if prev.xdg.surface.buffer.get().is_some() { + return Err(XdgToplevelDragV1Error::ToplevelAttached); + } + if prev.id != req.toplevel { + prev.drag.set(None); + } + } + self.x_off.set(req.x_offset); + self.y_off.set(req.y_offset); + self.start_drag(); + Ok(()) + } + + pub fn start_drag(self: &Rc) { + if !self.is_ongoing() { + return; + } + let Some(tl) = self.toplevel.get() else { + return; + }; + tl.prepare_toplevel_drag(); + self.client.state.tree_changed(); + } + + pub fn finish_drag(&self, seat: &Rc) { + if self.source.data.was_used() { + if let Some(tl) = self.toplevel.get() { + let output = seat.get_output(); + let (x, y) = seat.position(); + tl.drag.take(); + tl.after_toplevel_drag( + &output, + x.round_down() - self.x_off.get(), + y.round_down() - self.y_off.get(), + ); + } + } + self.detach(); + } +} + +object_base! { + self = XdgToplevelDragV1; + + DESTROY => destroy, + ATTACH => attach, +} + +impl Object for XdgToplevelDragV1 { + fn break_loops(&self) { + self.detach(); + } +} + +simple_add_obj!(XdgToplevelDragV1); + +#[derive(Debug, Error)] +pub enum XdgToplevelDragV1Error { + #[error(transparent)] + ClientError(Box), + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error("The toplevel already has a drag attached")] + AlreadyDragged, + #[error("There already is a mapped toplevel attached")] + ToplevelAttached, + #[error("The drag is ongoing")] + ActiveDrag, +} +efrom!(XdgToplevelDragV1Error, ClientError); +efrom!(XdgToplevelDragV1Error, MsgParserError); diff --git a/src/renderer.rs b/src/renderer.rs index 07d09b0b..5231c8b9 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -267,9 +267,7 @@ impl Renderer<'_> { ) { let surface = &xdg.surface; if let Some(geo) = xdg.geometry() { - let (xt, yt) = geo.translate(x, y); - x = xt; - y = yt; + (x, y) = geo.translate(x, y); } self.render_surface(surface, x, y, bounds); } diff --git a/src/state.rs b/src/state.rs index bef89f55..6816a991 100644 --- a/src/state.rs +++ b/src/state.rs @@ -527,12 +527,23 @@ impl State { mut width: i32, mut height: i32, workspace: &Rc, + abs_pos: Option<(i32, i32)>, ) { width += 2 * self.theme.sizes.border_width.get(); height += 2 * self.theme.sizes.border_width.get() + self.theme.sizes.title_height.get() + 1; let output = workspace.output.get(); let output_rect = output.global.pos.get(); - let position = { + let position = if let Some((mut x1, mut y1)) = abs_pos { + if y1 <= output_rect.y1() { + y1 = output_rect.y1() + 1; + } + if y1 > output_rect.y2() { + y1 = output_rect.y2(); + } + y1 -= self.theme.sizes.border_width.get() + self.theme.sizes.title_height.get() + 1; + x1 -= self.theme.sizes.border_width.get(); + Rect::new_sized(x1, y1, width, height).unwrap() + } else { let mut x1 = output_rect.x1(); let mut y1 = output_rect.y1(); if width < output_rect.width() { diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index e79b30a0..c4e6da03 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -266,6 +266,10 @@ impl ToplevelData { handle.send_closed(); } } + self.detach_node(node); + } + + pub fn detach_node(&self, node: &dyn Node) { if let Some(fd) = self.fullscrceen_data.borrow_mut().take() { fd.placeholder.tl_destroy(); } diff --git a/wire/xdg_toplevel_drag_manager_v1.txt b/wire/xdg_toplevel_drag_manager_v1.txt new file mode 100644 index 00000000..02b02219 --- /dev/null +++ b/wire/xdg_toplevel_drag_manager_v1.txt @@ -0,0 +1,10 @@ +# requests + +msg destroy = 0 { + +} + +msg get_xdg_toplevel_drag = 1 { + id: id(xdg_toplevel_drag_v1), + data_source: id(wl_data_source), +} diff --git a/wire/xdg_toplevel_drag_v1.txt b/wire/xdg_toplevel_drag_v1.txt new file mode 100644 index 00000000..ff325fed --- /dev/null +++ b/wire/xdg_toplevel_drag_v1.txt @@ -0,0 +1,11 @@ +# requests + +msg destroy = 0 { + +} + +msg attach = 1 { + toplevel: id(xdg_toplevel), + x_offset: i32, + y_offset: i32, +}