1
0
Fork 0
forked from wry/wry

wayland: implement xdg-toplevel-drag

This commit is contained in:
Julian Orth 2024-03-02 04:57:20 +01:00
parent e665a18242
commit 364872258a
21 changed files with 535 additions and 70 deletions

View file

@ -254,6 +254,17 @@ impl dyn GfxFramebuffer {
let seats = state.globals.lock_seats();
for seat in seats.values() {
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(),

View file

@ -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<dyn Backend>) {

View file

@ -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;

View file

@ -56,7 +56,7 @@ pub trait IpcVtable: Sized {
data: OfferData<Self>,
) -> Result<Rc<Self::Offer>, ClientError>;
fn send_selection(dd: &Self::Device, offer: Option<&Rc<Self::Offer>>);
fn send_cancelled(source: &Rc<Self::Source>);
fn send_cancelled(source: &Rc<Self::Source>, seat: &Rc<WlSeatGlobal>);
fn get_offer_id(offer: &Self::Offer) -> u64;
fn send_offer(dd: &Self::Device, offer: &Rc<Self::Offer>);
fn send_mime_type(offer: &Rc<Self::Offer>, 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<T: IpcVtable> {
pub seat: CloneCell<Option<Rc<WlSeatGlobal>>>,
offers: SmallMap<u64, Rc<T::Offer>, 1>,
offer_client: Cell<ClientId>,
mime_types: RefCell<AHashSet<String>>,
client: Rc<Client>,
pub client: Rc<Client>,
state: NumCell<u32>,
actions: Cell<Option<u32>>,
role: Cell<Role>,
@ -151,6 +154,16 @@ impl<T: IpcVtable> SourceData<T> {
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<T: IpcVtable>(
@ -188,12 +201,12 @@ pub fn cancel_offers<T: IpcVtable>(src: &T::Source) {
}
}
pub fn detach_seat<T: IpcVtable>(src: &Rc<T::Source>) {
pub fn detach_seat<T: IpcVtable>(src: &Rc<T::Source>, seat: &Rc<WlSeatGlobal>) {
let data = T::get_source_data(src);
data.seat.set(None);
cancel_offers::<T>(src);
if !data.state.get().contains(SOURCE_STATE_FINISHED) {
T::send_cancelled(src);
T::send_cancelled(src, seat);
}
// data.client.flush();
}

View file

@ -246,8 +246,8 @@ impl IpcVtable for ClipboardIpc {
dd.send_selection(offer);
}
fn send_cancelled(source: &Rc<Self::Source>) {
source.send_cancelled();
fn send_cancelled(source: &Rc<Self::Source>, seat: &Rc<WlSeatGlobal>) {
source.send_cancelled(seat);
}
fn get_offer_id(offer: &Self::Offer) -> u64 {

View file

@ -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<ClipboardIpc>,
pub version: u32,
pub tracker: Tracker<Self>,
pub toplevel_drag: CloneCell<Option<Rc<XdgToplevelDragV1>>>,
}
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<WlSeatGlobal>) {
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<Self>) {
pub fn send_cancelled(self: &Rc<Self>, seat: &Rc<WlSeatGlobal>) {
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::<ClipboardIpc>(self);
self.toplevel_drag.take();
}
}

View file

@ -193,7 +193,7 @@ impl IpcVtable for PrimarySelectionIpc {
dd.send_selection(offer);
}
fn send_cancelled(source: &Rc<Self::Source>) {
fn send_cancelled(source: &Rc<Self::Source>, _seat: &Rc<WlSeatGlobal>) {
source.send_cancelled();
}

View file

@ -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::<ClipboardIpc>(&src);
ipc::detach_seat::<ClipboardIpc>(&src, &self.dnd.seat);
}
}
}
@ -232,6 +233,10 @@ impl WlSeatGlobal {
slf
}
pub fn toplevel_drag(&self) -> Option<Rc<XdgToplevelDragV1>> {
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::<T>(new, self, ipc::Role::Selection)?;
}
if let Some(old) = field.set(src.clone()) {
ipc::detach_seat::<T>(&old);
ipc::detach_seat::<T>(&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_::<ClipboardIpc>(&self.selection, selection)
}
@ -1117,6 +1133,8 @@ pub enum WlSeatError {
MsgParserError(#[source] Box<MsgParserError>),
#[error(transparent)]
WlKeyboardError(Box<WlKeyboardError>),
#[error("Data source has a toplevel attached")]
OfferHasDrag,
}
efrom!(WlSeatError, ClientError);
efrom!(WlSeatError, MsgParserError);

View file

@ -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<Rc<XdgToplevelDragV1>> {
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<WlSeatGlobal>);
fn dnd_target_removed(&self, seat: &Rc<WlSeatGlobal>);
fn dnd_icon(&self) -> Option<Rc<WlSurface>>;
fn toplevel_drag(&self) -> Option<Rc<XdgToplevelDragV1>>;
fn remove_dnd_icon(&self);
}
@ -267,14 +273,14 @@ impl PointerOwner for DefaultPointerOwner {
fn start_drag(
&self,
_seat: &Rc<WlSeatGlobal>,
seat: &Rc<WlSeatGlobal>,
_origin: &Rc<WlSurface>,
source: Option<Rc<WlDataSource>>,
_icon: Option<Rc<WlSurface>>,
_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<Rc<XdgToplevelDragV1>> {
None
}
fn remove_dnd_icon(&self) {
// nothing
}
@ -362,6 +372,9 @@ impl PointerOwner for GrabPointerOwner {
}
if let Some(new) = &src {
ipc::attach_seat::<ClipboardIpc>(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<Rc<XdgToplevelDragV1>> {
None
}
fn remove_dnd_icon(&self) {
// nothing
}
@ -422,7 +439,7 @@ impl PointerOwner for DndPointerOwner {
return;
}
if let Some(src) = &self.dnd.src {
src.on_drop();
src.on_drop(seat);
}
let should_drop = match &self.dnd.src {
None => true,
@ -439,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::<ClipboardIpc>(src);
ipc::detach_seat::<ClipboardIpc>(src, seat);
}
}
if let Some(icon) = self.icon.get() {
@ -493,14 +510,14 @@ impl PointerOwner for DndPointerOwner {
fn start_drag(
&self,
_seat: &Rc<WlSeatGlobal>,
seat: &Rc<WlSeatGlobal>,
_origin: &Rc<WlSurface>,
source: Option<Rc<WlDataSource>>,
_icon: Option<Rc<WlSurface>>,
_serial: u32,
) -> Result<(), WlSeatError> {
if let Some(src) = source {
src.send_cancelled();
src.send_cancelled(seat);
}
Ok(())
}
@ -510,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::<ClipboardIpc>(src);
ipc::detach_seat::<ClipboardIpc>(src, seat);
}
if let Some(icon) = self.icon.get() {
icon.dnd_icons.remove(&seat.id());
@ -533,6 +550,14 @@ impl PointerOwner for DndPointerOwner {
self.icon.get()
}
fn toplevel_drag(&self) -> Option<Rc<XdgToplevelDragV1>> {
if let Some(src) = &self.dnd.src {
src.toplevel_drag.get()
} else {
None
}
}
fn remove_dnd_icon(&self) {
self.icon.set(None);
}

View file

@ -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();

View file

@ -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 => {

View file

@ -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<OutputNode>) {
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<Rect> {
self.geometry.get()
}

View file

@ -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! {

View file

@ -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<Option<i32>>,
pub tracker: Tracker<Self>,
toplevel_data: ToplevelData,
pub drag: CloneCell<Option<Rc<XdgToplevelDragV1>>>,
is_mapped: Cell<bool>,
}
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<Self>, workspace: &Rc<WorkspaceNode>) {
fn map_floating(self: &Rc<Self>, workspace: &Rc<WorkspaceNode>, 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<Self>, parent: &XdgToplevel) {
fn map_child(self: &Rc<Self>, parent: &XdgToplevel, pos: Option<(&Rc<OutputNode>, 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>) {
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<Self>, output: &Rc<OutputNode>, 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<Self>, pos: Option<(&Rc<OutputNode>, 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<Self>) {
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) {

View file

@ -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<Self>,
id: XdgToplevelDragManagerV1Id,
client: &Rc<Client>,
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<Client>,
pub tracker: Tracker<Self>,
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<ClientError>),
#[error("Parsing failed")]
MsgParserError(#[source] Box<MsgParserError>),
#[error("The data source has already been used")]
AlreadyUsed,
#[error("The source already has a drag object")]
HasDrag,
}
efrom!(XdgToplevelDragManagerV1Error, ClientError);
efrom!(XdgToplevelDragManagerV1Error, MsgParserError);

View file

@ -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<Client>,
pub source: Rc<WlDataSource>,
pub tracker: Tracker<Self>,
pub toplevel: CloneCell<Option<Rc<XdgToplevel>>>,
pub x_off: Cell<i32>,
pub y_off: Cell<i32>,
}
impl XdgToplevelDragV1 {
pub fn new(id: XdgToplevelDragV1Id, source: &Rc<WlDataSource>) -> 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<Self>, 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<Self>) {
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<WlSeatGlobal>) {
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<ClientError>),
#[error("Parsing failed")]
MsgParserError(#[source] Box<MsgParserError>),
#[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);

View file

@ -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);
}

View file

@ -527,12 +527,23 @@ impl State {
mut width: i32,
mut height: i32,
workspace: &Rc<WorkspaceNode>,
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() {

View file

@ -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();
}

View file

@ -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),
}

View file

@ -0,0 +1,11 @@
# requests
msg destroy = 0 {
}
msg attach = 1 {
toplevel: id(xdg_toplevel),
x_offset: i32,
y_offset: i32,
}