Merge pull request #142 from mahkoh/jorth/wlr-data-device
wayland: implement wlr-data-control
This commit is contained in:
commit
4558bdb7c1
30 changed files with 2125 additions and 697 deletions
|
|
@ -3,7 +3,7 @@ use {
|
||||||
client::{Client, ClientError},
|
client::{Client, ClientError},
|
||||||
ifs::{
|
ifs::{
|
||||||
ipc::{
|
ipc::{
|
||||||
wl_data_source::WlDataSource,
|
wl_data_source::WlDataSource, zwlr_data_control_source_v1::ZwlrDataControlSourceV1,
|
||||||
zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1,
|
zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1,
|
||||||
},
|
},
|
||||||
jay_output::JayOutput,
|
jay_output::JayOutput,
|
||||||
|
|
@ -32,7 +32,7 @@ use {
|
||||||
JayOutputId, JayScreencastId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId,
|
JayOutputId, JayScreencastId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId,
|
||||||
WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId,
|
WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId,
|
||||||
WpLinuxDrmSyncobjTimelineV1Id, XdgPositionerId, XdgSurfaceId, XdgToplevelId,
|
WpLinuxDrmSyncobjTimelineV1Id, XdgPositionerId, XdgSurfaceId, XdgToplevelId,
|
||||||
XdgWmBaseId, ZwpPrimarySelectionSourceV1Id,
|
XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
std::{cell::RefCell, mem, rc::Rc},
|
std::{cell::RefCell, mem, rc::Rc},
|
||||||
|
|
@ -59,6 +59,7 @@ pub struct Objects {
|
||||||
pub seats: CopyHashMap<WlSeatId, Rc<WlSeat>>,
|
pub seats: CopyHashMap<WlSeatId, Rc<WlSeat>>,
|
||||||
pub screencasts: CopyHashMap<JayScreencastId, Rc<JayScreencast>>,
|
pub screencasts: CopyHashMap<JayScreencastId, Rc<JayScreencast>>,
|
||||||
pub timelines: CopyHashMap<WpLinuxDrmSyncobjTimelineV1Id, Rc<WpLinuxDrmSyncobjTimelineV1>>,
|
pub timelines: CopyHashMap<WpLinuxDrmSyncobjTimelineV1Id, Rc<WpLinuxDrmSyncobjTimelineV1>>,
|
||||||
|
pub zwlr_data_sources: CopyHashMap<ZwlrDataControlSourceV1Id, Rc<ZwlrDataControlSourceV1>>,
|
||||||
ids: RefCell<Vec<usize>>,
|
ids: RefCell<Vec<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,6 +88,7 @@ impl Objects {
|
||||||
seats: Default::default(),
|
seats: Default::default(),
|
||||||
screencasts: Default::default(),
|
screencasts: Default::default(),
|
||||||
timelines: Default::default(),
|
timelines: Default::default(),
|
||||||
|
zwlr_data_sources: Default::default(),
|
||||||
ids: RefCell::new(vec![]),
|
ids: RefCell::new(vec![]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -118,6 +120,8 @@ impl Objects {
|
||||||
self.seats.clear();
|
self.seats.clear();
|
||||||
self.pointers.clear();
|
self.pointers.clear();
|
||||||
self.screencasts.clear();
|
self.screencasts.clear();
|
||||||
|
self.timelines.clear();
|
||||||
|
self.zwlr_data_sources.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id<T>(&self, client_data: &Client) -> Result<T, ClientError>
|
pub fn id<T>(&self, client_data: &Client) -> Result<T, ClientError>
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,7 @@ fn start_compositor2(
|
||||||
enabled: Cell::new(true),
|
enabled: Cell::new(true),
|
||||||
handler: Default::default(),
|
handler: Default::default(),
|
||||||
queue: Default::default(),
|
queue: Default::default(),
|
||||||
|
ipc_device_ids: Default::default(),
|
||||||
},
|
},
|
||||||
acceptor: Default::default(),
|
acceptor: Default::default(),
|
||||||
serial: Default::default(),
|
serial: Default::default(),
|
||||||
|
|
@ -200,6 +201,7 @@ fn start_compositor2(
|
||||||
config_file_id: NumCell::new(1),
|
config_file_id: NumCell::new(1),
|
||||||
tracker: Default::default(),
|
tracker: Default::default(),
|
||||||
data_offer_ids: Default::default(),
|
data_offer_ids: Default::default(),
|
||||||
|
data_source_ids: Default::default(),
|
||||||
drm_dev_ids: Default::default(),
|
drm_dev_ids: Default::default(),
|
||||||
ring: ring.clone(),
|
ring: ring.clone(),
|
||||||
lock: ScreenlockState {
|
lock: ScreenlockState {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use {
|
||||||
ext_session_lock_manager_v1::ExtSessionLockManagerV1Global,
|
ext_session_lock_manager_v1::ExtSessionLockManagerV1Global,
|
||||||
ipc::{
|
ipc::{
|
||||||
wl_data_device_manager::WlDataDeviceManagerGlobal,
|
wl_data_device_manager::WlDataDeviceManagerGlobal,
|
||||||
|
zwlr_data_control_manager_v1::ZwlrDataControlManagerV1Global,
|
||||||
zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1Global,
|
zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1Global,
|
||||||
},
|
},
|
||||||
jay_compositor::JayCompositorGlobal,
|
jay_compositor::JayCompositorGlobal,
|
||||||
|
|
@ -171,6 +172,7 @@ impl Globals {
|
||||||
add_singleton!(ZwpIdleInhibitManagerV1Global);
|
add_singleton!(ZwpIdleInhibitManagerV1Global);
|
||||||
add_singleton!(ExtIdleNotifierV1Global);
|
add_singleton!(ExtIdleNotifierV1Global);
|
||||||
add_singleton!(XdgToplevelDragManagerV1Global);
|
add_singleton!(XdgToplevelDragManagerV1Global);
|
||||||
|
add_singleton!(ZwlrDataControlManagerV1Global);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_backend_singletons(&self, backend: &Rc<dyn Backend>) {
|
pub fn add_backend_singletons(&self, backend: &Rc<dyn Backend>) {
|
||||||
|
|
|
||||||
362
src/ifs/ipc.rs
362
src/ifs/ipc.rs
|
|
@ -1,14 +1,23 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
client::{Client, ClientError, ClientId, WaylandObject},
|
client::{Client, ClientError, ClientId},
|
||||||
ifs::wl_seat::{WlSeatError, WlSeatGlobal},
|
fixed::Fixed,
|
||||||
|
ifs::{
|
||||||
|
ipc::{
|
||||||
|
x_data_device::XIpcDevice, zwlr_data_control_device_v1::ZwlrDataControlDeviceV1,
|
||||||
|
},
|
||||||
|
wl_seat::{WlSeatError, WlSeatGlobal},
|
||||||
|
},
|
||||||
utils::{
|
utils::{
|
||||||
bitflags::BitflagsExt, cell_ext::CellExt, clonecell::CloneCell, numcell::NumCell,
|
bitflags::BitflagsExt, cell_ext::CellExt, clonecell::CloneCell, numcell::NumCell,
|
||||||
smallmap::SmallMap,
|
smallmap::SmallMap,
|
||||||
},
|
},
|
||||||
|
wire::WlSurfaceId,
|
||||||
},
|
},
|
||||||
ahash::AHashSet,
|
ahash::AHashSet,
|
||||||
|
smallvec::SmallVec,
|
||||||
std::{
|
std::{
|
||||||
|
any,
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
|
@ -21,71 +30,166 @@ pub mod wl_data_device;
|
||||||
pub mod wl_data_device_manager;
|
pub mod wl_data_device_manager;
|
||||||
pub mod wl_data_offer;
|
pub mod wl_data_offer;
|
||||||
pub mod wl_data_source;
|
pub mod wl_data_source;
|
||||||
|
pub mod x_data_device;
|
||||||
|
pub mod x_data_offer;
|
||||||
|
pub mod x_data_source;
|
||||||
|
pub mod zwlr_data_control_device_v1;
|
||||||
|
pub mod zwlr_data_control_manager_v1;
|
||||||
|
pub mod zwlr_data_control_offer_v1;
|
||||||
|
pub mod zwlr_data_control_source_v1;
|
||||||
pub mod zwp_primary_selection_device_manager_v1;
|
pub mod zwp_primary_selection_device_manager_v1;
|
||||||
pub mod zwp_primary_selection_device_v1;
|
pub mod zwp_primary_selection_device_v1;
|
||||||
pub mod zwp_primary_selection_offer_v1;
|
pub mod zwp_primary_selection_offer_v1;
|
||||||
pub mod zwp_primary_selection_source_v1;
|
pub mod zwp_primary_selection_source_v1;
|
||||||
|
|
||||||
|
linear_ids!(DataSourceIds, DataSourceId, u64);
|
||||||
|
linear_ids!(DataOfferIds, DataOfferId, u64);
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum IpcLocation {
|
||||||
|
Clipboard,
|
||||||
|
PrimarySelection,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub enum Role {
|
pub enum Role {
|
||||||
Selection,
|
Selection,
|
||||||
Dnd,
|
Dnd,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IpcVtable: Sized {
|
pub trait DataSource: DynDataSource {
|
||||||
type Device;
|
fn send_cancelled(&self, seat: &Rc<WlSeatGlobal>);
|
||||||
type Source;
|
}
|
||||||
type Offer: WaylandObject;
|
|
||||||
|
|
||||||
fn get_device_data(dd: &Self::Device) -> &DeviceData<Self>;
|
pub trait DynDataSource: 'static {
|
||||||
|
fn source_data(&self) -> &SourceData;
|
||||||
|
fn send_send(&self, mime_type: &str, fd: Rc<OwnedFd>);
|
||||||
|
fn offer_to_regular_client(self: Rc<Self>, client: &Rc<Client>);
|
||||||
|
fn offer_to_x(self: Rc<Self>, dd: &Rc<XIpcDevice>);
|
||||||
|
fn offer_to_wlr_device(self: Rc<Self>, dd: &Rc<ZwlrDataControlDeviceV1>);
|
||||||
|
fn detach_seat(&self, seat: &Rc<WlSeatGlobal>);
|
||||||
|
fn cancel_unprivileged_offers(&self);
|
||||||
|
|
||||||
|
fn send_target(&self, mime_type: Option<&str>) {
|
||||||
|
let _ = mime_type;
|
||||||
|
log::warn!(
|
||||||
|
"send_target called on data source of type {}",
|
||||||
|
any::type_name_of_val(self)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn send_dnd_finished(&self) {
|
||||||
|
log::warn!(
|
||||||
|
"send_dnd_finished called on data source of type {}",
|
||||||
|
any::type_name_of_val(self)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn update_selected_action(&self) {
|
||||||
|
log::warn!(
|
||||||
|
"update_selected_action called on data source of type {}",
|
||||||
|
any::type_name_of_val(self)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DataOffer: DynDataOffer {
|
||||||
|
type Device;
|
||||||
|
|
||||||
|
fn offer_data(&self) -> &OfferData<Self::Device>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DynDataOffer: 'static {
|
||||||
|
fn offer_id(&self) -> DataOfferId;
|
||||||
|
fn client_id(&self) -> ClientId;
|
||||||
|
fn send_offer(&self, mime_type: &str);
|
||||||
|
fn destroy(&self);
|
||||||
|
fn cancel(&self);
|
||||||
|
fn get_seat(&self) -> Rc<WlSeatGlobal>;
|
||||||
|
|
||||||
|
fn is_privileged(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_action(&self, action: u32) {
|
||||||
|
let _ = action;
|
||||||
|
log::warn!(
|
||||||
|
"send_action called on data source of type {}",
|
||||||
|
any::type_name_of_val(self)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn send_enter(&self, surface: WlSurfaceId, x: Fixed, y: Fixed, serial: u32) {
|
||||||
|
let _ = surface;
|
||||||
|
let _ = x;
|
||||||
|
let _ = y;
|
||||||
|
let _ = serial;
|
||||||
|
log::warn!(
|
||||||
|
"send_enter called on data source of type {}",
|
||||||
|
any::type_name_of_val(self)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn send_source_actions(&self) {
|
||||||
|
log::warn!(
|
||||||
|
"send_source_actions called on data source of type {}",
|
||||||
|
any::type_name_of_val(self)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IterableIpcVtable: IpcVtable {
|
||||||
|
fn for_each_device<C>(seat: &WlSeatGlobal, client: ClientId, f: C)
|
||||||
|
where
|
||||||
|
C: FnMut(&Rc<Self::Device>);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait WlrIpcVtable: IpcVtable<Device = ZwlrDataControlDeviceV1> {
|
||||||
|
fn for_each_device<C>(seat: &WlSeatGlobal, f: C)
|
||||||
|
where
|
||||||
|
C: FnMut(&Rc<Self::Device>);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IpcVtable: Sized {
|
||||||
|
const LOCATION: IpcLocation;
|
||||||
|
|
||||||
|
type Device;
|
||||||
|
type Source: DataSource;
|
||||||
|
type Offer: DataOffer<Device = Self::Device>;
|
||||||
|
|
||||||
|
fn get_device_data(dd: &Self::Device) -> &DeviceData<Self::Offer>;
|
||||||
fn get_device_seat(dd: &Self::Device) -> Rc<WlSeatGlobal>;
|
fn get_device_seat(dd: &Self::Device) -> Rc<WlSeatGlobal>;
|
||||||
fn create_xwm_source(client: &Rc<Client>) -> Self::Source;
|
|
||||||
fn set_seat_selection(
|
fn set_seat_selection(
|
||||||
seat: &Rc<WlSeatGlobal>,
|
seat: &Rc<WlSeatGlobal>,
|
||||||
source: &Rc<Self::Source>,
|
source: &Rc<Self::Source>,
|
||||||
serial: Option<u32>,
|
serial: Option<u32>,
|
||||||
) -> Result<(), WlSeatError>;
|
) -> Result<(), WlSeatError>;
|
||||||
fn get_offer_data(offer: &Self::Offer) -> &OfferData<Self>;
|
|
||||||
fn get_source_data(src: &Self::Source) -> &SourceData<Self>;
|
|
||||||
fn for_each_device<C>(seat: &WlSeatGlobal, client: ClientId, f: C)
|
|
||||||
where
|
|
||||||
C: FnMut(&Rc<Self::Device>);
|
|
||||||
fn create_offer(
|
fn create_offer(
|
||||||
client: &Rc<Client>,
|
|
||||||
dd: &Rc<Self::Device>,
|
dd: &Rc<Self::Device>,
|
||||||
data: OfferData<Self>,
|
data: OfferData<Self::Device>,
|
||||||
) -> Result<Rc<Self::Offer>, ClientError>;
|
) -> Result<Rc<Self::Offer>, ClientError>;
|
||||||
fn send_selection(dd: &Self::Device, offer: Option<&Rc<Self::Offer>>);
|
fn send_selection(dd: &Self::Device, offer: Option<&Rc<Self::Offer>>);
|
||||||
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_offer(dd: &Self::Device, offer: &Rc<Self::Offer>);
|
||||||
fn send_mime_type(offer: &Rc<Self::Offer>, mime_type: &str);
|
|
||||||
fn unset(seat: &Rc<WlSeatGlobal>, role: Role);
|
fn unset(seat: &Rc<WlSeatGlobal>, role: Role);
|
||||||
fn send_send(src: &Rc<Self::Source>, mime_type: &str, fd: Rc<OwnedFd>);
|
fn device_client(dd: &Rc<Self::Device>) -> &Rc<Client>;
|
||||||
fn remove_from_seat(device: &Self::Device);
|
|
||||||
fn get_offer_seat(offer: &Self::Offer) -> Rc<WlSeatGlobal>;
|
|
||||||
fn source_eq(left: &Self::Source, right: &Self::Source) -> bool;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DeviceData<T: IpcVtable> {
|
pub struct DeviceData<O> {
|
||||||
selection: CloneCell<Option<Rc<T::Offer>>>,
|
selection: CloneCell<Option<Rc<O>>>,
|
||||||
dnd: CloneCell<Option<Rc<T::Offer>>>,
|
dnd: CloneCell<Option<Rc<O>>>,
|
||||||
pub is_xwm: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OfferData<T: IpcVtable> {
|
impl<O> Default for DeviceData<O> {
|
||||||
device: CloneCell<Option<Rc<T::Device>>>,
|
fn default() -> Self {
|
||||||
source: CloneCell<Option<Rc<T::Source>>>,
|
Self {
|
||||||
shared: Rc<SharedState>,
|
selection: Default::default(),
|
||||||
pub is_xwm: bool,
|
dnd: Default::default(),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: IpcVtable> OfferData<T> {
|
|
||||||
pub fn source(&self) -> Option<Rc<T::Source>> {
|
|
||||||
self.source.get()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct OfferData<D> {
|
||||||
|
device: CloneCell<Option<Rc<D>>>,
|
||||||
|
source: CloneCell<Option<Rc<dyn DynDataSource>>>,
|
||||||
|
shared: Rc<SharedState>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum IpcError {
|
pub enum IpcError {
|
||||||
#[error("The data source is already attached")]
|
#[error("The data source is already attached")]
|
||||||
|
|
@ -106,17 +210,16 @@ const SOURCE_STATE_DROPPED: u32 = 1 << 3;
|
||||||
const SOURCE_STATE_CANCELLED: u32 = 1 << 4;
|
const SOURCE_STATE_CANCELLED: u32 = 1 << 4;
|
||||||
const SOURCE_STATE_DROPPED_OR_CANCELLED: u32 = SOURCE_STATE_DROPPED | SOURCE_STATE_CANCELLED;
|
const SOURCE_STATE_DROPPED_OR_CANCELLED: u32 = SOURCE_STATE_DROPPED | SOURCE_STATE_CANCELLED;
|
||||||
|
|
||||||
pub struct SourceData<T: IpcVtable> {
|
pub struct SourceData {
|
||||||
pub seat: CloneCell<Option<Rc<WlSeatGlobal>>>,
|
pub seat: CloneCell<Option<Rc<WlSeatGlobal>>>,
|
||||||
offers: SmallMap<u64, Rc<T::Offer>, 1>,
|
pub id: DataSourceId,
|
||||||
offer_client: Cell<ClientId>,
|
offers: SmallMap<DataOfferId, Rc<dyn DynDataOffer>, 1>,
|
||||||
mime_types: RefCell<AHashSet<String>>,
|
mime_types: RefCell<AHashSet<String>>,
|
||||||
pub client: Rc<Client>,
|
pub client: Rc<Client>,
|
||||||
state: NumCell<u32>,
|
state: NumCell<u32>,
|
||||||
actions: Cell<Option<u32>>,
|
actions: Cell<Option<u32>>,
|
||||||
role: Cell<Role>,
|
role: Cell<Role>,
|
||||||
shared: CloneCell<Rc<SharedState>>,
|
shared: CloneCell<Rc<SharedState>>,
|
||||||
pub is_xwm: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SharedState {
|
struct SharedState {
|
||||||
|
|
@ -139,19 +242,18 @@ impl Default for SharedState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: IpcVtable> SourceData<T> {
|
impl SourceData {
|
||||||
fn new(client: &Rc<Client>, is_xwm: bool) -> Self {
|
pub fn new(client: &Rc<Client>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
seat: Default::default(),
|
seat: Default::default(),
|
||||||
|
id: client.state.data_source_ids.next(),
|
||||||
offers: Default::default(),
|
offers: Default::default(),
|
||||||
offer_client: Cell::new(client.id),
|
|
||||||
mime_types: Default::default(),
|
mime_types: Default::default(),
|
||||||
client: client.clone(),
|
client: client.clone(),
|
||||||
state: NumCell::new(0),
|
state: NumCell::new(0),
|
||||||
actions: Cell::new(None),
|
actions: Cell::new(None),
|
||||||
role: Cell::new(Role::Selection),
|
role: Cell::new(Role::Selection),
|
||||||
shared: Default::default(),
|
shared: Default::default(),
|
||||||
is_xwm,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,12 +268,12 @@ impl<T: IpcVtable> SourceData<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attach_seat<T: IpcVtable>(
|
pub fn attach_seat<S: DynDataSource>(
|
||||||
src: &T::Source,
|
src: &S,
|
||||||
seat: &Rc<WlSeatGlobal>,
|
seat: &Rc<WlSeatGlobal>,
|
||||||
role: Role,
|
role: Role,
|
||||||
) -> Result<(), IpcError> {
|
) -> Result<(), IpcError> {
|
||||||
let data = T::get_source_data(src);
|
let data = src.source_data();
|
||||||
let mut state = data.state.get();
|
let mut state = data.state.get();
|
||||||
if state.contains(SOURCE_STATE_USED) {
|
if state.contains(SOURCE_STATE_USED) {
|
||||||
return Err(IpcError::AlreadyAttached);
|
return Err(IpcError::AlreadyAttached);
|
||||||
|
|
@ -192,27 +294,99 @@ pub fn attach_seat<T: IpcVtable>(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cancel_offers<T: IpcVtable>(src: &T::Source) {
|
pub fn cancel_offers<S: DynDataSource>(src: &S, cancel_privileged: bool) {
|
||||||
let data = T::get_source_data(src);
|
let data = src.source_data();
|
||||||
while let Some((_, offer)) = data.offers.pop() {
|
let mut offers = data.offers.take();
|
||||||
let data = T::get_offer_data(&offer);
|
offers.retain(|o| {
|
||||||
data.source.take();
|
let retain = !cancel_privileged && o.1.is_privileged();
|
||||||
destroy_data_offer::<T>(&offer);
|
if !retain {
|
||||||
}
|
o.1.cancel();
|
||||||
|
}
|
||||||
|
retain
|
||||||
|
});
|
||||||
|
data.offers.replace(offers);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn detach_seat<T: IpcVtable>(src: &Rc<T::Source>, seat: &Rc<WlSeatGlobal>) {
|
pub fn cancel_offer<T: IpcVtable>(offer: &T::Offer) {
|
||||||
let data = T::get_source_data(src);
|
let data = offer.offer_data();
|
||||||
|
data.source.take();
|
||||||
|
destroy_data_offer::<T>(&offer);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn detach_seat<S: DataSource>(src: &S, seat: &Rc<WlSeatGlobal>) {
|
||||||
|
let data = src.source_data();
|
||||||
data.seat.set(None);
|
data.seat.set(None);
|
||||||
cancel_offers::<T>(src);
|
cancel_offers(src, true);
|
||||||
if !data.state.get().contains(SOURCE_STATE_FINISHED) {
|
if !data.state.get().contains(SOURCE_STATE_FINISHED) {
|
||||||
T::send_cancelled(src, seat);
|
src.send_cancelled(seat);
|
||||||
}
|
}
|
||||||
// data.client.flush();
|
// data.client.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn offer_source_to<T: IpcVtable>(src: &Rc<T::Source>, client: &Rc<Client>) {
|
fn offer_source_to_device<T: IpcVtable, S: DynDataSource>(
|
||||||
let data = T::get_source_data(src);
|
src: &Rc<S>,
|
||||||
|
dd: &Rc<T::Device>,
|
||||||
|
data: &SourceData,
|
||||||
|
shared: Rc<SharedState>,
|
||||||
|
) {
|
||||||
|
let device_data = T::get_device_data(dd);
|
||||||
|
let offer_data = OfferData {
|
||||||
|
device: CloneCell::new(Some(dd.clone())),
|
||||||
|
source: CloneCell::new(Some(src.clone())),
|
||||||
|
shared: shared.clone(),
|
||||||
|
};
|
||||||
|
let offer = match T::create_offer(dd, offer_data) {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(e) => {
|
||||||
|
T::device_client(dd).error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
data.offers.insert(offer.offer_id(), offer.clone());
|
||||||
|
let mt = data.mime_types.borrow_mut();
|
||||||
|
T::send_offer(dd, &offer);
|
||||||
|
for mt in mt.deref() {
|
||||||
|
offer.clone().send_offer(mt);
|
||||||
|
}
|
||||||
|
match data.role.get() {
|
||||||
|
Role::Selection => {
|
||||||
|
T::send_selection(dd, Some(&offer));
|
||||||
|
device_data.selection.set(Some(offer.clone()));
|
||||||
|
}
|
||||||
|
Role::Dnd => {
|
||||||
|
device_data.dnd.set(Some(offer.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offer_source_to_x<T, S>(src: &Rc<S>, dd: &Rc<XIpcDevice>)
|
||||||
|
where
|
||||||
|
T: IpcVtable<Device = XIpcDevice>,
|
||||||
|
S: DynDataSource,
|
||||||
|
{
|
||||||
|
let data = src.source_data();
|
||||||
|
src.cancel_unprivileged_offers();
|
||||||
|
let shared = data.shared.get();
|
||||||
|
shared.role.set(data.role.get());
|
||||||
|
offer_source_to_device::<T, S>(src, dd, data, shared);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn offer_source_to_wlr_device<T, S>(src: &Rc<S>, dd: &Rc<T::Device>)
|
||||||
|
where
|
||||||
|
T: IpcVtable<Device = ZwlrDataControlDeviceV1>,
|
||||||
|
S: DynDataSource,
|
||||||
|
{
|
||||||
|
let data = src.source_data();
|
||||||
|
let shared = data.shared.get();
|
||||||
|
shared.role.set(data.role.get());
|
||||||
|
offer_source_to_device::<T, _>(src, dd, data, shared);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offer_source_to_regular_client<T: IterableIpcVtable, S: DynDataSource>(
|
||||||
|
src: &Rc<S>,
|
||||||
|
client: &Rc<Client>,
|
||||||
|
) {
|
||||||
|
let data = src.source_data();
|
||||||
let seat = match data.seat.get() {
|
let seat = match data.seat.get() {
|
||||||
Some(a) => a,
|
Some(a) => a,
|
||||||
_ => {
|
_ => {
|
||||||
|
|
@ -220,51 +394,19 @@ pub fn offer_source_to<T: IpcVtable>(src: &Rc<T::Source>, client: &Rc<Client>) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cancel_offers::<T>(src);
|
src.cancel_unprivileged_offers();
|
||||||
data.offer_client.set(client.id);
|
|
||||||
let shared = data.shared.get();
|
let shared = data.shared.get();
|
||||||
shared.role.set(data.role.get());
|
shared.role.set(data.role.get());
|
||||||
T::for_each_device(&seat, client.id, |dd| {
|
T::for_each_device(&seat, client.id, |dd| {
|
||||||
let device_data = T::get_device_data(dd);
|
offer_source_to_device::<T, S>(src, dd, data, shared.clone());
|
||||||
let offer_data = OfferData {
|
|
||||||
device: CloneCell::new(Some(dd.clone())),
|
|
||||||
source: CloneCell::new(Some(src.clone())),
|
|
||||||
shared: shared.clone(),
|
|
||||||
is_xwm: device_data.is_xwm,
|
|
||||||
};
|
|
||||||
let offer = match T::create_offer(client, dd, offer_data) {
|
|
||||||
Ok(o) => o,
|
|
||||||
Err(e) => {
|
|
||||||
client.error(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
data.offers.insert(T::get_offer_id(&offer), offer.clone());
|
|
||||||
let mt = data.mime_types.borrow_mut();
|
|
||||||
T::send_offer(dd, &offer);
|
|
||||||
for mt in mt.deref() {
|
|
||||||
T::send_mime_type(&offer, mt);
|
|
||||||
}
|
|
||||||
match data.role.get() {
|
|
||||||
Role::Selection => {
|
|
||||||
T::send_selection(dd, Some(&offer));
|
|
||||||
device_data.selection.set(Some(offer.clone()));
|
|
||||||
}
|
|
||||||
Role::Dnd => {
|
|
||||||
device_data.dnd.set(Some(offer.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !device_data.is_xwm {
|
|
||||||
client.add_server_obj(&offer);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_data_source_mime_type<T: IpcVtable>(src: &T::Source, mime_type: &str) {
|
pub fn add_data_source_mime_type<T: IpcVtable>(src: &T::Source, mime_type: &str) {
|
||||||
let data = T::get_source_data(src);
|
let data = src.source_data();
|
||||||
if data.mime_types.borrow_mut().insert(mime_type.to_string()) {
|
if data.mime_types.borrow_mut().insert(mime_type.to_string()) {
|
||||||
for (_, offer) in &data.offers {
|
for (_, offer) in &data.offers {
|
||||||
T::send_mime_type(&offer, mime_type);
|
offer.send_offer(mime_type);
|
||||||
// let data = T::get_offer_data(&offer);
|
// let data = T::get_offer_data(&offer);
|
||||||
// data.client.flush();
|
// data.client.flush();
|
||||||
}
|
}
|
||||||
|
|
@ -272,14 +414,14 @@ pub fn add_data_source_mime_type<T: IpcVtable>(src: &T::Source, mime_type: &str)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy_data_source<T: IpcVtable>(src: &T::Source) {
|
pub fn destroy_data_source<T: IpcVtable>(src: &T::Source) {
|
||||||
let data = T::get_source_data(src);
|
let data = src.source_data();
|
||||||
if let Some(seat) = data.seat.take() {
|
if let Some(seat) = data.seat.take() {
|
||||||
T::unset(&seat, data.role.get());
|
T::unset(&seat, data.role.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy_data_offer<T: IpcVtable>(offer: &T::Offer) {
|
pub fn destroy_data_offer<T: IpcVtable>(offer: &T::Offer) {
|
||||||
let data = T::get_offer_data(offer);
|
let data = offer.offer_data();
|
||||||
if let Some(device) = data.device.take() {
|
if let Some(device) = data.device.take() {
|
||||||
let device_data = T::get_device_data(&device);
|
let device_data = T::get_device_data(&device);
|
||||||
match data.shared.role.get() {
|
match data.shared.role.get() {
|
||||||
|
|
@ -293,8 +435,8 @@ pub fn destroy_data_offer<T: IpcVtable>(offer: &T::Offer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(src) = data.source.take() {
|
if let Some(src) = data.source.take() {
|
||||||
let src_data = T::get_source_data(&src);
|
let src_data = src.source_data();
|
||||||
src_data.offers.remove(&T::get_offer_id(offer));
|
src_data.offers.remove(&offer.offer_id());
|
||||||
if src_data.offers.is_empty()
|
if src_data.offers.is_empty()
|
||||||
&& src_data.role.get() == Role::Dnd
|
&& src_data.role.get() == Role::Dnd
|
||||||
&& data.shared.state.get().contains(OFFER_STATE_DROPPED)
|
&& data.shared.state.get().contains(OFFER_STATE_DROPPED)
|
||||||
|
|
@ -310,21 +452,27 @@ pub fn destroy_data_device<T: IpcVtable>(dd: &T::Device) {
|
||||||
let data = T::get_device_data(dd);
|
let data = T::get_device_data(dd);
|
||||||
let offers = [data.selection.take(), data.dnd.take()];
|
let offers = [data.selection.take(), data.dnd.take()];
|
||||||
for offer in offers.into_iter().flat_map(|o| o.into_iter()) {
|
for offer in offers.into_iter().flat_map(|o| o.into_iter()) {
|
||||||
T::get_offer_data(&offer).device.take();
|
offer.offer_data().device.take();
|
||||||
destroy_data_offer::<T>(&offer);
|
destroy_data_offer::<T>(&offer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn break_source_loops<T: IpcVtable>(src: &T::Source) {
|
fn break_source_loops<T: IpcVtable>(src: &T::Source) {
|
||||||
let data = T::get_source_data(src);
|
let data = src.source_data();
|
||||||
if data.offer_client.get() == data.client.id {
|
let mut remove = SmallVec::<[DataOfferId; 1]>::new();
|
||||||
data.offers.take();
|
for (id, offer) in &data.offers {
|
||||||
|
if offer.client_id() == data.client.id {
|
||||||
|
remove.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while let Some(id) = remove.pop() {
|
||||||
|
data.offers.remove(&id);
|
||||||
}
|
}
|
||||||
destroy_data_source::<T>(src);
|
destroy_data_source::<T>(src);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn break_offer_loops<T: IpcVtable>(offer: &T::Offer) {
|
fn break_offer_loops<T: IpcVtable>(offer: &T::Offer) {
|
||||||
let data = T::get_offer_data(offer);
|
let data = offer.offer_data();
|
||||||
data.device.set(None);
|
data.device.set(None);
|
||||||
destroy_data_offer::<T>(offer);
|
destroy_data_offer::<T>(offer);
|
||||||
}
|
}
|
||||||
|
|
@ -337,9 +485,9 @@ fn break_device_loops<T: IpcVtable>(dd: &T::Device) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn receive_data_offer<T: IpcVtable>(offer: &T::Offer, mime_type: &str, fd: Rc<OwnedFd>) {
|
pub fn receive_data_offer<T: IpcVtable>(offer: &T::Offer, mime_type: &str, fd: Rc<OwnedFd>) {
|
||||||
let data = T::get_offer_data(offer);
|
let data = offer.offer_data();
|
||||||
if let Some(src) = data.source.get() {
|
if let Some(src) = data.source.get() {
|
||||||
T::send_send(&src, mime_type, fd);
|
src.send_send(mime_type, fd);
|
||||||
// let data = T::get_source_data(&src);
|
// let data = T::get_source_data(&src);
|
||||||
// data.client.flush();
|
// data.client.flush();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ use {
|
||||||
ifs::{
|
ifs::{
|
||||||
ipc::{
|
ipc::{
|
||||||
break_device_loops, destroy_data_device, wl_data_offer::WlDataOffer,
|
break_device_loops, destroy_data_device, wl_data_offer::WlDataOffer,
|
||||||
wl_data_source::WlDataSource, DeviceData, IpcVtable, OfferData, Role, SourceData,
|
wl_data_source::WlDataSource, DeviceData, IpcLocation, IpcVtable,
|
||||||
|
IterableIpcVtable, OfferData, Role,
|
||||||
},
|
},
|
||||||
wl_seat::{WlSeatError, WlSeatGlobal},
|
wl_seat::{WlSeatError, WlSeatGlobal},
|
||||||
wl_surface::{SurfaceRole, WlSurfaceError},
|
wl_surface::{SurfaceRole, WlSurfaceError},
|
||||||
|
|
@ -13,12 +14,10 @@ use {
|
||||||
leaks::Tracker,
|
leaks::Tracker,
|
||||||
object::Object,
|
object::Object,
|
||||||
utils::buffd::{MsgParser, MsgParserError},
|
utils::buffd::{MsgParser, MsgParserError},
|
||||||
wire::{wl_data_device::*, WlDataDeviceId, WlDataOfferId, WlDataSourceId, WlSurfaceId},
|
wire::{wl_data_device::*, WlDataDeviceId, WlDataOfferId, WlSurfaceId},
|
||||||
xwayland::XWaylandEvent,
|
|
||||||
},
|
},
|
||||||
std::rc::Rc,
|
std::rc::Rc,
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
uapi::OwnedFd,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|
@ -29,7 +28,7 @@ pub struct WlDataDevice {
|
||||||
pub client: Rc<Client>,
|
pub client: Rc<Client>,
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
pub seat: Rc<WlSeatGlobal>,
|
pub seat: Rc<WlSeatGlobal>,
|
||||||
pub data: DeviceData<ClipboardIpc>,
|
pub data: DeviceData<WlDataOffer>,
|
||||||
pub tracker: Tracker<Self>,
|
pub tracker: Tracker<Self>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,60 +38,34 @@ impl WlDataDevice {
|
||||||
client: &Rc<Client>,
|
client: &Rc<Client>,
|
||||||
version: u32,
|
version: u32,
|
||||||
seat: &Rc<WlSeatGlobal>,
|
seat: &Rc<WlSeatGlobal>,
|
||||||
is_xwm: bool,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
client: client.clone(),
|
client: client.clone(),
|
||||||
version,
|
version,
|
||||||
seat: seat.clone(),
|
seat: seat.clone(),
|
||||||
data: DeviceData {
|
data: Default::default(),
|
||||||
selection: Default::default(),
|
|
||||||
dnd: Default::default(),
|
|
||||||
is_xwm,
|
|
||||||
},
|
|
||||||
tracker: Default::default(),
|
tracker: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_data_offer(&self, offer: &Rc<WlDataOffer>) {
|
pub fn send_data_offer(&self, offer: &Rc<WlDataOffer>) {
|
||||||
if self.data.is_xwm {
|
self.client.event(DataOffer {
|
||||||
self.client
|
self_id: self.id,
|
||||||
.state
|
id: offer.id,
|
||||||
.xwayland
|
})
|
||||||
.queue
|
|
||||||
.push(XWaylandEvent::ClipboardSetOffer(offer.clone()));
|
|
||||||
} else {
|
|
||||||
self.client.event(DataOffer {
|
|
||||||
self_id: self.id,
|
|
||||||
id: offer.id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_selection(&self, offer: Option<&Rc<WlDataOffer>>) {
|
pub fn send_selection(&self, offer: Option<&Rc<WlDataOffer>>) {
|
||||||
if self.data.is_xwm {
|
let id = offer.map(|o| o.id).unwrap_or(WlDataOfferId::NONE);
|
||||||
self.client
|
self.client.event(Selection {
|
||||||
.state
|
self_id: self.id,
|
||||||
.xwayland
|
id,
|
||||||
.queue
|
})
|
||||||
.push(XWaylandEvent::ClipboardSetSelection(
|
|
||||||
self.seat.id(),
|
|
||||||
offer.cloned(),
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
let id = offer.map(|o| o.id).unwrap_or(WlDataOfferId::NONE);
|
|
||||||
self.client.event(Selection {
|
|
||||||
self_id: self.id,
|
|
||||||
id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_leave(&self) {
|
pub fn send_leave(&self) {
|
||||||
if !self.data.is_xwm {
|
self.client.event(Leave { self_id: self.id })
|
||||||
self.client.event(Leave { self_id: self.id })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_enter(
|
pub fn send_enter(
|
||||||
|
|
@ -103,33 +76,27 @@ impl WlDataDevice {
|
||||||
offer: WlDataOfferId,
|
offer: WlDataOfferId,
|
||||||
serial: u32,
|
serial: u32,
|
||||||
) {
|
) {
|
||||||
if !self.data.is_xwm {
|
self.client.event(Enter {
|
||||||
self.client.event(Enter {
|
self_id: self.id,
|
||||||
self_id: self.id,
|
serial,
|
||||||
serial,
|
surface,
|
||||||
surface,
|
x,
|
||||||
x,
|
y,
|
||||||
y,
|
id: offer,
|
||||||
id: offer,
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_motion(&self, time_usec: u64, x: Fixed, y: Fixed) {
|
pub fn send_motion(&self, time_usec: u64, x: Fixed, y: Fixed) {
|
||||||
if !self.data.is_xwm {
|
self.client.event(Motion {
|
||||||
self.client.event(Motion {
|
self_id: self.id,
|
||||||
self_id: self.id,
|
time: (time_usec / 1000) as _,
|
||||||
time: (time_usec / 1000) as _,
|
x,
|
||||||
x,
|
y,
|
||||||
y,
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_drop(&self) {
|
pub fn send_drop(&self) {
|
||||||
if !self.data.is_xwm {
|
self.client.event(Drop { self_id: self.id })
|
||||||
self.client.event(Drop { self_id: self.id })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_drag(&self, parser: MsgParser<'_, '_>) -> Result<(), WlDataDeviceError> {
|
fn start_drag(&self, parser: MsgParser<'_, '_>) -> Result<(), WlDataDeviceError> {
|
||||||
|
|
@ -170,7 +137,8 @@ impl WlDataDevice {
|
||||||
} else {
|
} else {
|
||||||
Some(self.client.lookup(req.source)?)
|
Some(self.client.lookup(req.source)?)
|
||||||
};
|
};
|
||||||
self.seat.set_selection(src, Some(req.serial))?;
|
self.seat
|
||||||
|
.set_wl_data_source_selection(src, Some(req.serial))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,12 +153,23 @@ impl WlDataDevice {
|
||||||
|
|
||||||
pub struct ClipboardIpc;
|
pub struct ClipboardIpc;
|
||||||
|
|
||||||
|
impl IterableIpcVtable for ClipboardIpc {
|
||||||
|
fn for_each_device<C>(seat: &WlSeatGlobal, client: ClientId, f: C)
|
||||||
|
where
|
||||||
|
C: FnMut(&Rc<Self::Device>),
|
||||||
|
{
|
||||||
|
seat.for_each_data_device(0, client, f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IpcVtable for ClipboardIpc {
|
impl IpcVtable for ClipboardIpc {
|
||||||
|
const LOCATION: IpcLocation = IpcLocation::Clipboard;
|
||||||
|
|
||||||
type Device = WlDataDevice;
|
type Device = WlDataDevice;
|
||||||
type Source = WlDataSource;
|
type Source = WlDataSource;
|
||||||
type Offer = WlDataOffer;
|
type Offer = WlDataOffer;
|
||||||
|
|
||||||
fn get_device_data(dd: &Self::Device) -> &DeviceData<Self> {
|
fn get_device_data(dd: &Self::Device) -> &DeviceData<Self::Offer> {
|
||||||
&dd.data
|
&dd.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,47 +177,28 @@ impl IpcVtable for ClipboardIpc {
|
||||||
dd.seat.clone()
|
dd.seat.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_xwm_source(client: &Rc<Client>) -> Self::Source {
|
|
||||||
WlDataSource::new(WlDataSourceId::NONE, client, true, 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_seat_selection(
|
fn set_seat_selection(
|
||||||
seat: &Rc<WlSeatGlobal>,
|
seat: &Rc<WlSeatGlobal>,
|
||||||
source: &Rc<Self::Source>,
|
source: &Rc<Self::Source>,
|
||||||
serial: Option<u32>,
|
serial: Option<u32>,
|
||||||
) -> Result<(), WlSeatError> {
|
) -> Result<(), WlSeatError> {
|
||||||
seat.set_selection(Some(source.clone()), serial)
|
seat.set_wl_data_source_selection(Some(source.clone()), serial)
|
||||||
}
|
|
||||||
|
|
||||||
fn get_offer_data(offer: &Self::Offer) -> &OfferData<Self> {
|
|
||||||
&offer.data
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_source_data(src: &Self::Source) -> &SourceData<Self> {
|
|
||||||
&src.data
|
|
||||||
}
|
|
||||||
|
|
||||||
fn for_each_device<C>(seat: &WlSeatGlobal, client: ClientId, f: C)
|
|
||||||
where
|
|
||||||
C: FnMut(&Rc<Self::Device>),
|
|
||||||
{
|
|
||||||
seat.for_each_data_device(0, client, f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_offer(
|
fn create_offer(
|
||||||
client: &Rc<Client>,
|
|
||||||
device: &Rc<WlDataDevice>,
|
device: &Rc<WlDataDevice>,
|
||||||
offer_data: OfferData<Self>,
|
offer_data: OfferData<Self::Device>,
|
||||||
) -> Result<Rc<Self::Offer>, ClientError> {
|
) -> Result<Rc<Self::Offer>, ClientError> {
|
||||||
let rc = Rc::new(WlDataOffer {
|
let rc = Rc::new(WlDataOffer {
|
||||||
id: client.new_id()?,
|
id: device.client.new_id()?,
|
||||||
u64_id: client.state.data_offer_ids.fetch_add(1),
|
offer_id: device.client.state.data_offer_ids.next(),
|
||||||
client: client.clone(),
|
client: device.client.clone(),
|
||||||
device: device.clone(),
|
device: device.clone(),
|
||||||
data: offer_data,
|
data: offer_data,
|
||||||
tracker: Default::default(),
|
tracker: Default::default(),
|
||||||
});
|
});
|
||||||
track!(client, rc);
|
track!(device.client, rc);
|
||||||
|
device.client.add_server_obj(&rc);
|
||||||
Ok(rc)
|
Ok(rc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,22 +206,10 @@ impl IpcVtable for ClipboardIpc {
|
||||||
dd.send_selection(offer);
|
dd.send_selection(offer);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_cancelled(source: &Rc<Self::Source>, seat: &Rc<WlSeatGlobal>) {
|
|
||||||
source.send_cancelled(seat);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_offer_id(offer: &Self::Offer) -> u64 {
|
|
||||||
offer.u64_id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_offer(dd: &Self::Device, offer: &Rc<Self::Offer>) {
|
fn send_offer(dd: &Self::Device, offer: &Rc<Self::Offer>) {
|
||||||
dd.send_data_offer(offer);
|
dd.send_data_offer(offer);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_mime_type(offer: &Rc<Self::Offer>, mime_type: &str) {
|
|
||||||
offer.send_offer(mime_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unset(seat: &Rc<WlSeatGlobal>, role: Role) {
|
fn unset(seat: &Rc<WlSeatGlobal>, role: Role) {
|
||||||
match role {
|
match role {
|
||||||
Role::Selection => seat.unset_selection(),
|
Role::Selection => seat.unset_selection(),
|
||||||
|
|
@ -269,20 +217,8 @@ impl IpcVtable for ClipboardIpc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_send(src: &Rc<Self::Source>, mime_type: &str, fd: Rc<OwnedFd>) {
|
fn device_client(dd: &Rc<Self::Device>) -> &Rc<Client> {
|
||||||
src.send_send(mime_type, fd);
|
&dd.client
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_from_seat(device: &Self::Device) {
|
|
||||||
device.seat.remove_data_device(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_offer_seat(offer: &Self::Offer) -> Rc<WlSeatGlobal> {
|
|
||||||
offer.device.seat.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn source_eq(left: &Self::Source, right: &Self::Source) -> bool {
|
|
||||||
left as *const _ == right as *const _
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ impl WlDataDeviceManager {
|
||||||
parser: MsgParser<'_, '_>,
|
parser: MsgParser<'_, '_>,
|
||||||
) -> Result<(), WlDataDeviceManagerError> {
|
) -> Result<(), WlDataDeviceManagerError> {
|
||||||
let req: CreateDataSource = self.client.parse(self, parser)?;
|
let req: CreateDataSource = self.client.parse(self, parser)?;
|
||||||
let res = Rc::new(WlDataSource::new(req.id, &self.client, false, self.version));
|
let res = Rc::new(WlDataSource::new(req.id, &self.client, self.version));
|
||||||
track!(self.client, res);
|
track!(self.client, res);
|
||||||
self.client.add_client_obj(&res)?;
|
self.client.add_client_obj(&res)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -78,7 +78,6 @@ impl WlDataDeviceManager {
|
||||||
&self.client,
|
&self.client,
|
||||||
self.version,
|
self.version,
|
||||||
&seat.global,
|
&seat.global,
|
||||||
false,
|
|
||||||
));
|
));
|
||||||
track!(self.client, dev);
|
track!(self.client, dev);
|
||||||
seat.global.add_data_device(&dev);
|
seat.global.add_data_device(&dev);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
client::{Client, ClientError},
|
client::{Client, ClientError, ClientId},
|
||||||
ifs::ipc::{
|
fixed::Fixed,
|
||||||
break_offer_loops, destroy_data_offer, receive_data_offer,
|
ifs::{
|
||||||
wl_data_device::{ClipboardIpc, WlDataDevice},
|
ipc::{
|
||||||
wl_data_device_manager::DND_ALL,
|
break_offer_loops, cancel_offer, destroy_data_offer, receive_data_offer,
|
||||||
OfferData, Role, OFFER_STATE_ACCEPTED, OFFER_STATE_DROPPED, OFFER_STATE_FINISHED,
|
wl_data_device::{ClipboardIpc, WlDataDevice},
|
||||||
SOURCE_STATE_FINISHED,
|
wl_data_device_manager::DND_ALL,
|
||||||
|
DataOffer, DataOfferId, DynDataOffer, OfferData, Role, OFFER_STATE_ACCEPTED,
|
||||||
|
OFFER_STATE_DROPPED, OFFER_STATE_FINISHED, SOURCE_STATE_FINISHED,
|
||||||
|
},
|
||||||
|
wl_seat::WlSeatGlobal,
|
||||||
},
|
},
|
||||||
leaks::Tracker,
|
leaks::Tracker,
|
||||||
object::Object,
|
object::Object,
|
||||||
|
|
@ -14,8 +18,7 @@ use {
|
||||||
bitflags::BitflagsExt,
|
bitflags::BitflagsExt,
|
||||||
buffd::{MsgParser, MsgParserError},
|
buffd::{MsgParser, MsgParserError},
|
||||||
},
|
},
|
||||||
wire::{wl_data_offer::*, WlDataOfferId},
|
wire::{wl_data_offer::*, WlDataOfferId, WlSurfaceId},
|
||||||
xwayland::XWaylandEvent,
|
|
||||||
},
|
},
|
||||||
std::rc::Rc,
|
std::rc::Rc,
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
|
|
@ -32,54 +35,83 @@ const INVALID_OFFER: u32 = 3;
|
||||||
|
|
||||||
pub struct WlDataOffer {
|
pub struct WlDataOffer {
|
||||||
pub id: WlDataOfferId,
|
pub id: WlDataOfferId,
|
||||||
pub u64_id: u64,
|
pub offer_id: DataOfferId,
|
||||||
pub client: Rc<Client>,
|
pub client: Rc<Client>,
|
||||||
pub device: Rc<WlDataDevice>,
|
pub device: Rc<WlDataDevice>,
|
||||||
pub data: OfferData<ClipboardIpc>,
|
pub data: OfferData<WlDataDevice>,
|
||||||
pub tracker: Tracker<Self>,
|
pub tracker: Tracker<Self>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DataOffer for WlDataOffer {
|
||||||
|
type Device = WlDataDevice;
|
||||||
|
|
||||||
|
fn offer_data(&self) -> &OfferData<Self::Device> {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynDataOffer for WlDataOffer {
|
||||||
|
fn offer_id(&self) -> DataOfferId {
|
||||||
|
self.offer_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client_id(&self) -> ClientId {
|
||||||
|
self.client.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_action(&self, action: u32) {
|
||||||
|
WlDataOffer::send_action(self, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_offer(&self, mime_type: &str) {
|
||||||
|
WlDataOffer::send_offer(self, mime_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(&self) {
|
||||||
|
destroy_data_offer::<ClipboardIpc>(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel(&self) {
|
||||||
|
cancel_offer::<ClipboardIpc>(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_enter(&self, surface: WlSurfaceId, x: Fixed, y: Fixed, serial: u32) {
|
||||||
|
self.device.send_enter(surface, x, y, self.id, serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_source_actions(&self) {
|
||||||
|
WlDataOffer::send_source_actions(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_seat(&self) -> Rc<WlSeatGlobal> {
|
||||||
|
self.device.seat.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl WlDataOffer {
|
impl WlDataOffer {
|
||||||
pub fn send_offer(self: &Rc<Self>, mime_type: &str) {
|
pub fn send_offer(&self, mime_type: &str) {
|
||||||
if self.data.is_xwm {
|
self.client.event(Offer {
|
||||||
if let Some(src) = self.data.source.get() {
|
self_id: self.id,
|
||||||
if !src.data.is_xwm {
|
mime_type,
|
||||||
self.client.state.xwayland.queue.push(
|
})
|
||||||
XWaylandEvent::ClipboardAddOfferMimeType(
|
|
||||||
self.clone(),
|
|
||||||
mime_type.to_string(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.client.event(Offer {
|
|
||||||
self_id: self.id,
|
|
||||||
mime_type,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_source_actions(&self) {
|
pub fn send_source_actions(&self) {
|
||||||
if !self.data.is_xwm {
|
if let Some(src) = self.data.source.get() {
|
||||||
if let Some(src) = self.data.source.get() {
|
if let Some(source_actions) = src.source_data().actions.get() {
|
||||||
if let Some(source_actions) = src.data.actions.get() {
|
self.client.event(SourceActions {
|
||||||
self.client.event(SourceActions {
|
self_id: self.id,
|
||||||
self_id: self.id,
|
source_actions,
|
||||||
source_actions,
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_action(&self, dnd_action: u32) {
|
pub fn send_action(&self, dnd_action: u32) {
|
||||||
if !self.data.is_xwm {
|
self.client.event(Action {
|
||||||
self.client.event(Action {
|
self_id: self.id,
|
||||||
self_id: self.id,
|
dnd_action,
|
||||||
dnd_action,
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accept(&self, parser: MsgParser<'_, '_>) -> Result<(), WlDataOfferError> {
|
fn accept(&self, parser: MsgParser<'_, '_>) -> Result<(), WlDataOfferError> {
|
||||||
|
|
@ -134,7 +166,7 @@ impl WlDataOffer {
|
||||||
}
|
}
|
||||||
state |= OFFER_STATE_FINISHED;
|
state |= OFFER_STATE_FINISHED;
|
||||||
if let Some(src) = self.data.source.get() {
|
if let Some(src) = self.data.source.get() {
|
||||||
src.data.state.or_assign(SOURCE_STATE_FINISHED);
|
src.source_data().state.or_assign(SOURCE_STATE_FINISHED);
|
||||||
src.send_dnd_finished();
|
src.send_dnd_finished();
|
||||||
} else {
|
} else {
|
||||||
log::error!("no source");
|
log::error!("no source");
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,15 @@ use {
|
||||||
ifs::{
|
ifs::{
|
||||||
ipc::{
|
ipc::{
|
||||||
add_data_source_mime_type, break_source_loops, cancel_offers, destroy_data_source,
|
add_data_source_mime_type, break_source_loops, cancel_offers, destroy_data_source,
|
||||||
|
detach_seat, offer_source_to_regular_client, offer_source_to_wlr_device,
|
||||||
|
offer_source_to_x,
|
||||||
wl_data_device::ClipboardIpc,
|
wl_data_device::ClipboardIpc,
|
||||||
wl_data_device_manager::{DND_ALL, DND_NONE},
|
wl_data_device_manager::{DND_ALL, DND_NONE},
|
||||||
wl_data_offer::WlDataOffer,
|
x_data_device::{XClipboardIpc, XIpcDevice},
|
||||||
SharedState, SourceData, OFFER_STATE_ACCEPTED, OFFER_STATE_DROPPED,
|
zwlr_data_control_device_v1::{WlrClipboardIpc, ZwlrDataControlDeviceV1},
|
||||||
SOURCE_STATE_CANCELLED, SOURCE_STATE_DROPPED,
|
DataSource, DynDataOffer, DynDataSource, SharedState, SourceData,
|
||||||
|
OFFER_STATE_ACCEPTED, OFFER_STATE_DROPPED, SOURCE_STATE_CANCELLED,
|
||||||
|
SOURCE_STATE_DROPPED,
|
||||||
},
|
},
|
||||||
wl_seat::WlSeatGlobal,
|
wl_seat::WlSeatGlobal,
|
||||||
xdg_toplevel_drag_v1::XdgToplevelDragV1,
|
xdg_toplevel_drag_v1::XdgToplevelDragV1,
|
||||||
|
|
@ -22,7 +26,6 @@ use {
|
||||||
clonecell::CloneCell,
|
clonecell::CloneCell,
|
||||||
},
|
},
|
||||||
wire::{wl_data_source::*, WlDataSourceId},
|
wire::{wl_data_source::*, WlDataSourceId},
|
||||||
xwayland::XWaylandEvent,
|
|
||||||
},
|
},
|
||||||
std::rc::Rc,
|
std::rc::Rc,
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
|
|
@ -36,18 +39,66 @@ const INVALID_SOURCE: u32 = 1;
|
||||||
|
|
||||||
pub struct WlDataSource {
|
pub struct WlDataSource {
|
||||||
pub id: WlDataSourceId,
|
pub id: WlDataSourceId,
|
||||||
pub data: SourceData<ClipboardIpc>,
|
pub data: SourceData,
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
pub tracker: Tracker<Self>,
|
pub tracker: Tracker<Self>,
|
||||||
pub toplevel_drag: CloneCell<Option<Rc<XdgToplevelDragV1>>>,
|
pub toplevel_drag: CloneCell<Option<Rc<XdgToplevelDragV1>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DataSource for WlDataSource {
|
||||||
|
fn send_cancelled(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
|
WlDataSource::send_cancelled(self, seat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynDataSource for WlDataSource {
|
||||||
|
fn source_data(&self) -> &SourceData {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_send(&self, mime_type: &str, fd: Rc<OwnedFd>) {
|
||||||
|
WlDataSource::send_send(self, mime_type, fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offer_to_regular_client(self: Rc<Self>, client: &Rc<Client>) {
|
||||||
|
offer_source_to_regular_client::<ClipboardIpc, Self>(&self, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offer_to_x(self: Rc<Self>, dd: &Rc<XIpcDevice>) {
|
||||||
|
offer_source_to_x::<XClipboardIpc, Self>(&self, dd);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offer_to_wlr_device(self: Rc<Self>, dd: &Rc<ZwlrDataControlDeviceV1>) {
|
||||||
|
offer_source_to_wlr_device::<WlrClipboardIpc, Self>(&self, dd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detach_seat(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
|
detach_seat(self, seat);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel_unprivileged_offers(&self) {
|
||||||
|
cancel_offers(self, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_target(&self, mime_type: Option<&str>) {
|
||||||
|
WlDataSource::send_target(self, mime_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_dnd_finished(&self) {
|
||||||
|
WlDataSource::send_dnd_finished(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_selected_action(&self) {
|
||||||
|
WlDataSource::update_selected_action(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl WlDataSource {
|
impl WlDataSource {
|
||||||
pub fn new(id: WlDataSourceId, client: &Rc<Client>, is_xwm: bool, version: u32) -> Self {
|
pub fn new(id: WlDataSourceId, client: &Rc<Client>, version: u32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
tracker: Default::default(),
|
tracker: Default::default(),
|
||||||
data: SourceData::new(client, is_xwm),
|
data: SourceData::new(client),
|
||||||
version,
|
version,
|
||||||
toplevel_drag: Default::default(),
|
toplevel_drag: Default::default(),
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +118,7 @@ impl WlDataSource {
|
||||||
self.data.shared.set(Rc::new(SharedState::default()));
|
self.data.shared.set(Rc::new(SharedState::default()));
|
||||||
self.send_target(None);
|
self.send_target(None);
|
||||||
self.send_action(DND_NONE);
|
self.send_action(DND_NONE);
|
||||||
cancel_offers::<ClipboardIpc>(self);
|
cancel_offers(self, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_selected_action(&self) {
|
pub fn update_selected_action(&self) {
|
||||||
|
|
@ -97,9 +148,9 @@ impl WlDataSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_each_data_offer<C: FnMut(&WlDataOffer)>(&self, mut f: C) {
|
pub fn for_each_data_offer<C: FnMut(&dyn DynDataOffer)>(&self, mut f: C) {
|
||||||
for (_, offer) in &self.data.offers {
|
for (_, offer) in &self.data.offers {
|
||||||
f(&offer);
|
f(&*offer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,74 +169,44 @@ impl WlDataSource {
|
||||||
shared.state.or_assign(OFFER_STATE_DROPPED);
|
shared.state.or_assign(OFFER_STATE_DROPPED);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_cancelled(self: &Rc<Self>, seat: &Rc<WlSeatGlobal>) {
|
pub fn send_cancelled(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
if self.data.is_xwm {
|
self.data.state.or_assign(SOURCE_STATE_CANCELLED);
|
||||||
self.data
|
if let Some(drag) = self.toplevel_drag.take() {
|
||||||
.client
|
drag.finish_drag(seat);
|
||||||
.state
|
|
||||||
.xwayland
|
|
||||||
.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 })
|
|
||||||
}
|
}
|
||||||
|
self.data.client.event(Cancelled { self_id: self.id })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_send(self: &Rc<Self>, mime_type: &str, fd: Rc<OwnedFd>) {
|
pub fn send_send(&self, mime_type: &str, fd: Rc<OwnedFd>) {
|
||||||
if self.data.is_xwm {
|
self.data.client.event(Send {
|
||||||
self.data
|
self_id: self.id,
|
||||||
.client
|
mime_type,
|
||||||
.state
|
fd,
|
||||||
.xwayland
|
})
|
||||||
.queue
|
|
||||||
.push(XWaylandEvent::ClipboardSendSource(
|
|
||||||
self.clone(),
|
|
||||||
mime_type.to_string(),
|
|
||||||
fd,
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
self.data.client.event(Send {
|
|
||||||
self_id: self.id,
|
|
||||||
mime_type,
|
|
||||||
fd,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_target(&self, mime_type: Option<&str>) {
|
pub fn send_target(&self, mime_type: Option<&str>) {
|
||||||
if !self.data.is_xwm {
|
self.data.client.event(Target {
|
||||||
self.data.client.event(Target {
|
self_id: self.id,
|
||||||
self_id: self.id,
|
mime_type,
|
||||||
mime_type,
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_dnd_finished(&self) {
|
pub fn send_dnd_finished(&self) {
|
||||||
if !self.data.is_xwm {
|
self.data.client.event(DndFinished { self_id: self.id })
|
||||||
self.data.client.event(DndFinished { self_id: self.id })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_action(&self, dnd_action: u32) {
|
pub fn send_action(&self, dnd_action: u32) {
|
||||||
if !self.data.is_xwm {
|
self.data.client.event(Action {
|
||||||
self.data.client.event(Action {
|
self_id: self.id,
|
||||||
self_id: self.id,
|
dnd_action,
|
||||||
dnd_action,
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_dnd_drop_performed(&self) {
|
pub fn send_dnd_drop_performed(&self) {
|
||||||
if !self.data.is_xwm {
|
self.data
|
||||||
self.data
|
.client
|
||||||
.client
|
.event(DndDropPerformed { self_id: self.id })
|
||||||
.event(DndDropPerformed { self_id: self.id })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn offer(&self, parser: MsgParser<'_, '_>) -> Result<(), WlDataSourceError> {
|
fn offer(&self, parser: MsgParser<'_, '_>) -> Result<(), WlDataSourceError> {
|
||||||
|
|
|
||||||
134
src/ifs/ipc/x_data_device.rs
Normal file
134
src/ifs/ipc/x_data_device.rs
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
client::{Client, ClientError},
|
||||||
|
ifs::{
|
||||||
|
ipc::{
|
||||||
|
x_data_offer::XDataOffer, x_data_source::XDataSource, DeviceData, IpcLocation,
|
||||||
|
IpcVtable, OfferData, Role,
|
||||||
|
},
|
||||||
|
wl_seat::{WlSeatError, WlSeatGlobal},
|
||||||
|
},
|
||||||
|
state::State,
|
||||||
|
xwayland::XWaylandEvent,
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
XWaylandEvent::IpcSetOffer,
|
||||||
|
};
|
||||||
|
|
||||||
|
linear_ids!(XIpcDeviceIds, XIpcDeviceId, u64);
|
||||||
|
|
||||||
|
pub struct XIpcDevice {
|
||||||
|
pub id: XIpcDeviceId,
|
||||||
|
pub clipboard: DeviceData<XDataOffer>,
|
||||||
|
pub primary_selection: DeviceData<XDataOffer>,
|
||||||
|
pub seat: Rc<WlSeatGlobal>,
|
||||||
|
pub state: Rc<State>,
|
||||||
|
pub client: Rc<Client>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct XClipboardIpc;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct XPrimarySelectionIpc;
|
||||||
|
|
||||||
|
pub trait XIpc {
|
||||||
|
const LOCATION: IpcLocation;
|
||||||
|
|
||||||
|
fn x_unset(seat: &Rc<WlSeatGlobal>);
|
||||||
|
|
||||||
|
fn x_device_data(dd: &XIpcDevice) -> &DeviceData<XDataOffer>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XIpc for XClipboardIpc {
|
||||||
|
const LOCATION: IpcLocation = IpcLocation::Clipboard;
|
||||||
|
|
||||||
|
fn x_unset(seat: &Rc<WlSeatGlobal>) {
|
||||||
|
seat.unset_selection();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn x_device_data(dd: &XIpcDevice) -> &DeviceData<XDataOffer> {
|
||||||
|
&dd.clipboard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XIpc for XPrimarySelectionIpc {
|
||||||
|
const LOCATION: IpcLocation = IpcLocation::PrimarySelection;
|
||||||
|
|
||||||
|
fn x_unset(seat: &Rc<WlSeatGlobal>) {
|
||||||
|
seat.unset_primary_selection();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn x_device_data(dd: &XIpcDevice) -> &DeviceData<XDataOffer> {
|
||||||
|
&dd.primary_selection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: XIpc> IpcVtable for T {
|
||||||
|
const LOCATION: IpcLocation = T::LOCATION;
|
||||||
|
type Device = XIpcDevice;
|
||||||
|
type Source = XDataSource;
|
||||||
|
type Offer = XDataOffer;
|
||||||
|
|
||||||
|
fn get_device_data(dd: &Self::Device) -> &DeviceData<Self::Offer> {
|
||||||
|
T::x_device_data(dd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_device_seat(dd: &Self::Device) -> Rc<WlSeatGlobal> {
|
||||||
|
dd.seat.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_seat_selection(
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
source: &Rc<Self::Source>,
|
||||||
|
_serial: Option<u32>,
|
||||||
|
) -> Result<(), WlSeatError> {
|
||||||
|
match source.location {
|
||||||
|
IpcLocation::Clipboard => seat.set_selection(Some(source.clone())),
|
||||||
|
IpcLocation::PrimarySelection => seat.set_primary_selection(Some(source.clone())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_offer(
|
||||||
|
dd: &Rc<Self::Device>,
|
||||||
|
data: OfferData<Self::Device>,
|
||||||
|
) -> Result<Rc<Self::Offer>, ClientError> {
|
||||||
|
debug_assert!(dd.client.is_xwayland);
|
||||||
|
let rc = Rc::new(XDataOffer {
|
||||||
|
offer_id: dd.state.data_offer_ids.next(),
|
||||||
|
device: dd.clone(),
|
||||||
|
data,
|
||||||
|
tracker: Default::default(),
|
||||||
|
location: T::LOCATION,
|
||||||
|
});
|
||||||
|
track!(dd.client, rc);
|
||||||
|
Ok(rc)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_selection(dd: &Self::Device, offer: Option<&Rc<Self::Offer>>) {
|
||||||
|
dd.state
|
||||||
|
.xwayland
|
||||||
|
.queue
|
||||||
|
.push(XWaylandEvent::IpcSetSelection {
|
||||||
|
seat: dd.seat.id(),
|
||||||
|
location: T::LOCATION,
|
||||||
|
offer: offer.cloned(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_offer(dd: &Self::Device, offer: &Rc<Self::Offer>) {
|
||||||
|
dd.state.xwayland.queue.push(IpcSetOffer {
|
||||||
|
location: T::LOCATION,
|
||||||
|
seat: dd.seat.id(),
|
||||||
|
offer: offer.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unset(seat: &Rc<WlSeatGlobal>, _role: Role) {
|
||||||
|
T::x_unset(seat)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn device_client(dd: &Rc<Self::Device>) -> &Rc<Client> {
|
||||||
|
&dd.client
|
||||||
|
}
|
||||||
|
}
|
||||||
70
src/ifs/ipc/x_data_offer.rs
Normal file
70
src/ifs/ipc/x_data_offer.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
client::ClientId,
|
||||||
|
ifs::{
|
||||||
|
ipc::{
|
||||||
|
cancel_offer, destroy_data_offer,
|
||||||
|
x_data_device::{XClipboardIpc, XIpcDevice, XPrimarySelectionIpc},
|
||||||
|
DataOffer, DataOfferId, DynDataOffer, IpcLocation, OfferData,
|
||||||
|
},
|
||||||
|
wl_seat::WlSeatGlobal,
|
||||||
|
},
|
||||||
|
leaks::Tracker,
|
||||||
|
xwayland::XWaylandEvent,
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
XWaylandEvent::IpcAddOfferMimeType,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct XDataOffer {
|
||||||
|
pub offer_id: DataOfferId,
|
||||||
|
pub device: Rc<XIpcDevice>,
|
||||||
|
pub data: OfferData<XIpcDevice>,
|
||||||
|
pub tracker: Tracker<Self>,
|
||||||
|
pub location: IpcLocation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataOffer for XDataOffer {
|
||||||
|
type Device = XIpcDevice;
|
||||||
|
|
||||||
|
fn offer_data(&self) -> &OfferData<Self::Device> {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynDataOffer for XDataOffer {
|
||||||
|
fn offer_id(&self) -> DataOfferId {
|
||||||
|
self.offer_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client_id(&self) -> ClientId {
|
||||||
|
self.device.client.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_offer(&self, mime_type: &str) {
|
||||||
|
self.device.state.xwayland.queue.push(IpcAddOfferMimeType {
|
||||||
|
location: self.location,
|
||||||
|
seat: self.device.seat.id(),
|
||||||
|
offer: self.offer_id,
|
||||||
|
mime_type: mime_type.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(&self) {
|
||||||
|
match self.location {
|
||||||
|
IpcLocation::Clipboard => destroy_data_offer::<XClipboardIpc>(self),
|
||||||
|
IpcLocation::PrimarySelection => destroy_data_offer::<XPrimarySelectionIpc>(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel(&self) {
|
||||||
|
match self.location {
|
||||||
|
IpcLocation::Clipboard => cancel_offer::<XClipboardIpc>(self),
|
||||||
|
IpcLocation::PrimarySelection => cancel_offer::<XPrimarySelectionIpc>(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_seat(&self) -> Rc<WlSeatGlobal> {
|
||||||
|
self.device.seat.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
95
src/ifs/ipc/x_data_source.rs
Normal file
95
src/ifs/ipc/x_data_source.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
client::Client,
|
||||||
|
ifs::{
|
||||||
|
ipc::{
|
||||||
|
cancel_offers, detach_seat, offer_source_to_regular_client,
|
||||||
|
offer_source_to_wlr_device,
|
||||||
|
wl_data_device::ClipboardIpc,
|
||||||
|
x_data_device::XIpcDevice,
|
||||||
|
zwlr_data_control_device_v1::{
|
||||||
|
WlrClipboardIpc, WlrPrimarySelectionIpc, ZwlrDataControlDeviceV1,
|
||||||
|
},
|
||||||
|
zwp_primary_selection_device_v1::PrimarySelectionIpc,
|
||||||
|
DataSource, DynDataSource, IpcLocation, SourceData,
|
||||||
|
},
|
||||||
|
wl_seat::WlSeatGlobal,
|
||||||
|
},
|
||||||
|
state::State,
|
||||||
|
xwayland::XWaylandEvent::{IpcCancelSource, IpcSendSource, IpcSetSelection},
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
uapi::OwnedFd,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct XDataSource {
|
||||||
|
pub state: Rc<State>,
|
||||||
|
pub device: Rc<XIpcDevice>,
|
||||||
|
pub data: SourceData,
|
||||||
|
pub location: IpcLocation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataSource for XDataSource {
|
||||||
|
fn send_cancelled(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
|
self.state.xwayland.queue.push(IpcCancelSource {
|
||||||
|
location: self.location,
|
||||||
|
seat: seat.id(),
|
||||||
|
source: self.data.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynDataSource for XDataSource {
|
||||||
|
fn source_data(&self) -> &SourceData {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_send(&self, mime_type: &str, fd: Rc<OwnedFd>) {
|
||||||
|
self.state.xwayland.queue.push(IpcSendSource {
|
||||||
|
location: self.location,
|
||||||
|
seat: self.device.seat.id(),
|
||||||
|
source: self.data.id,
|
||||||
|
mime_type: mime_type.to_string(),
|
||||||
|
fd,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offer_to_regular_client(self: Rc<Self>, client: &Rc<Client>) {
|
||||||
|
match self.location {
|
||||||
|
IpcLocation::Clipboard => {
|
||||||
|
offer_source_to_regular_client::<ClipboardIpc, Self>(&self, client)
|
||||||
|
}
|
||||||
|
IpcLocation::PrimarySelection => {
|
||||||
|
offer_source_to_regular_client::<PrimarySelectionIpc, Self>(&self, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offer_to_x(self: Rc<Self>, _dd: &Rc<XIpcDevice>) {
|
||||||
|
self.cancel_unprivileged_offers();
|
||||||
|
self.state.xwayland.queue.push(IpcSetSelection {
|
||||||
|
location: self.location,
|
||||||
|
seat: self.device.seat.id(),
|
||||||
|
offer: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offer_to_wlr_device(self: Rc<Self>, dd: &Rc<ZwlrDataControlDeviceV1>) {
|
||||||
|
match self.location {
|
||||||
|
IpcLocation::Clipboard => {
|
||||||
|
offer_source_to_wlr_device::<WlrClipboardIpc, Self>(&self, dd)
|
||||||
|
}
|
||||||
|
IpcLocation::PrimarySelection => {
|
||||||
|
offer_source_to_wlr_device::<WlrPrimarySelectionIpc, Self>(&self, dd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detach_seat(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
|
detach_seat(self, seat);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel_unprivileged_offers(&self) {
|
||||||
|
cancel_offers(self, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
306
src/ifs/ipc/zwlr_data_control_device_v1.rs
Normal file
306
src/ifs/ipc/zwlr_data_control_device_v1.rs
Normal file
|
|
@ -0,0 +1,306 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
client::{Client, ClientError},
|
||||||
|
ifs::{
|
||||||
|
ipc::{
|
||||||
|
destroy_data_device,
|
||||||
|
zwlr_data_control_device_v1::private::{
|
||||||
|
WlrClipboardIpcCore, WlrIpcImpl, WlrPrimarySelectionIpcCore,
|
||||||
|
},
|
||||||
|
zwlr_data_control_offer_v1::ZwlrDataControlOfferV1,
|
||||||
|
zwlr_data_control_source_v1::ZwlrDataControlSourceV1,
|
||||||
|
DeviceData, IpcLocation, IpcVtable, OfferData, Role, WlrIpcVtable,
|
||||||
|
},
|
||||||
|
wl_seat::{WlSeatError, WlSeatGlobal},
|
||||||
|
},
|
||||||
|
leaks::Tracker,
|
||||||
|
object::Object,
|
||||||
|
utils::buffd::{MsgParser, MsgParserError},
|
||||||
|
wire::{
|
||||||
|
zwlr_data_control_device_v1::*, ZwlrDataControlDeviceV1Id, ZwlrDataControlOfferV1Id,
|
||||||
|
ZwlrDataControlSourceV1Id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const PRIMARY_SELECTION_SINCE: u32 = 2;
|
||||||
|
|
||||||
|
pub struct ZwlrDataControlDeviceV1 {
|
||||||
|
pub id: ZwlrDataControlDeviceV1Id,
|
||||||
|
pub client: Rc<Client>,
|
||||||
|
pub version: u32,
|
||||||
|
pub seat: Rc<WlSeatGlobal>,
|
||||||
|
pub clipboard_data: DeviceData<ZwlrDataControlOfferV1>,
|
||||||
|
pub primary_selection_data: DeviceData<ZwlrDataControlOfferV1>,
|
||||||
|
pub tracker: Tracker<Self>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZwlrDataControlDeviceV1 {
|
||||||
|
pub fn new(
|
||||||
|
id: ZwlrDataControlDeviceV1Id,
|
||||||
|
client: &Rc<Client>,
|
||||||
|
version: u32,
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
client: client.clone(),
|
||||||
|
version,
|
||||||
|
seat: seat.clone(),
|
||||||
|
clipboard_data: Default::default(),
|
||||||
|
primary_selection_data: Default::default(),
|
||||||
|
tracker: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_data_offer(&self, offer: &Rc<ZwlrDataControlOfferV1>) {
|
||||||
|
self.client.event(DataOffer {
|
||||||
|
self_id: self.id,
|
||||||
|
id: offer.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_selection(&self, offer: Option<&Rc<ZwlrDataControlOfferV1>>) {
|
||||||
|
let id = offer
|
||||||
|
.map(|o| o.id)
|
||||||
|
.unwrap_or(ZwlrDataControlOfferV1Id::NONE);
|
||||||
|
self.client.event(Selection {
|
||||||
|
self_id: self.id,
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_primary_selection(&self, offer: Option<&Rc<ZwlrDataControlOfferV1>>) {
|
||||||
|
let id = offer
|
||||||
|
.map(|o| o.id)
|
||||||
|
.unwrap_or(ZwlrDataControlOfferV1Id::NONE);
|
||||||
|
self.client.event(PrimarySelection {
|
||||||
|
self_id: self.id,
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn use_source(
|
||||||
|
&self,
|
||||||
|
source: ZwlrDataControlSourceV1Id,
|
||||||
|
location: IpcLocation,
|
||||||
|
) -> Result<Option<Rc<ZwlrDataControlSourceV1>>, ZwlrDataControlDeviceV1Error> {
|
||||||
|
if source.is_none() {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
let src = self.client.lookup(source)?;
|
||||||
|
if src.used.replace(true) {
|
||||||
|
return Err(ZwlrDataControlDeviceV1Error::AlreadyUsed);
|
||||||
|
}
|
||||||
|
src.location.set(location);
|
||||||
|
Ok(Some(src))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_selection(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrDataControlDeviceV1Error> {
|
||||||
|
let req: SetSelection = self.client.parse(self, parser)?;
|
||||||
|
let src = self.use_source(req.source, IpcLocation::Clipboard)?;
|
||||||
|
self.seat.set_selection(src)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrDataControlDeviceV1Error> {
|
||||||
|
let _req: Destroy = self.client.parse(self, parser)?;
|
||||||
|
destroy_data_device::<WlrClipboardIpc>(self);
|
||||||
|
destroy_data_device::<WlrPrimarySelectionIpc>(self);
|
||||||
|
self.seat.remove_wlr_device(self);
|
||||||
|
self.client.remove_obj(self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_primary_selection(
|
||||||
|
&self,
|
||||||
|
parser: MsgParser<'_, '_>,
|
||||||
|
) -> Result<(), ZwlrDataControlDeviceV1Error> {
|
||||||
|
let req: SetPrimarySelection = self.client.parse(self, parser)?;
|
||||||
|
let src = self.use_source(req.source, IpcLocation::PrimarySelection)?;
|
||||||
|
self.seat.set_primary_selection(src)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod private {
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
pub struct WlrClipboardIpcCore;
|
||||||
|
pub struct WlrPrimarySelectionIpcCore;
|
||||||
|
pub struct WlrIpcImpl<T>(PhantomData<T>);
|
||||||
|
}
|
||||||
|
pub type WlrClipboardIpc = WlrIpcImpl<WlrClipboardIpcCore>;
|
||||||
|
pub type WlrPrimarySelectionIpc = WlrIpcImpl<WlrPrimarySelectionIpcCore>;
|
||||||
|
|
||||||
|
trait WlrIpc {
|
||||||
|
const MIN_VERSION: u32;
|
||||||
|
const LOCATION: IpcLocation;
|
||||||
|
|
||||||
|
fn wlr_get_device_data(dd: &ZwlrDataControlDeviceV1) -> &DeviceData<ZwlrDataControlOfferV1>;
|
||||||
|
|
||||||
|
fn wlr_set_seat_selection(
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
source: &Rc<ZwlrDataControlSourceV1>,
|
||||||
|
) -> Result<(), WlSeatError>;
|
||||||
|
|
||||||
|
fn wlr_send_selection(dd: &ZwlrDataControlDeviceV1, offer: Option<&Rc<ZwlrDataControlOfferV1>>);
|
||||||
|
|
||||||
|
fn wlr_unset(seat: &Rc<WlSeatGlobal>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WlrIpc for WlrClipboardIpcCore {
|
||||||
|
const MIN_VERSION: u32 = 1;
|
||||||
|
const LOCATION: IpcLocation = IpcLocation::Clipboard;
|
||||||
|
|
||||||
|
fn wlr_get_device_data(dd: &ZwlrDataControlDeviceV1) -> &DeviceData<ZwlrDataControlOfferV1> {
|
||||||
|
&dd.clipboard_data
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wlr_set_seat_selection(
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
source: &Rc<ZwlrDataControlSourceV1>,
|
||||||
|
) -> Result<(), WlSeatError> {
|
||||||
|
seat.set_selection(Some(source.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wlr_send_selection(
|
||||||
|
dd: &ZwlrDataControlDeviceV1,
|
||||||
|
offer: Option<&Rc<ZwlrDataControlOfferV1>>,
|
||||||
|
) {
|
||||||
|
dd.send_selection(offer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wlr_unset(seat: &Rc<WlSeatGlobal>) {
|
||||||
|
seat.unset_selection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WlrIpc for WlrPrimarySelectionIpcCore {
|
||||||
|
const MIN_VERSION: u32 = PRIMARY_SELECTION_SINCE;
|
||||||
|
const LOCATION: IpcLocation = IpcLocation::PrimarySelection;
|
||||||
|
|
||||||
|
fn wlr_get_device_data(dd: &ZwlrDataControlDeviceV1) -> &DeviceData<ZwlrDataControlOfferV1> {
|
||||||
|
&dd.primary_selection_data
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wlr_set_seat_selection(
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
source: &Rc<ZwlrDataControlSourceV1>,
|
||||||
|
) -> Result<(), WlSeatError> {
|
||||||
|
seat.set_primary_selection(Some(source.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wlr_send_selection(
|
||||||
|
dd: &ZwlrDataControlDeviceV1,
|
||||||
|
offer: Option<&Rc<ZwlrDataControlOfferV1>>,
|
||||||
|
) {
|
||||||
|
dd.send_primary_selection(offer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wlr_unset(seat: &Rc<WlSeatGlobal>) {
|
||||||
|
seat.unset_primary_selection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: WlrIpc> WlrIpcVtable for WlrIpcImpl<T> {
|
||||||
|
fn for_each_device<C>(seat: &WlSeatGlobal, f: C)
|
||||||
|
where
|
||||||
|
C: FnMut(&Rc<Self::Device>),
|
||||||
|
{
|
||||||
|
seat.for_each_wlr_data_device(T::MIN_VERSION, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: WlrIpc> IpcVtable for WlrIpcImpl<T> {
|
||||||
|
const LOCATION: IpcLocation = T::LOCATION;
|
||||||
|
type Device = ZwlrDataControlDeviceV1;
|
||||||
|
type Source = ZwlrDataControlSourceV1;
|
||||||
|
type Offer = ZwlrDataControlOfferV1;
|
||||||
|
|
||||||
|
fn get_device_data(dd: &Self::Device) -> &DeviceData<Self::Offer> {
|
||||||
|
T::wlr_get_device_data(dd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_device_seat(dd: &Self::Device) -> Rc<WlSeatGlobal> {
|
||||||
|
dd.seat.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_seat_selection(
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
source: &Rc<Self::Source>,
|
||||||
|
serial: Option<u32>,
|
||||||
|
) -> Result<(), WlSeatError> {
|
||||||
|
debug_assert!(serial.is_none());
|
||||||
|
let _ = serial;
|
||||||
|
T::wlr_set_seat_selection(seat, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_offer(
|
||||||
|
device: &Rc<ZwlrDataControlDeviceV1>,
|
||||||
|
offer_data: OfferData<Self::Device>,
|
||||||
|
) -> Result<Rc<Self::Offer>, ClientError> {
|
||||||
|
let rc = Rc::new(ZwlrDataControlOfferV1 {
|
||||||
|
id: device.client.new_id()?,
|
||||||
|
offer_id: device.client.state.data_offer_ids.next(),
|
||||||
|
client: device.client.clone(),
|
||||||
|
device: device.clone(),
|
||||||
|
data: offer_data,
|
||||||
|
location: T::LOCATION,
|
||||||
|
tracker: Default::default(),
|
||||||
|
});
|
||||||
|
track!(device.client, rc);
|
||||||
|
device.client.add_server_obj(&rc);
|
||||||
|
Ok(rc)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_selection(dd: &Self::Device, offer: Option<&Rc<Self::Offer>>) {
|
||||||
|
T::wlr_send_selection(dd, offer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_offer(dd: &Self::Device, offer: &Rc<Self::Offer>) {
|
||||||
|
dd.send_data_offer(offer);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unset(seat: &Rc<WlSeatGlobal>, _role: Role) {
|
||||||
|
T::wlr_unset(seat)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn device_client(dd: &Rc<Self::Device>) -> &Rc<Client> {
|
||||||
|
&dd.client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object_base! {
|
||||||
|
self = ZwlrDataControlDeviceV1;
|
||||||
|
|
||||||
|
SET_SELECTION => set_selection,
|
||||||
|
DESTROY => destroy,
|
||||||
|
SET_PRIMARY_SELECTION => set_primary_selection if self.version >= 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object for ZwlrDataControlDeviceV1 {
|
||||||
|
fn break_loops(&self) {
|
||||||
|
self.seat.remove_wlr_device(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
simple_add_obj!(ZwlrDataControlDeviceV1);
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ZwlrDataControlDeviceV1Error {
|
||||||
|
#[error("Parsing failed")]
|
||||||
|
MsgParserError(#[source] Box<MsgParserError>),
|
||||||
|
#[error(transparent)]
|
||||||
|
ClientError(Box<ClientError>),
|
||||||
|
#[error(transparent)]
|
||||||
|
WlSeatError(Box<WlSeatError>),
|
||||||
|
#[error("The source has already been used")]
|
||||||
|
AlreadyUsed,
|
||||||
|
}
|
||||||
|
efrom!(ZwlrDataControlDeviceV1Error, MsgParserError);
|
||||||
|
efrom!(ZwlrDataControlDeviceV1Error, ClientError);
|
||||||
|
efrom!(ZwlrDataControlDeviceV1Error, WlSeatError);
|
||||||
145
src/ifs/ipc/zwlr_data_control_manager_v1.rs
Normal file
145
src/ifs/ipc/zwlr_data_control_manager_v1.rs
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
client::{Client, ClientError},
|
||||||
|
globals::{Global, GlobalName},
|
||||||
|
ifs::ipc::{
|
||||||
|
zwlr_data_control_device_v1::{ZwlrDataControlDeviceV1, PRIMARY_SELECTION_SINCE},
|
||||||
|
zwlr_data_control_source_v1::ZwlrDataControlSourceV1,
|
||||||
|
},
|
||||||
|
leaks::Tracker,
|
||||||
|
object::Object,
|
||||||
|
utils::buffd::{MsgParser, MsgParserError},
|
||||||
|
wire::{zwlr_data_control_manager_v1::*, ZwlrDataControlManagerV1Id},
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct ZwlrDataControlManagerV1Global {
|
||||||
|
name: GlobalName,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ZwlrDataControlManagerV1 {
|
||||||
|
pub id: ZwlrDataControlManagerV1Id,
|
||||||
|
pub client: Rc<Client>,
|
||||||
|
pub version: u32,
|
||||||
|
tracker: Tracker<Self>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZwlrDataControlManagerV1Global {
|
||||||
|
pub fn new(name: GlobalName) -> Self {
|
||||||
|
Self { name }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bind_(
|
||||||
|
self: Rc<Self>,
|
||||||
|
id: ZwlrDataControlManagerV1Id,
|
||||||
|
client: &Rc<Client>,
|
||||||
|
version: u32,
|
||||||
|
) -> Result<(), ZwlrDataControlManagerV1Error> {
|
||||||
|
let obj = Rc::new(ZwlrDataControlManagerV1 {
|
||||||
|
id,
|
||||||
|
client: client.clone(),
|
||||||
|
version,
|
||||||
|
tracker: Default::default(),
|
||||||
|
});
|
||||||
|
track!(client, obj);
|
||||||
|
client.add_client_obj(&obj)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZwlrDataControlManagerV1 {
|
||||||
|
fn create_data_source(
|
||||||
|
&self,
|
||||||
|
parser: MsgParser<'_, '_>,
|
||||||
|
) -> Result<(), ZwlrDataControlManagerV1Error> {
|
||||||
|
let req: CreateDataSource = self.client.parse(self, parser)?;
|
||||||
|
let res = Rc::new(ZwlrDataControlSourceV1::new(
|
||||||
|
req.id,
|
||||||
|
&self.client,
|
||||||
|
self.version,
|
||||||
|
));
|
||||||
|
track!(self.client, res);
|
||||||
|
self.client.add_client_obj(&res)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_data_device(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
parser: MsgParser<'_, '_>,
|
||||||
|
) -> Result<(), ZwlrDataControlManagerV1Error> {
|
||||||
|
let req: GetDataDevice = self.client.parse(&**self, parser)?;
|
||||||
|
let seat = self.client.lookup(req.seat)?;
|
||||||
|
let dev = Rc::new(ZwlrDataControlDeviceV1::new(
|
||||||
|
req.id,
|
||||||
|
&self.client,
|
||||||
|
self.version,
|
||||||
|
&seat.global,
|
||||||
|
));
|
||||||
|
track!(self.client, dev);
|
||||||
|
seat.global.add_wlr_device(&dev);
|
||||||
|
self.client.add_client_obj(&dev)?;
|
||||||
|
match seat.global.get_selection() {
|
||||||
|
Some(s) => s.offer_to_wlr_device(&dev),
|
||||||
|
_ => dev.send_selection(None),
|
||||||
|
}
|
||||||
|
if self.version >= PRIMARY_SELECTION_SINCE {
|
||||||
|
match seat.global.get_primary_selection() {
|
||||||
|
Some(s) => s.offer_to_wlr_device(&dev),
|
||||||
|
_ => dev.send_primary_selection(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrDataControlManagerV1Error> {
|
||||||
|
let _req: Destroy = self.client.parse(self, parser)?;
|
||||||
|
self.client.remove_obj(self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global_base!(
|
||||||
|
ZwlrDataControlManagerV1Global,
|
||||||
|
ZwlrDataControlManagerV1,
|
||||||
|
ZwlrDataControlManagerV1Error
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Global for ZwlrDataControlManagerV1Global {
|
||||||
|
fn singleton(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn version(&self) -> u32 {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn secure(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
simple_add_global!(ZwlrDataControlManagerV1Global);
|
||||||
|
|
||||||
|
object_base! {
|
||||||
|
self = ZwlrDataControlManagerV1;
|
||||||
|
|
||||||
|
CREATE_DATA_SOURCE => create_data_source,
|
||||||
|
GET_DATA_DEVICE => get_data_device,
|
||||||
|
DESTROY => destroy,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object for ZwlrDataControlManagerV1 {}
|
||||||
|
|
||||||
|
simple_add_obj!(ZwlrDataControlManagerV1);
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ZwlrDataControlManagerV1Error {
|
||||||
|
#[error(transparent)]
|
||||||
|
ClientError(Box<ClientError>),
|
||||||
|
#[error("Parsing failed")]
|
||||||
|
MsgParserError(#[source] Box<MsgParserError>),
|
||||||
|
}
|
||||||
|
efrom!(ZwlrDataControlManagerV1Error, ClientError);
|
||||||
|
efrom!(ZwlrDataControlManagerV1Error, MsgParserError);
|
||||||
135
src/ifs/ipc/zwlr_data_control_offer_v1.rs
Normal file
135
src/ifs/ipc/zwlr_data_control_offer_v1.rs
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
client::{Client, ClientError, ClientId},
|
||||||
|
ifs::{
|
||||||
|
ipc::{
|
||||||
|
break_offer_loops, cancel_offer, destroy_data_offer, receive_data_offer,
|
||||||
|
zwlr_data_control_device_v1::{
|
||||||
|
WlrClipboardIpc, WlrPrimarySelectionIpc, ZwlrDataControlDeviceV1,
|
||||||
|
},
|
||||||
|
DataOffer, DataOfferId, DynDataOffer, IpcLocation, OfferData,
|
||||||
|
},
|
||||||
|
wl_seat::WlSeatGlobal,
|
||||||
|
},
|
||||||
|
leaks::Tracker,
|
||||||
|
object::Object,
|
||||||
|
utils::buffd::{MsgParser, MsgParserError},
|
||||||
|
wire::{zwlr_data_control_offer_v1::*, ZwlrDataControlOfferV1Id},
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct ZwlrDataControlOfferV1 {
|
||||||
|
pub id: ZwlrDataControlOfferV1Id,
|
||||||
|
pub offer_id: DataOfferId,
|
||||||
|
pub client: Rc<Client>,
|
||||||
|
pub device: Rc<ZwlrDataControlDeviceV1>,
|
||||||
|
pub data: OfferData<ZwlrDataControlDeviceV1>,
|
||||||
|
pub location: IpcLocation,
|
||||||
|
pub tracker: Tracker<Self>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataOffer for ZwlrDataControlOfferV1 {
|
||||||
|
type Device = ZwlrDataControlDeviceV1;
|
||||||
|
|
||||||
|
fn offer_data(&self) -> &OfferData<ZwlrDataControlDeviceV1> {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynDataOffer for ZwlrDataControlOfferV1 {
|
||||||
|
fn offer_id(&self) -> DataOfferId {
|
||||||
|
self.offer_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client_id(&self) -> ClientId {
|
||||||
|
self.client.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_offer(&self, mime_type: &str) {
|
||||||
|
ZwlrDataControlOfferV1::send_offer(self, mime_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(&self) {
|
||||||
|
match self.location {
|
||||||
|
IpcLocation::Clipboard => destroy_data_offer::<WlrClipboardIpc>(self),
|
||||||
|
IpcLocation::PrimarySelection => destroy_data_offer::<WlrPrimarySelectionIpc>(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel(&self) {
|
||||||
|
match self.location {
|
||||||
|
IpcLocation::Clipboard => cancel_offer::<WlrClipboardIpc>(self),
|
||||||
|
IpcLocation::PrimarySelection => cancel_offer::<WlrPrimarySelectionIpc>(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_seat(&self) -> Rc<WlSeatGlobal> {
|
||||||
|
self.device.seat.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_privileged(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZwlrDataControlOfferV1 {
|
||||||
|
pub fn send_offer(&self, mime_type: &str) {
|
||||||
|
self.client.event(Offer {
|
||||||
|
self_id: self.id,
|
||||||
|
mime_type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrDataControlOfferV1Error> {
|
||||||
|
let req: Receive = self.client.parse(self, parser)?;
|
||||||
|
match self.location {
|
||||||
|
IpcLocation::Clipboard => {
|
||||||
|
receive_data_offer::<WlrClipboardIpc>(self, req.mime_type, req.fd)
|
||||||
|
}
|
||||||
|
IpcLocation::PrimarySelection => {
|
||||||
|
receive_data_offer::<WlrPrimarySelectionIpc>(self, req.mime_type, req.fd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrDataControlOfferV1Error> {
|
||||||
|
let _req: Destroy = self.client.parse(self, parser)?;
|
||||||
|
match self.location {
|
||||||
|
IpcLocation::Clipboard => destroy_data_offer::<WlrClipboardIpc>(self),
|
||||||
|
IpcLocation::PrimarySelection => destroy_data_offer::<WlrPrimarySelectionIpc>(self),
|
||||||
|
}
|
||||||
|
self.client.remove_obj(self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object_base! {
|
||||||
|
self = ZwlrDataControlOfferV1;
|
||||||
|
|
||||||
|
RECEIVE => receive,
|
||||||
|
DESTROY => destroy,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object for ZwlrDataControlOfferV1 {
|
||||||
|
fn break_loops(&self) {
|
||||||
|
match self.location {
|
||||||
|
IpcLocation::Clipboard => break_offer_loops::<WlrClipboardIpc>(self),
|
||||||
|
IpcLocation::PrimarySelection => break_offer_loops::<WlrPrimarySelectionIpc>(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
simple_add_obj!(ZwlrDataControlOfferV1);
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ZwlrDataControlOfferV1Error {
|
||||||
|
#[error(transparent)]
|
||||||
|
ClientError(Box<ClientError>),
|
||||||
|
#[error("Parsing failed")]
|
||||||
|
MsgParserError(#[source] Box<MsgParserError>),
|
||||||
|
}
|
||||||
|
efrom!(ZwlrDataControlOfferV1Error, ClientError);
|
||||||
|
efrom!(ZwlrDataControlOfferV1Error, MsgParserError);
|
||||||
169
src/ifs/ipc/zwlr_data_control_source_v1.rs
Normal file
169
src/ifs/ipc/zwlr_data_control_source_v1.rs
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
client::{Client, ClientError},
|
||||||
|
ifs::{
|
||||||
|
ipc::{
|
||||||
|
add_data_source_mime_type, break_source_loops, cancel_offers, destroy_data_source,
|
||||||
|
detach_seat, offer_source_to_regular_client, offer_source_to_wlr_device,
|
||||||
|
offer_source_to_x,
|
||||||
|
wl_data_device::ClipboardIpc,
|
||||||
|
x_data_device::{XClipboardIpc, XIpcDevice, XPrimarySelectionIpc},
|
||||||
|
zwlr_data_control_device_v1::{
|
||||||
|
WlrClipboardIpc, WlrPrimarySelectionIpc, ZwlrDataControlDeviceV1,
|
||||||
|
},
|
||||||
|
zwp_primary_selection_device_v1::PrimarySelectionIpc,
|
||||||
|
DataSource, DynDataSource, IpcLocation, SourceData,
|
||||||
|
},
|
||||||
|
wl_seat::WlSeatGlobal,
|
||||||
|
},
|
||||||
|
leaks::Tracker,
|
||||||
|
object::Object,
|
||||||
|
utils::buffd::{MsgParser, MsgParserError},
|
||||||
|
wire::{zwlr_data_control_source_v1::*, ZwlrDataControlSourceV1Id},
|
||||||
|
},
|
||||||
|
std::{cell::Cell, rc::Rc},
|
||||||
|
thiserror::Error,
|
||||||
|
uapi::OwnedFd,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct ZwlrDataControlSourceV1 {
|
||||||
|
pub id: ZwlrDataControlSourceV1Id,
|
||||||
|
pub data: SourceData,
|
||||||
|
pub version: u32,
|
||||||
|
pub location: Cell<IpcLocation>,
|
||||||
|
pub used: Cell<bool>,
|
||||||
|
pub tracker: Tracker<Self>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataSource for ZwlrDataControlSourceV1 {
|
||||||
|
fn send_cancelled(&self, _seat: &Rc<WlSeatGlobal>) {
|
||||||
|
ZwlrDataControlSourceV1::send_cancelled(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynDataSource for ZwlrDataControlSourceV1 {
|
||||||
|
fn source_data(&self) -> &SourceData {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_send(&self, mime_type: &str, fd: Rc<OwnedFd>) {
|
||||||
|
ZwlrDataControlSourceV1::send_send(&self, mime_type, fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offer_to_regular_client(self: Rc<Self>, client: &Rc<Client>) {
|
||||||
|
match self.location.get() {
|
||||||
|
IpcLocation::Clipboard => {
|
||||||
|
offer_source_to_regular_client::<ClipboardIpc, Self>(&self, client)
|
||||||
|
}
|
||||||
|
IpcLocation::PrimarySelection => {
|
||||||
|
offer_source_to_regular_client::<PrimarySelectionIpc, Self>(&self, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offer_to_x(self: Rc<Self>, dd: &Rc<XIpcDevice>) {
|
||||||
|
match self.location.get() {
|
||||||
|
IpcLocation::Clipboard => offer_source_to_x::<XClipboardIpc, Self>(&self, dd),
|
||||||
|
IpcLocation::PrimarySelection => {
|
||||||
|
offer_source_to_x::<XPrimarySelectionIpc, Self>(&self, dd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offer_to_wlr_device(self: Rc<Self>, dd: &Rc<ZwlrDataControlDeviceV1>) {
|
||||||
|
match self.location.get() {
|
||||||
|
IpcLocation::Clipboard => {
|
||||||
|
offer_source_to_wlr_device::<WlrClipboardIpc, Self>(&self, dd)
|
||||||
|
}
|
||||||
|
IpcLocation::PrimarySelection => {
|
||||||
|
offer_source_to_wlr_device::<WlrPrimarySelectionIpc, Self>(&self, dd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detach_seat(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
|
detach_seat(self, seat)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel_unprivileged_offers(&self) {
|
||||||
|
cancel_offers(self, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZwlrDataControlSourceV1 {
|
||||||
|
pub fn new(id: ZwlrDataControlSourceV1Id, client: &Rc<Client>, version: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
tracker: Default::default(),
|
||||||
|
data: SourceData::new(client),
|
||||||
|
version,
|
||||||
|
location: Cell::new(IpcLocation::Clipboard),
|
||||||
|
used: Cell::new(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_send(&self, mime_type: &str, fd: Rc<OwnedFd>) {
|
||||||
|
self.data.client.event(Send {
|
||||||
|
self_id: self.id,
|
||||||
|
mime_type,
|
||||||
|
fd,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_cancelled(&self) {
|
||||||
|
self.data.client.event(Cancelled { self_id: self.id })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offer(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrDataControlSourceV1Error> {
|
||||||
|
let req: Offer = self.data.client.parse(self, parser)?;
|
||||||
|
if self.used.get() {
|
||||||
|
return Err(ZwlrDataControlSourceV1Error::AlreadyUsed);
|
||||||
|
}
|
||||||
|
add_data_source_mime_type::<WlrClipboardIpc>(self, req.mime_type);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrDataControlSourceV1Error> {
|
||||||
|
let _req: Destroy = self.data.client.parse(self, parser)?;
|
||||||
|
match self.location.get() {
|
||||||
|
IpcLocation::Clipboard => destroy_data_source::<WlrClipboardIpc>(self),
|
||||||
|
IpcLocation::PrimarySelection => destroy_data_source::<WlrPrimarySelectionIpc>(self),
|
||||||
|
}
|
||||||
|
self.data.client.remove_obj(self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object_base! {
|
||||||
|
self = ZwlrDataControlSourceV1;
|
||||||
|
|
||||||
|
OFFER => offer,
|
||||||
|
DESTROY => destroy,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object for ZwlrDataControlSourceV1 {
|
||||||
|
fn break_loops(&self) {
|
||||||
|
match self.location.get() {
|
||||||
|
IpcLocation::Clipboard => break_source_loops::<WlrClipboardIpc>(self),
|
||||||
|
IpcLocation::PrimarySelection => break_source_loops::<WlrPrimarySelectionIpc>(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dedicated_add_obj!(
|
||||||
|
ZwlrDataControlSourceV1,
|
||||||
|
ZwlrDataControlSourceV1Id,
|
||||||
|
zwlr_data_sources
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ZwlrDataControlSourceV1Error {
|
||||||
|
#[error("Parsing failed")]
|
||||||
|
MsgParserError(#[source] Box<MsgParserError>),
|
||||||
|
#[error(transparent)]
|
||||||
|
ClientError(Box<ClientError>),
|
||||||
|
#[error("The source has already been used")]
|
||||||
|
AlreadyUsed,
|
||||||
|
}
|
||||||
|
efrom!(ZwlrDataControlSourceV1Error, ClientError);
|
||||||
|
efrom!(ZwlrDataControlSourceV1Error, MsgParserError);
|
||||||
|
|
@ -55,11 +55,7 @@ impl ZwpPrimarySelectionDeviceManagerV1 {
|
||||||
parser: MsgParser<'_, '_>,
|
parser: MsgParser<'_, '_>,
|
||||||
) -> Result<(), ZwpPrimarySelectionDeviceManagerV1Error> {
|
) -> Result<(), ZwpPrimarySelectionDeviceManagerV1Error> {
|
||||||
let req: CreateSource = self.client.parse(self, parser)?;
|
let req: CreateSource = self.client.parse(self, parser)?;
|
||||||
let res = Rc::new(ZwpPrimarySelectionSourceV1::new(
|
let res = Rc::new(ZwpPrimarySelectionSourceV1::new(req.id, &self.client));
|
||||||
req.id,
|
|
||||||
&self.client,
|
|
||||||
false,
|
|
||||||
));
|
|
||||||
track!(self.client, res);
|
track!(self.client, res);
|
||||||
self.client.add_client_obj(&res)?;
|
self.client.add_client_obj(&res)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -76,7 +72,6 @@ impl ZwpPrimarySelectionDeviceManagerV1 {
|
||||||
&self.client,
|
&self.client,
|
||||||
self.version,
|
self.version,
|
||||||
&seat.global,
|
&seat.global,
|
||||||
false,
|
|
||||||
));
|
));
|
||||||
track!(self.client, dev);
|
track!(self.client, dev);
|
||||||
seat.global.add_primary_selection_device(&dev);
|
seat.global.add_primary_selection_device(&dev);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use {
|
||||||
break_device_loops, destroy_data_device,
|
break_device_loops, destroy_data_device,
|
||||||
zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1,
|
zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1,
|
||||||
zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, DeviceData,
|
zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, DeviceData,
|
||||||
IpcVtable, OfferData, Role, SourceData,
|
IpcLocation, IpcVtable, IterableIpcVtable, OfferData, Role,
|
||||||
},
|
},
|
||||||
wl_seat::{WlSeatError, WlSeatGlobal},
|
wl_seat::{WlSeatError, WlSeatGlobal},
|
||||||
},
|
},
|
||||||
|
|
@ -15,13 +15,11 @@ use {
|
||||||
utils::buffd::{MsgParser, MsgParserError},
|
utils::buffd::{MsgParser, MsgParserError},
|
||||||
wire::{
|
wire::{
|
||||||
zwp_primary_selection_device_v1::*, ZwpPrimarySelectionDeviceV1Id,
|
zwp_primary_selection_device_v1::*, ZwpPrimarySelectionDeviceV1Id,
|
||||||
ZwpPrimarySelectionOfferV1Id, ZwpPrimarySelectionSourceV1Id,
|
ZwpPrimarySelectionOfferV1Id,
|
||||||
},
|
},
|
||||||
xwayland::XWaylandEvent,
|
|
||||||
},
|
},
|
||||||
std::rc::Rc,
|
std::rc::Rc,
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
uapi::OwnedFd,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ZwpPrimarySelectionDeviceV1 {
|
pub struct ZwpPrimarySelectionDeviceV1 {
|
||||||
|
|
@ -29,7 +27,7 @@ pub struct ZwpPrimarySelectionDeviceV1 {
|
||||||
pub client: Rc<Client>,
|
pub client: Rc<Client>,
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
pub seat: Rc<WlSeatGlobal>,
|
pub seat: Rc<WlSeatGlobal>,
|
||||||
data: DeviceData<PrimarySelectionIpc>,
|
data: DeviceData<ZwpPrimarySelectionOfferV1>,
|
||||||
pub tracker: Tracker<Self>,
|
pub tracker: Tracker<Self>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,56 +37,32 @@ impl ZwpPrimarySelectionDeviceV1 {
|
||||||
client: &Rc<Client>,
|
client: &Rc<Client>,
|
||||||
version: u32,
|
version: u32,
|
||||||
seat: &Rc<WlSeatGlobal>,
|
seat: &Rc<WlSeatGlobal>,
|
||||||
is_xwm: bool,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
client: client.clone(),
|
client: client.clone(),
|
||||||
version,
|
version,
|
||||||
seat: seat.clone(),
|
seat: seat.clone(),
|
||||||
data: DeviceData {
|
data: Default::default(),
|
||||||
selection: Default::default(),
|
|
||||||
dnd: Default::default(),
|
|
||||||
is_xwm,
|
|
||||||
},
|
|
||||||
tracker: Default::default(),
|
tracker: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_data_offer(&self, offer: &Rc<ZwpPrimarySelectionOfferV1>) {
|
pub fn send_data_offer(&self, offer: &Rc<ZwpPrimarySelectionOfferV1>) {
|
||||||
if self.data.is_xwm {
|
self.client.event(DataOffer {
|
||||||
self.client
|
self_id: self.id,
|
||||||
.state
|
offer: offer.id,
|
||||||
.xwayland
|
})
|
||||||
.queue
|
|
||||||
.push(XWaylandEvent::PrimarySelectionSetOffer(offer.clone()));
|
|
||||||
} else {
|
|
||||||
self.client.event(DataOffer {
|
|
||||||
self_id: self.id,
|
|
||||||
offer: offer.id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_selection(&self, offer: Option<&Rc<ZwpPrimarySelectionOfferV1>>) {
|
pub fn send_selection(&self, offer: Option<&Rc<ZwpPrimarySelectionOfferV1>>) {
|
||||||
if self.data.is_xwm {
|
let id = offer
|
||||||
self.client
|
.map(|o| o.id)
|
||||||
.state
|
.unwrap_or(ZwpPrimarySelectionOfferV1Id::NONE);
|
||||||
.xwayland
|
self.client.event(Selection {
|
||||||
.queue
|
self_id: self.id,
|
||||||
.push(XWaylandEvent::PrimarySelectionSetSelection(
|
id,
|
||||||
self.seat.id(),
|
})
|
||||||
offer.cloned(),
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
let id = offer
|
|
||||||
.map(|o| o.id)
|
|
||||||
.unwrap_or(ZwpPrimarySelectionOfferV1Id::NONE);
|
|
||||||
self.client.event(Selection {
|
|
||||||
self_id: self.id,
|
|
||||||
id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_selection(
|
fn set_selection(
|
||||||
|
|
@ -112,7 +86,7 @@ impl ZwpPrimarySelectionDeviceV1 {
|
||||||
} else {
|
} else {
|
||||||
Some(self.client.lookup(req.source)?)
|
Some(self.client.lookup(req.source)?)
|
||||||
};
|
};
|
||||||
self.seat.set_primary_selection(src, Some(req.serial))?;
|
self.seat.set_zwp_primary_selection(src, Some(req.serial))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,12 +101,23 @@ impl ZwpPrimarySelectionDeviceV1 {
|
||||||
|
|
||||||
pub struct PrimarySelectionIpc;
|
pub struct PrimarySelectionIpc;
|
||||||
|
|
||||||
|
impl IterableIpcVtable for PrimarySelectionIpc {
|
||||||
|
fn for_each_device<C>(seat: &WlSeatGlobal, client: ClientId, f: C)
|
||||||
|
where
|
||||||
|
C: FnMut(&Rc<Self::Device>),
|
||||||
|
{
|
||||||
|
seat.for_each_primary_selection_device(0, client, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IpcVtable for PrimarySelectionIpc {
|
impl IpcVtable for PrimarySelectionIpc {
|
||||||
|
const LOCATION: IpcLocation = IpcLocation::PrimarySelection;
|
||||||
|
|
||||||
type Device = ZwpPrimarySelectionDeviceV1;
|
type Device = ZwpPrimarySelectionDeviceV1;
|
||||||
type Source = ZwpPrimarySelectionSourceV1;
|
type Source = ZwpPrimarySelectionSourceV1;
|
||||||
type Offer = ZwpPrimarySelectionOfferV1;
|
type Offer = ZwpPrimarySelectionOfferV1;
|
||||||
|
|
||||||
fn get_device_data(dd: &Self::Device) -> &DeviceData<Self> {
|
fn get_device_data(dd: &Self::Device) -> &DeviceData<Self::Offer> {
|
||||||
&dd.data
|
&dd.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,52 +125,28 @@ impl IpcVtable for PrimarySelectionIpc {
|
||||||
dd.seat.clone()
|
dd.seat.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_xwm_source(client: &Rc<Client>) -> Self::Source {
|
|
||||||
ZwpPrimarySelectionSourceV1::new(ZwpPrimarySelectionSourceV1Id::NONE, client, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_seat_selection(
|
fn set_seat_selection(
|
||||||
seat: &Rc<WlSeatGlobal>,
|
seat: &Rc<WlSeatGlobal>,
|
||||||
source: &Rc<Self::Source>,
|
source: &Rc<Self::Source>,
|
||||||
serial: Option<u32>,
|
serial: Option<u32>,
|
||||||
) -> Result<(), WlSeatError> {
|
) -> Result<(), WlSeatError> {
|
||||||
seat.set_primary_selection(Some(source.clone()), serial)
|
seat.set_zwp_primary_selection(Some(source.clone()), serial)
|
||||||
}
|
|
||||||
|
|
||||||
fn get_offer_data(offer: &Self::Offer) -> &OfferData<Self> {
|
|
||||||
&offer.data
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_source_data(src: &Self::Source) -> &SourceData<Self> {
|
|
||||||
&src.data
|
|
||||||
}
|
|
||||||
|
|
||||||
fn for_each_device<C>(seat: &WlSeatGlobal, client: ClientId, f: C)
|
|
||||||
where
|
|
||||||
C: FnMut(&Rc<Self::Device>),
|
|
||||||
{
|
|
||||||
seat.for_each_primary_selection_device(0, client, f)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_offer(
|
fn create_offer(
|
||||||
client: &Rc<Client>,
|
|
||||||
device: &Rc<ZwpPrimarySelectionDeviceV1>,
|
device: &Rc<ZwpPrimarySelectionDeviceV1>,
|
||||||
offer_data: OfferData<Self>,
|
offer_data: OfferData<Self::Device>,
|
||||||
) -> Result<Rc<Self::Offer>, ClientError> {
|
) -> Result<Rc<Self::Offer>, ClientError> {
|
||||||
let id = if device.data.is_xwm {
|
|
||||||
ZwpPrimarySelectionOfferV1Id::NONE
|
|
||||||
} else {
|
|
||||||
client.new_id()?
|
|
||||||
};
|
|
||||||
let rc = Rc::new(ZwpPrimarySelectionOfferV1 {
|
let rc = Rc::new(ZwpPrimarySelectionOfferV1 {
|
||||||
id,
|
id: device.client.new_id()?,
|
||||||
u64_id: client.state.data_offer_ids.fetch_add(1),
|
offer_id: device.client.state.data_offer_ids.next(),
|
||||||
seat: device.seat.clone(),
|
seat: device.seat.clone(),
|
||||||
client: client.clone(),
|
client: device.client.clone(),
|
||||||
data: offer_data,
|
data: offer_data,
|
||||||
tracker: Default::default(),
|
tracker: Default::default(),
|
||||||
});
|
});
|
||||||
track!(client, rc);
|
track!(device.client, rc);
|
||||||
|
device.client.add_server_obj(&rc);
|
||||||
Ok(rc)
|
Ok(rc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,40 +154,16 @@ impl IpcVtable for PrimarySelectionIpc {
|
||||||
dd.send_selection(offer);
|
dd.send_selection(offer);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_cancelled(source: &Rc<Self::Source>, _seat: &Rc<WlSeatGlobal>) {
|
|
||||||
source.send_cancelled();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_offer_id(offer: &Self::Offer) -> u64 {
|
|
||||||
offer.u64_id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_offer(dd: &Self::Device, offer: &Rc<Self::Offer>) {
|
fn send_offer(dd: &Self::Device, offer: &Rc<Self::Offer>) {
|
||||||
dd.send_data_offer(offer);
|
dd.send_data_offer(offer);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_mime_type(offer: &Rc<Self::Offer>, mime_type: &str) {
|
|
||||||
offer.send_offer(mime_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unset(seat: &Rc<WlSeatGlobal>, _role: Role) {
|
fn unset(seat: &Rc<WlSeatGlobal>, _role: Role) {
|
||||||
seat.unset_primary_selection();
|
seat.unset_primary_selection();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_send(src: &Rc<Self::Source>, mime_type: &str, fd: Rc<OwnedFd>) {
|
fn device_client(dd: &Rc<Self::Device>) -> &Rc<Client> {
|
||||||
src.send_send(mime_type, fd);
|
&dd.client
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_from_seat(device: &Self::Device) {
|
|
||||||
device.seat.remove_primary_selection_device(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_offer_seat(offer: &Self::Offer) -> Rc<WlSeatGlobal> {
|
|
||||||
offer.seat.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn source_eq(left: &Self::Source, right: &Self::Source) -> bool {
|
|
||||||
left as *const _ == right as *const _
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
client::{Client, ClientError},
|
client::{Client, ClientError, ClientId},
|
||||||
ifs::{
|
ifs::{
|
||||||
ipc::{
|
ipc::{
|
||||||
break_offer_loops, destroy_data_offer, receive_data_offer,
|
break_offer_loops, cancel_offer, destroy_data_offer, receive_data_offer,
|
||||||
zwp_primary_selection_device_v1::PrimarySelectionIpc, OfferData,
|
zwp_primary_selection_device_v1::{
|
||||||
|
PrimarySelectionIpc, ZwpPrimarySelectionDeviceV1,
|
||||||
|
},
|
||||||
|
DataOffer, DataOfferId, DynDataOffer, OfferData,
|
||||||
},
|
},
|
||||||
wl_seat::WlSeatGlobal,
|
wl_seat::WlSeatGlobal,
|
||||||
},
|
},
|
||||||
|
|
@ -12,7 +15,6 @@ use {
|
||||||
object::Object,
|
object::Object,
|
||||||
utils::buffd::{MsgParser, MsgParserError},
|
utils::buffd::{MsgParser, MsgParserError},
|
||||||
wire::{zwp_primary_selection_offer_v1::*, ZwpPrimarySelectionOfferV1Id},
|
wire::{zwp_primary_selection_offer_v1::*, ZwpPrimarySelectionOfferV1Id},
|
||||||
xwayland::XWaylandEvent,
|
|
||||||
},
|
},
|
||||||
std::rc::Rc,
|
std::rc::Rc,
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
|
|
@ -20,32 +22,53 @@ use {
|
||||||
|
|
||||||
pub struct ZwpPrimarySelectionOfferV1 {
|
pub struct ZwpPrimarySelectionOfferV1 {
|
||||||
pub id: ZwpPrimarySelectionOfferV1Id,
|
pub id: ZwpPrimarySelectionOfferV1Id,
|
||||||
pub u64_id: u64,
|
pub offer_id: DataOfferId,
|
||||||
pub seat: Rc<WlSeatGlobal>,
|
pub seat: Rc<WlSeatGlobal>,
|
||||||
pub client: Rc<Client>,
|
pub client: Rc<Client>,
|
||||||
pub data: OfferData<PrimarySelectionIpc>,
|
pub data: OfferData<ZwpPrimarySelectionDeviceV1>,
|
||||||
pub tracker: Tracker<Self>,
|
pub tracker: Tracker<Self>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DataOffer for ZwpPrimarySelectionOfferV1 {
|
||||||
|
type Device = ZwpPrimarySelectionDeviceV1;
|
||||||
|
|
||||||
|
fn offer_data(&self) -> &OfferData<Self::Device> {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynDataOffer for ZwpPrimarySelectionOfferV1 {
|
||||||
|
fn offer_id(&self) -> DataOfferId {
|
||||||
|
self.offer_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client_id(&self) -> ClientId {
|
||||||
|
self.client.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_offer(&self, mime_type: &str) {
|
||||||
|
ZwpPrimarySelectionOfferV1::send_offer(self, mime_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(&self) {
|
||||||
|
destroy_data_offer::<PrimarySelectionIpc>(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel(&self) {
|
||||||
|
cancel_offer::<PrimarySelectionIpc>(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_seat(&self) -> Rc<WlSeatGlobal> {
|
||||||
|
self.seat.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ZwpPrimarySelectionOfferV1 {
|
impl ZwpPrimarySelectionOfferV1 {
|
||||||
pub fn send_offer(self: &Rc<Self>, mime_type: &str) {
|
pub fn send_offer(&self, mime_type: &str) {
|
||||||
if self.data.is_xwm {
|
self.client.event(Offer {
|
||||||
if let Some(src) = self.data.source.get() {
|
self_id: self.id,
|
||||||
if !src.data.is_xwm {
|
mime_type,
|
||||||
self.client.state.xwayland.queue.push(
|
})
|
||||||
XWaylandEvent::PrimarySelectionAddOfferMimeType(
|
|
||||||
self.clone(),
|
|
||||||
mime_type.to_string(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.client.event(Offer {
|
|
||||||
self_id: self.id,
|
|
||||||
mime_type,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn receive(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwpPrimarySelectionOfferV1Error> {
|
fn receive(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwpPrimarySelectionOfferV1Error> {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,22 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
client::{Client, ClientError},
|
client::{Client, ClientError},
|
||||||
ifs::ipc::{
|
ifs::{
|
||||||
add_data_source_mime_type, break_source_loops, destroy_data_source,
|
ipc::{
|
||||||
zwp_primary_selection_device_v1::PrimarySelectionIpc, SourceData,
|
add_data_source_mime_type, break_source_loops, cancel_offers, destroy_data_source,
|
||||||
|
detach_seat, offer_source_to_regular_client, offer_source_to_wlr_device,
|
||||||
|
offer_source_to_x,
|
||||||
|
x_data_device::{XIpcDevice, XPrimarySelectionIpc},
|
||||||
|
zwlr_data_control_device_v1::{WlrPrimarySelectionIpc, ZwlrDataControlDeviceV1},
|
||||||
|
zwp_primary_selection_device_v1::PrimarySelectionIpc,
|
||||||
|
DataSource, DynDataSource, SourceData,
|
||||||
|
},
|
||||||
|
wl_seat::WlSeatGlobal,
|
||||||
},
|
},
|
||||||
leaks::Tracker,
|
leaks::Tracker,
|
||||||
object::Object,
|
object::Object,
|
||||||
utils::buffd::{MsgParser, MsgParserError},
|
utils::buffd::{MsgParser, MsgParserError},
|
||||||
wire::{zwp_primary_selection_source_v1::*, ZwpPrimarySelectionSourceV1Id},
|
wire::{zwp_primary_selection_source_v1::*, ZwpPrimarySelectionSourceV1Id},
|
||||||
xwayland::XWaylandEvent,
|
|
||||||
},
|
},
|
||||||
std::rc::Rc,
|
std::rc::Rc,
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
|
|
@ -18,51 +25,65 @@ use {
|
||||||
|
|
||||||
pub struct ZwpPrimarySelectionSourceV1 {
|
pub struct ZwpPrimarySelectionSourceV1 {
|
||||||
pub id: ZwpPrimarySelectionSourceV1Id,
|
pub id: ZwpPrimarySelectionSourceV1Id,
|
||||||
pub data: SourceData<PrimarySelectionIpc>,
|
pub data: SourceData,
|
||||||
pub tracker: Tracker<Self>,
|
pub tracker: Tracker<Self>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DataSource for ZwpPrimarySelectionSourceV1 {
|
||||||
|
fn send_cancelled(&self, _seat: &Rc<WlSeatGlobal>) {
|
||||||
|
ZwpPrimarySelectionSourceV1::send_cancelled(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynDataSource for ZwpPrimarySelectionSourceV1 {
|
||||||
|
fn source_data(&self) -> &SourceData {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_send(&self, mime_type: &str, fd: Rc<OwnedFd>) {
|
||||||
|
ZwpPrimarySelectionSourceV1::send_send(self, mime_type, fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offer_to_regular_client(self: Rc<Self>, client: &Rc<Client>) {
|
||||||
|
offer_source_to_regular_client::<PrimarySelectionIpc, Self>(&self, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offer_to_x(self: Rc<Self>, dd: &Rc<XIpcDevice>) {
|
||||||
|
offer_source_to_x::<XPrimarySelectionIpc, Self>(&self, dd);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offer_to_wlr_device(self: Rc<Self>, dd: &Rc<ZwlrDataControlDeviceV1>) {
|
||||||
|
offer_source_to_wlr_device::<WlrPrimarySelectionIpc, Self>(&self, dd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detach_seat(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
|
detach_seat(self, seat);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel_unprivileged_offers(&self) {
|
||||||
|
cancel_offers(self, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ZwpPrimarySelectionSourceV1 {
|
impl ZwpPrimarySelectionSourceV1 {
|
||||||
pub fn new(id: ZwpPrimarySelectionSourceV1Id, client: &Rc<Client>, is_xwm: bool) -> Self {
|
pub fn new(id: ZwpPrimarySelectionSourceV1Id, client: &Rc<Client>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
data: SourceData::new(client, is_xwm),
|
data: SourceData::new(client),
|
||||||
tracker: Default::default(),
|
tracker: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_cancelled(self: &Rc<Self>) {
|
pub fn send_cancelled(&self) {
|
||||||
if self.data.is_xwm {
|
self.data.client.event(Cancelled { self_id: self.id });
|
||||||
self.data
|
|
||||||
.client
|
|
||||||
.state
|
|
||||||
.xwayland
|
|
||||||
.queue
|
|
||||||
.push(XWaylandEvent::PrimarySelectionCancelSource(self.clone()));
|
|
||||||
} else {
|
|
||||||
self.data.client.event(Cancelled { self_id: self.id });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_send(self: &Rc<Self>, mime_type: &str, fd: Rc<OwnedFd>) {
|
pub fn send_send(&self, mime_type: &str, fd: Rc<OwnedFd>) {
|
||||||
if self.data.is_xwm {
|
self.data.client.event(Send {
|
||||||
self.data
|
self_id: self.id,
|
||||||
.client
|
mime_type,
|
||||||
.state
|
fd,
|
||||||
.xwayland
|
})
|
||||||
.queue
|
|
||||||
.push(XWaylandEvent::PrimarySelectionSendSource(
|
|
||||||
self.clone(),
|
|
||||||
mime_type.to_string(),
|
|
||||||
fd,
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
self.data.client.event(Send {
|
|
||||||
self_id: self.id,
|
|
||||||
mime_type,
|
|
||||||
fd,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn offer(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwpPrimarySelectionSourceV1Error> {
|
fn offer(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwpPrimarySelectionSourceV1Error> {
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,15 @@ use {
|
||||||
self,
|
self,
|
||||||
wl_data_device::{ClipboardIpc, WlDataDevice},
|
wl_data_device::{ClipboardIpc, WlDataDevice},
|
||||||
wl_data_source::WlDataSource,
|
wl_data_source::WlDataSource,
|
||||||
|
x_data_device::{XClipboardIpc, XIpcDevice, XIpcDeviceId, XPrimarySelectionIpc},
|
||||||
|
zwlr_data_control_device_v1::{
|
||||||
|
WlrClipboardIpc, WlrPrimarySelectionIpc, ZwlrDataControlDeviceV1,
|
||||||
|
},
|
||||||
zwp_primary_selection_device_v1::{
|
zwp_primary_selection_device_v1::{
|
||||||
PrimarySelectionIpc, ZwpPrimarySelectionDeviceV1,
|
PrimarySelectionIpc, ZwpPrimarySelectionDeviceV1,
|
||||||
},
|
},
|
||||||
zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1,
|
zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1,
|
||||||
IpcError,
|
DynDataSource, IpcError,
|
||||||
},
|
},
|
||||||
wl_seat::{
|
wl_seat::{
|
||||||
kb_owner::KbOwnerHolder,
|
kb_owner::KbOwnerHolder,
|
||||||
|
|
@ -58,11 +62,13 @@ use {
|
||||||
linkedlist::LinkedNode,
|
linkedlist::LinkedNode,
|
||||||
numcell::NumCell,
|
numcell::NumCell,
|
||||||
rc_eq::rc_eq,
|
rc_eq::rc_eq,
|
||||||
|
smallmap::SmallMap,
|
||||||
transform_ext::TransformExt,
|
transform_ext::TransformExt,
|
||||||
},
|
},
|
||||||
wire::{
|
wire::{
|
||||||
wl_seat::*, ExtIdleNotificationV1Id, WlDataDeviceId, WlKeyboardId, WlPointerId,
|
wl_seat::*, ExtIdleNotificationV1Id, WlDataDeviceId, WlKeyboardId, WlPointerId,
|
||||||
WlSeatId, ZwpPrimarySelectionDeviceV1Id, ZwpRelativePointerV1Id,
|
WlSeatId, ZwlrDataControlDeviceV1Id, ZwpPrimarySelectionDeviceV1Id,
|
||||||
|
ZwpRelativePointerV1Id,
|
||||||
},
|
},
|
||||||
xkbcommon::{XkbKeymap, XkbState},
|
xkbcommon::{XkbKeymap, XkbState},
|
||||||
},
|
},
|
||||||
|
|
@ -108,7 +114,7 @@ pub struct DroppedDnd {
|
||||||
impl Drop for DroppedDnd {
|
impl Drop for DroppedDnd {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Some(src) = self.dnd.src.take() {
|
if let Some(src) = self.dnd.src.take() {
|
||||||
ipc::detach_seat::<ClipboardIpc>(&src, &self.dnd.seat);
|
ipc::detach_seat(&*src, &self.dnd.seat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -131,6 +137,7 @@ pub struct WlSeatGlobal {
|
||||||
keyboard_node: CloneCell<Rc<dyn Node>>,
|
keyboard_node: CloneCell<Rc<dyn Node>>,
|
||||||
pressed_keys: RefCell<AHashSet<u32>>,
|
pressed_keys: RefCell<AHashSet<u32>>,
|
||||||
bindings: RefCell<AHashMap<ClientId, AHashMap<WlSeatId, Rc<WlSeat>>>>,
|
bindings: RefCell<AHashMap<ClientId, AHashMap<WlSeatId, Rc<WlSeat>>>>,
|
||||||
|
x_data_devices: SmallMap<XIpcDeviceId, Rc<XIpcDevice>, 1>,
|
||||||
data_devices: RefCell<AHashMap<ClientId, AHashMap<WlDataDeviceId, Rc<WlDataDevice>>>>,
|
data_devices: RefCell<AHashMap<ClientId, AHashMap<WlDataDeviceId, Rc<WlDataDevice>>>>,
|
||||||
primary_selection_devices: RefCell<
|
primary_selection_devices: RefCell<
|
||||||
AHashMap<
|
AHashMap<
|
||||||
|
|
@ -138,14 +145,16 @@ pub struct WlSeatGlobal {
|
||||||
AHashMap<ZwpPrimarySelectionDeviceV1Id, Rc<ZwpPrimarySelectionDeviceV1>>,
|
AHashMap<ZwpPrimarySelectionDeviceV1Id, Rc<ZwpPrimarySelectionDeviceV1>>,
|
||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
|
wlr_data_devices:
|
||||||
|
CopyHashMap<(ClientId, ZwlrDataControlDeviceV1Id), Rc<ZwlrDataControlDeviceV1>>,
|
||||||
repeat_rate: Cell<(i32, i32)>,
|
repeat_rate: Cell<(i32, i32)>,
|
||||||
kb_map: CloneCell<Rc<XkbKeymap>>,
|
kb_map: CloneCell<Rc<XkbKeymap>>,
|
||||||
kb_state: RefCell<XkbState>,
|
kb_state: RefCell<XkbState>,
|
||||||
cursor: CloneCell<Option<Rc<dyn Cursor>>>,
|
cursor: CloneCell<Option<Rc<dyn Cursor>>>,
|
||||||
tree_changed: Rc<AsyncEvent>,
|
tree_changed: Rc<AsyncEvent>,
|
||||||
selection: CloneCell<Option<Rc<WlDataSource>>>,
|
selection: CloneCell<Option<Rc<dyn DynDataSource>>>,
|
||||||
selection_serial: Cell<u32>,
|
selection_serial: Cell<u32>,
|
||||||
primary_selection: CloneCell<Option<Rc<ZwpPrimarySelectionSourceV1>>>,
|
primary_selection: CloneCell<Option<Rc<dyn DynDataSource>>>,
|
||||||
primary_selection_serial: Cell<u32>,
|
primary_selection_serial: Cell<u32>,
|
||||||
pointer_owner: PointerOwnerHolder,
|
pointer_owner: PointerOwnerHolder,
|
||||||
kb_owner: KbOwnerHolder,
|
kb_owner: KbOwnerHolder,
|
||||||
|
|
@ -190,6 +199,7 @@ impl WlSeatGlobal {
|
||||||
keyboard_node: CloneCell::new(state.root.clone()),
|
keyboard_node: CloneCell::new(state.root.clone()),
|
||||||
pressed_keys: RefCell::new(Default::default()),
|
pressed_keys: RefCell::new(Default::default()),
|
||||||
bindings: Default::default(),
|
bindings: Default::default(),
|
||||||
|
x_data_devices: Default::default(),
|
||||||
data_devices: RefCell::new(Default::default()),
|
data_devices: RefCell::new(Default::default()),
|
||||||
primary_selection_devices: RefCell::new(Default::default()),
|
primary_selection_devices: RefCell::new(Default::default()),
|
||||||
repeat_rate: Cell::new((25, 250)),
|
repeat_rate: Cell::new((25, 250)),
|
||||||
|
|
@ -215,6 +225,7 @@ impl WlSeatGlobal {
|
||||||
constraint: Default::default(),
|
constraint: Default::default(),
|
||||||
idle_notifications: Default::default(),
|
idle_notifications: Default::default(),
|
||||||
last_input_usec: Cell::new(now_usec()),
|
last_input_usec: Cell::new(now_usec()),
|
||||||
|
wlr_data_devices: Default::default(),
|
||||||
});
|
});
|
||||||
state.add_cursor_size(*DEFAULT_CURSOR_SIZE);
|
state.add_cursor_size(*DEFAULT_CURSOR_SIZE);
|
||||||
let seat = slf.clone();
|
let seat = slf.clone();
|
||||||
|
|
@ -349,6 +360,20 @@ impl WlSeatGlobal {
|
||||||
.insert(device.id, device.clone());
|
.insert(device.id, device.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_x_data_device(&self, device: &Rc<XIpcDevice>) {
|
||||||
|
self.x_data_devices.insert(device.id, device.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unset_x_data_device(&self, id: XIpcDeviceId) {
|
||||||
|
self.x_data_devices.remove(&id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn for_each_x_data_device(&self, mut f: impl FnMut(&Rc<XIpcDevice>)) {
|
||||||
|
for (_, dev) in &self.x_data_devices {
|
||||||
|
f(&dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn remove_data_device(&self, device: &WlDataDevice) {
|
pub fn remove_data_device(&self, device: &WlDataDevice) {
|
||||||
let mut dd = self.data_devices.borrow_mut();
|
let mut dd = self.data_devices.borrow_mut();
|
||||||
if let Entry::Occupied(mut e) = dd.entry(device.client.id) {
|
if let Entry::Occupied(mut e) = dd.entry(device.client.id) {
|
||||||
|
|
@ -376,6 +401,15 @@ impl WlSeatGlobal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_wlr_device(&self, device: &Rc<ZwlrDataControlDeviceV1>) {
|
||||||
|
self.wlr_data_devices
|
||||||
|
.set((device.client.id, device.id), device.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_wlr_device(&self, device: &ZwlrDataControlDeviceV1) {
|
||||||
|
self.wlr_data_devices.remove(&(device.client.id, device.id));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_output(&self) -> Rc<OutputNode> {
|
pub fn get_output(&self) -> Rc<OutputNode> {
|
||||||
self.output.get()
|
self.output.get()
|
||||||
}
|
}
|
||||||
|
|
@ -709,32 +743,64 @@ impl WlSeatGlobal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_selection_<T: ipc::IpcVtable>(
|
fn set_selection_<T, X, W, S>(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
field: &CloneCell<Option<Rc<T::Source>>>,
|
field: &CloneCell<Option<Rc<dyn DynDataSource>>>,
|
||||||
src: Option<Rc<T::Source>>,
|
src: Option<Rc<S>>,
|
||||||
) -> Result<(), WlSeatError> {
|
) -> Result<(), WlSeatError>
|
||||||
|
where
|
||||||
|
T: ipc::IterableIpcVtable,
|
||||||
|
X: ipc::IpcVtable<Device = XIpcDevice>,
|
||||||
|
W: ipc::WlrIpcVtable,
|
||||||
|
S: DynDataSource,
|
||||||
|
{
|
||||||
if let (Some(new), Some(old)) = (&src, &field.get()) {
|
if let (Some(new), Some(old)) = (&src, &field.get()) {
|
||||||
if T::source_eq(old, new) {
|
if new.source_data().id == old.source_data().id {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(new) = &src {
|
if let Some(new) = &src {
|
||||||
ipc::attach_seat::<T>(new, self, ipc::Role::Selection)?;
|
ipc::attach_seat(&**new, self, ipc::Role::Selection)?;
|
||||||
}
|
}
|
||||||
if let Some(old) = field.set(src.clone()) {
|
let src_dyn = src.clone().map(|s| s as Rc<dyn DynDataSource>);
|
||||||
ipc::detach_seat::<T>(&old, self);
|
if let Some(old) = field.set(src_dyn) {
|
||||||
|
old.detach_seat(self);
|
||||||
}
|
}
|
||||||
if let Some(client) = self.keyboard_node.get().node_client() {
|
if let Some(client) = self.keyboard_node.get().node_client() {
|
||||||
match src {
|
self.offer_selection_to_client::<T, X>(src.clone().map(|v| v as Rc<_>), &client);
|
||||||
Some(src) => ipc::offer_source_to::<T>(&src, &client),
|
// client.flush();
|
||||||
|
}
|
||||||
|
W::for_each_device(self, |device| match &src {
|
||||||
|
Some(src) => src.clone().offer_to_wlr_device(device),
|
||||||
|
_ => W::send_selection(device, None),
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offer_selection_to_client<T, X>(
|
||||||
|
&self,
|
||||||
|
selection: Option<Rc<dyn DynDataSource>>,
|
||||||
|
client: &Rc<Client>,
|
||||||
|
) where
|
||||||
|
T: ipc::IterableIpcVtable,
|
||||||
|
X: ipc::IpcVtable<Device = XIpcDevice>,
|
||||||
|
{
|
||||||
|
if let Some(src) = &selection {
|
||||||
|
src.cancel_unprivileged_offers();
|
||||||
|
}
|
||||||
|
if client.is_xwayland {
|
||||||
|
self.for_each_x_data_device(|dd| match &selection {
|
||||||
|
Some(src) => src.clone().offer_to_x(&dd),
|
||||||
|
_ => X::send_selection(&dd, None),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
match selection {
|
||||||
|
Some(src) => src.offer_to_regular_client(client),
|
||||||
_ => T::for_each_device(self, client.id, |device| {
|
_ => T::for_each_device(self, client.id, |device| {
|
||||||
T::send_selection(device, None);
|
T::send_selection(device, None);
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
// client.flush();
|
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_drag(
|
pub fn start_drag(
|
||||||
|
|
@ -756,10 +822,10 @@ impl WlSeatGlobal {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unset_selection(self: &Rc<Self>) {
|
pub fn unset_selection(self: &Rc<Self>) {
|
||||||
let _ = self.set_selection(None, None);
|
let _ = self.set_wl_data_source_selection(None, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_selection(
|
pub fn set_wl_data_source_selection(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
selection: Option<Rc<WlDataSource>>,
|
selection: Option<Rc<WlDataSource>>,
|
||||||
serial: Option<u32>,
|
serial: Option<u32>,
|
||||||
|
|
@ -772,7 +838,21 @@ impl WlSeatGlobal {
|
||||||
return Err(WlSeatError::OfferHasDrag);
|
return Err(WlSeatError::OfferHasDrag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.set_selection_::<ClipboardIpc>(&self.selection, selection)
|
self.set_selection(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_selection<S: DynDataSource>(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
selection: Option<Rc<S>>,
|
||||||
|
) -> Result<(), WlSeatError> {
|
||||||
|
self.set_selection_::<ClipboardIpc, XClipboardIpc, WlrClipboardIpc, _>(
|
||||||
|
&self.selection,
|
||||||
|
selection,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_selection(&self) -> Option<Rc<dyn DynDataSource>> {
|
||||||
|
self.selection.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn may_modify_selection(&self, client: &Rc<Client>, serial: u32) -> bool {
|
pub fn may_modify_selection(&self, client: &Rc<Client>, serial: u32) -> bool {
|
||||||
|
|
@ -795,10 +875,10 @@ impl WlSeatGlobal {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unset_primary_selection(self: &Rc<Self>) {
|
pub fn unset_primary_selection(self: &Rc<Self>) {
|
||||||
let _ = self.set_primary_selection(None, None);
|
let _ = self.set_zwp_primary_selection(None, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_primary_selection(
|
pub fn set_zwp_primary_selection(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
selection: Option<Rc<ZwpPrimarySelectionSourceV1>>,
|
selection: Option<Rc<ZwpPrimarySelectionSourceV1>>,
|
||||||
serial: Option<u32>,
|
serial: Option<u32>,
|
||||||
|
|
@ -806,7 +886,21 @@ impl WlSeatGlobal {
|
||||||
if let Some(serial) = serial {
|
if let Some(serial) = serial {
|
||||||
self.primary_selection_serial.set(serial);
|
self.primary_selection_serial.set(serial);
|
||||||
}
|
}
|
||||||
self.set_selection_::<PrimarySelectionIpc>(&self.primary_selection, selection)
|
self.set_primary_selection(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_primary_selection<S: DynDataSource>(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
selection: Option<Rc<S>>,
|
||||||
|
) -> Result<(), WlSeatError> {
|
||||||
|
self.set_selection_::<PrimarySelectionIpc, XPrimarySelectionIpc, WlrPrimarySelectionIpc, _>(
|
||||||
|
&self.primary_selection,
|
||||||
|
selection,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_primary_selection(&self) -> Option<Rc<dyn DynDataSource>> {
|
||||||
|
self.primary_selection.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reload_known_cursor(&self) {
|
pub fn reload_known_cursor(&self) {
|
||||||
|
|
@ -916,6 +1010,7 @@ impl WlSeatGlobal {
|
||||||
self.bindings.borrow_mut().clear();
|
self.bindings.borrow_mut().clear();
|
||||||
self.data_devices.borrow_mut().clear();
|
self.data_devices.borrow_mut().clear();
|
||||||
self.primary_selection_devices.borrow_mut().clear();
|
self.primary_selection_devices.borrow_mut().clear();
|
||||||
|
self.wlr_data_devices.clear();
|
||||||
self.cursor.set(None);
|
self.cursor.set(None);
|
||||||
self.selection.set(None);
|
self.selection.set(None);
|
||||||
self.primary_selection.set(None);
|
self.primary_selection.set(None);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
backend::{ConnectorId, InputEvent, KeyState, AXIS_120},
|
backend::{ConnectorId, InputEvent, KeyState, AXIS_120},
|
||||||
client::{Client, ClientId},
|
client::ClientId,
|
||||||
fixed::Fixed,
|
fixed::Fixed,
|
||||||
ifs::{
|
ifs::{
|
||||||
ipc,
|
|
||||||
ipc::{
|
ipc::{
|
||||||
wl_data_device::{ClipboardIpc, WlDataDevice},
|
wl_data_device::{ClipboardIpc, WlDataDevice},
|
||||||
|
x_data_device::{XClipboardIpc, XPrimarySelectionIpc},
|
||||||
|
zwlr_data_control_device_v1::ZwlrDataControlDeviceV1,
|
||||||
zwp_primary_selection_device_v1::{
|
zwp_primary_selection_device_v1::{
|
||||||
PrimarySelectionIpc, ZwpPrimarySelectionDeviceV1,
|
PrimarySelectionIpc, ZwpPrimarySelectionDeviceV1,
|
||||||
},
|
},
|
||||||
|
DynDataSource,
|
||||||
},
|
},
|
||||||
wl_seat::{
|
wl_seat::{
|
||||||
wl_keyboard::{self, WlKeyboard},
|
wl_keyboard::{self, WlKeyboard},
|
||||||
|
|
@ -27,7 +29,7 @@ use {
|
||||||
},
|
},
|
||||||
state::DeviceHandlerData,
|
state::DeviceHandlerData,
|
||||||
tree::{Direction, FloatNode, Node, ToplevelNode},
|
tree::{Direction, FloatNode, Node, ToplevelNode},
|
||||||
utils::{bitflags::BitflagsExt, clonecell::CloneCell, smallmap::SmallMap},
|
utils::{bitflags::BitflagsExt, smallmap::SmallMap},
|
||||||
wire::WlDataOfferId,
|
wire::WlDataOfferId,
|
||||||
xkbcommon::{ModifierState, XKB_KEY_DOWN, XKB_KEY_UP},
|
xkbcommon::{ModifierState, XKB_KEY_DOWN, XKB_KEY_UP},
|
||||||
},
|
},
|
||||||
|
|
@ -429,19 +431,6 @@ impl WlSeatGlobal {
|
||||||
self.kb_owner.set_kb_node(self, node);
|
self.kb_owner.set_kb_node(self, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn offer_selection<T: ipc::IpcVtable>(
|
|
||||||
&self,
|
|
||||||
field: &CloneCell<Option<Rc<T::Source>>>,
|
|
||||||
client: &Rc<Client>,
|
|
||||||
) {
|
|
||||||
match field.get() {
|
|
||||||
Some(sel) => ipc::offer_source_to::<T>(&sel, client),
|
|
||||||
None => T::for_each_device(self, client.id, |dd| {
|
|
||||||
T::send_selection(dd, None);
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn for_each_seat<C>(&self, ver: u32, client: ClientId, mut f: C)
|
fn for_each_seat<C>(&self, ver: u32, client: ClientId, mut f: C)
|
||||||
where
|
where
|
||||||
C: FnMut(&Rc<WlSeat>),
|
C: FnMut(&Rc<WlSeat>),
|
||||||
|
|
@ -520,6 +509,17 @@ impl WlSeatGlobal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn for_each_wlr_data_device<C>(&self, ver: u32, mut f: C)
|
||||||
|
where
|
||||||
|
C: FnMut(&Rc<ZwlrDataControlDeviceV1>),
|
||||||
|
{
|
||||||
|
for dd in self.wlr_data_devices.lock().values() {
|
||||||
|
if dd.version >= ver {
|
||||||
|
f(dd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn surface_pointer_frame(&self, surface: &WlSurface) {
|
fn surface_pointer_frame(&self, surface: &WlSurface) {
|
||||||
self.surface_pointer_event(POINTER_FRAME_SINCE_VERSION, surface, |p| p.send_frame());
|
self.surface_pointer_event(POINTER_FRAME_SINCE_VERSION, surface, |p| p.send_frame());
|
||||||
}
|
}
|
||||||
|
|
@ -757,8 +757,14 @@ impl WlSeatGlobal {
|
||||||
});
|
});
|
||||||
|
|
||||||
if self.keyboard_node.get().node_client_id() != Some(surface.client.id) {
|
if self.keyboard_node.get().node_client_id() != Some(surface.client.id) {
|
||||||
self.offer_selection::<ClipboardIpc>(&self.selection, &surface.client);
|
self.offer_selection_to_client::<ClipboardIpc, XClipboardIpc>(
|
||||||
self.offer_selection::<PrimarySelectionIpc>(&self.primary_selection, &surface.client);
|
self.selection.get(),
|
||||||
|
&surface.client,
|
||||||
|
);
|
||||||
|
self.offer_selection_to_client::<PrimarySelectionIpc, XPrimarySelectionIpc>(
|
||||||
|
self.primary_selection.get(),
|
||||||
|
&surface.client,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -820,9 +826,11 @@ impl WlSeatGlobal {
|
||||||
serial: u32,
|
serial: u32,
|
||||||
) {
|
) {
|
||||||
if let Some(src) = &dnd.src {
|
if let Some(src) = &dnd.src {
|
||||||
ipc::offer_source_to::<ClipboardIpc>(src, &surface.client);
|
if !surface.client.is_xwayland {
|
||||||
|
src.clone().offer_to_regular_client(&surface.client);
|
||||||
|
}
|
||||||
src.for_each_data_offer(|offer| {
|
src.for_each_data_offer(|offer| {
|
||||||
offer.device.send_enter(surface.id, x, y, offer.id, serial);
|
offer.send_enter(surface.id, x, y, serial);
|
||||||
offer.send_source_actions();
|
offer.send_source_actions();
|
||||||
})
|
})
|
||||||
} else if surface.client.id == dnd.client.id {
|
} else if surface.client.id == dnd.client.id {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use {
|
||||||
fixed::Fixed,
|
fixed::Fixed,
|
||||||
ifs::{
|
ifs::{
|
||||||
ipc,
|
ipc,
|
||||||
ipc::{wl_data_device::ClipboardIpc, wl_data_source::WlDataSource},
|
ipc::wl_data_source::WlDataSource,
|
||||||
wl_seat::{
|
wl_seat::{
|
||||||
wl_pointer::PendingScroll, Dnd, DroppedDnd, WlSeatError, WlSeatGlobal,
|
wl_pointer::PendingScroll, Dnd, DroppedDnd, WlSeatError, WlSeatGlobal,
|
||||||
CHANGE_CURSOR_MOVED,
|
CHANGE_CURSOR_MOVED,
|
||||||
|
|
@ -371,7 +371,7 @@ impl PointerOwner for GrabPointerOwner {
|
||||||
icon.set_dnd_icon_seat(seat.id, Some(seat));
|
icon.set_dnd_icon_seat(seat.id, Some(seat));
|
||||||
}
|
}
|
||||||
if let Some(new) = &src {
|
if let Some(new) = &src {
|
||||||
ipc::attach_seat::<ClipboardIpc>(new, seat, ipc::Role::Dnd)?;
|
ipc::attach_seat(&**new, seat, ipc::Role::Dnd)?;
|
||||||
if let Some(drag) = new.toplevel_drag.get() {
|
if let Some(drag) = new.toplevel_drag.get() {
|
||||||
drag.start_drag();
|
drag.start_drag();
|
||||||
}
|
}
|
||||||
|
|
@ -456,7 +456,7 @@ impl PointerOwner for DndPointerOwner {
|
||||||
target.node_seat_state().remove_dnd_target(seat);
|
target.node_seat_state().remove_dnd_target(seat);
|
||||||
if !should_drop {
|
if !should_drop {
|
||||||
if let Some(src) = &self.dnd.src {
|
if let Some(src) = &self.dnd.src {
|
||||||
ipc::detach_seat::<ClipboardIpc>(src, seat);
|
ipc::detach_seat(&**src, seat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(icon) = self.icon.get() {
|
if let Some(icon) = self.icon.get() {
|
||||||
|
|
@ -527,7 +527,7 @@ impl PointerOwner for DndPointerOwner {
|
||||||
target.node_on_dnd_leave(&self.dnd);
|
target.node_on_dnd_leave(&self.dnd);
|
||||||
target.node_seat_state().remove_dnd_target(seat);
|
target.node_seat_state().remove_dnd_target(seat);
|
||||||
if let Some(src) = &self.dnd.src {
|
if let Some(src) = &self.dnd.src {
|
||||||
ipc::detach_seat::<ClipboardIpc>(src, seat);
|
ipc::detach_seat(&**src, seat);
|
||||||
}
|
}
|
||||||
if let Some(icon) = self.icon.get() {
|
if let Some(icon) = self.icon.get() {
|
||||||
icon.set_dnd_icon_seat(seat.id(), None);
|
icon.set_dnd_icon_seat(seat.id(), None);
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ use {
|
||||||
ifs::{
|
ifs::{
|
||||||
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
|
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
|
||||||
ext_session_lock_v1::ExtSessionLockV1,
|
ext_session_lock_v1::ExtSessionLockV1,
|
||||||
|
ipc::{x_data_device::XIpcDeviceIds, DataOfferIds, DataSourceIds},
|
||||||
jay_render_ctx::JayRenderCtx,
|
jay_render_ctx::JayRenderCtx,
|
||||||
jay_seat_events::JaySeatEvents,
|
jay_seat_events::JaySeatEvents,
|
||||||
jay_workspace_watcher::JayWorkspaceWatcher,
|
jay_workspace_watcher::JayWorkspaceWatcher,
|
||||||
|
|
@ -149,7 +150,8 @@ pub struct State {
|
||||||
pub config_dir: Option<String>,
|
pub config_dir: Option<String>,
|
||||||
pub config_file_id: NumCell<u64>,
|
pub config_file_id: NumCell<u64>,
|
||||||
pub tracker: Tracker<Self>,
|
pub tracker: Tracker<Self>,
|
||||||
pub data_offer_ids: NumCell<u64>,
|
pub data_offer_ids: DataOfferIds,
|
||||||
|
pub data_source_ids: DataSourceIds,
|
||||||
pub ring: Rc<IoUring>,
|
pub ring: Rc<IoUring>,
|
||||||
pub lock: ScreenlockState,
|
pub lock: ScreenlockState,
|
||||||
pub scales: RefCounted<Scale>,
|
pub scales: RefCounted<Scale>,
|
||||||
|
|
@ -196,6 +198,7 @@ pub struct XWaylandState {
|
||||||
pub enabled: Cell<bool>,
|
pub enabled: Cell<bool>,
|
||||||
pub handler: RefCell<Option<SpawnedFuture<()>>>,
|
pub handler: RefCell<Option<SpawnedFuture<()>>>,
|
||||||
pub queue: Rc<AsyncQueue<XWaylandEvent>>,
|
pub queue: Rc<AsyncQueue<XWaylandEvent>>,
|
||||||
|
pub ipc_device_ids: XIpcDeviceIds,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IdleState {
|
pub struct IdleState {
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,10 @@ impl<K: Eq, V, const N: usize> SmallMap<K, V, N> {
|
||||||
unsafe { self.m.get().deref_mut().take() }
|
unsafe { self.m.get().deref_mut().take() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn replace(&self, other: SmallVec<[(K, V); N]>) -> SmallVec<[(K, V); N]> {
|
||||||
|
unsafe { mem::replace(&mut self.m.get().deref_mut().m, other) }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pop(&self) -> Option<(K, V)> {
|
pub fn pop(&self) -> Option<(K, V)> {
|
||||||
unsafe { self.m.get().deref_mut().pop() }
|
unsafe { self.m.get().deref_mut().pop() }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,7 @@ use {
|
||||||
compositor::DISPLAY,
|
compositor::DISPLAY,
|
||||||
forker::{ForkerError, ForkerProxy},
|
forker::{ForkerError, ForkerProxy},
|
||||||
ifs::{
|
ifs::{
|
||||||
ipc::{
|
ipc::{x_data_offer::XDataOffer, DataOfferId, DataSourceId, IpcLocation},
|
||||||
wl_data_offer::WlDataOffer, wl_data_source::WlDataSource,
|
|
||||||
zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1,
|
|
||||||
zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1,
|
|
||||||
},
|
|
||||||
wl_seat::SeatId,
|
wl_seat::SeatId,
|
||||||
wl_surface::x_surface::xwindow::{Xwindow, XwindowData},
|
wl_surface::x_surface::xwindow::{Xwindow, XwindowData},
|
||||||
},
|
},
|
||||||
|
|
@ -237,15 +233,32 @@ pub enum XWaylandEvent {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
SeatChanged,
|
SeatChanged,
|
||||||
|
|
||||||
PrimarySelectionCancelSource(Rc<ZwpPrimarySelectionSourceV1>),
|
IpcCancelSource {
|
||||||
PrimarySelectionSendSource(Rc<ZwpPrimarySelectionSourceV1>, String, Rc<OwnedFd>),
|
location: IpcLocation,
|
||||||
PrimarySelectionSetOffer(Rc<ZwpPrimarySelectionOfferV1>),
|
seat: SeatId,
|
||||||
PrimarySelectionSetSelection(SeatId, Option<Rc<ZwpPrimarySelectionOfferV1>>),
|
source: DataSourceId,
|
||||||
PrimarySelectionAddOfferMimeType(Rc<ZwpPrimarySelectionOfferV1>, String),
|
},
|
||||||
|
IpcSendSource {
|
||||||
ClipboardCancelSource(Rc<WlDataSource>),
|
location: IpcLocation,
|
||||||
ClipboardSendSource(Rc<WlDataSource>, String, Rc<OwnedFd>),
|
seat: SeatId,
|
||||||
ClipboardSetOffer(Rc<WlDataOffer>),
|
source: DataSourceId,
|
||||||
ClipboardSetSelection(SeatId, Option<Rc<WlDataOffer>>),
|
mime_type: String,
|
||||||
ClipboardAddOfferMimeType(Rc<WlDataOffer>, String),
|
fd: Rc<OwnedFd>,
|
||||||
|
},
|
||||||
|
IpcSetOffer {
|
||||||
|
location: IpcLocation,
|
||||||
|
seat: SeatId,
|
||||||
|
offer: Rc<XDataOffer>,
|
||||||
|
},
|
||||||
|
IpcSetSelection {
|
||||||
|
location: IpcLocation,
|
||||||
|
seat: SeatId,
|
||||||
|
offer: Option<Rc<XDataOffer>>,
|
||||||
|
},
|
||||||
|
IpcAddOfferMimeType {
|
||||||
|
location: IpcLocation,
|
||||||
|
seat: SeatId,
|
||||||
|
offer: DataOfferId,
|
||||||
|
mime_type: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ use {
|
||||||
ipc::{
|
ipc::{
|
||||||
add_data_source_mime_type, destroy_data_device, destroy_data_offer,
|
add_data_source_mime_type, destroy_data_device, destroy_data_offer,
|
||||||
destroy_data_source, receive_data_offer,
|
destroy_data_source, receive_data_offer,
|
||||||
wl_data_device::{ClipboardIpc, WlDataDevice},
|
x_data_device::{XClipboardIpc, XIpc, XIpcDevice, XPrimarySelectionIpc},
|
||||||
zwp_primary_selection_device_v1::{
|
x_data_offer::XDataOffer,
|
||||||
PrimarySelectionIpc, ZwpPrimarySelectionDeviceV1,
|
x_data_source::XDataSource,
|
||||||
},
|
DataOfferId, DataSourceId, DynDataOffer, DynDataSource, IpcLocation, IpcVtable,
|
||||||
IpcVtable,
|
SourceData,
|
||||||
},
|
},
|
||||||
wl_seat::{SeatId, WlSeatGlobal},
|
wl_seat::{SeatId, WlSeatGlobal},
|
||||||
wl_surface::{
|
wl_surface::{
|
||||||
|
|
@ -30,7 +30,7 @@ use {
|
||||||
copyhashmap::CopyHashMap, errorfmt::ErrorFmt, linkedlist::LinkedList, numcell::NumCell,
|
copyhashmap::CopyHashMap, errorfmt::ErrorFmt, linkedlist::LinkedList, numcell::NumCell,
|
||||||
oserror::OsError, rc_eq::rc_eq,
|
oserror::OsError, rc_eq::rc_eq,
|
||||||
},
|
},
|
||||||
wire::{WlDataDeviceId, WlSurfaceId, ZwpPrimarySelectionDeviceV1Id},
|
wire::WlSurfaceId,
|
||||||
wire_xcon::{
|
wire_xcon::{
|
||||||
ChangeProperty, ChangeWindowAttributes, ClientMessage, CompositeRedirectSubwindows,
|
ChangeProperty, ChangeWindowAttributes, ClientMessage, CompositeRedirectSubwindows,
|
||||||
ConfigureNotify, ConfigureRequest, ConfigureWindow, ConfigureWindowValues,
|
ConfigureNotify, ConfigureRequest, ConfigureWindow, ConfigureWindowValues,
|
||||||
|
|
@ -67,6 +67,7 @@ use {
|
||||||
std::{
|
std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
|
marker::PhantomData,
|
||||||
mem::{self},
|
mem::{self},
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
|
@ -151,47 +152,30 @@ atoms! {
|
||||||
XdndTypeList,
|
XdndTypeList,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EnhancedOffer<T: IpcVtable> {
|
struct EnhancedOffer {
|
||||||
offer: Rc<T::Offer>,
|
offer: Rc<XDataOffer>,
|
||||||
mime_types: RefCell<Vec<u32>>,
|
mime_types: RefCell<Vec<u32>>,
|
||||||
active: Cell<bool>,
|
active: Cell<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SelectionData<T: IpcVtable> {
|
#[derive(Default)]
|
||||||
devices: CopyHashMap<SeatId, Rc<T::Device>>,
|
struct SelectionData<T: XIpc> {
|
||||||
sources: CopyHashMap<SeatId, Rc<T::Source>>,
|
sources: CopyHashMap<SeatId, Rc<XDataSource>>,
|
||||||
offers: CopyHashMap<SeatId, Rc<EnhancedOffer<T>>>,
|
offers: CopyHashMap<SeatId, Rc<EnhancedOffer>>,
|
||||||
active_offer: CloneCell<Option<Rc<EnhancedOffer<T>>>>,
|
active_offer: CloneCell<Option<Rc<EnhancedOffer>>>,
|
||||||
win: Cell<u32>,
|
win: Cell<u32>,
|
||||||
selection: Cell<u32>,
|
selection: Cell<u32>,
|
||||||
pending_transfers: RefCell<Vec<PendingTransfer>>,
|
pending_transfers: RefCell<Vec<PendingTransfer>>,
|
||||||
|
_phantom: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: IpcVtable> Default for SelectionData<T> {
|
impl<T: XIpc> SelectionData<T> {
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
devices: Default::default(),
|
|
||||||
sources: Default::default(),
|
|
||||||
offers: Default::default(),
|
|
||||||
active_offer: Default::default(),
|
|
||||||
win: Cell::new(0),
|
|
||||||
selection: Cell::new(0),
|
|
||||||
pending_transfers: RefCell::new(vec![]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: IpcVtable> SelectionData<T> {
|
|
||||||
fn destroy(&self) {
|
fn destroy(&self) {
|
||||||
for (_, offer) in self.offers.lock().drain() {
|
for (_, offer) in self.offers.lock().drain() {
|
||||||
destroy_data_offer::<T>(&offer.offer);
|
destroy_data_offer::<T>(&offer.offer);
|
||||||
}
|
}
|
||||||
self.active_offer.take();
|
self.active_offer.take();
|
||||||
self.destroy_sources();
|
self.destroy_sources();
|
||||||
for (_, device) in self.devices.lock().drain() {
|
|
||||||
destroy_data_device::<T>(&device);
|
|
||||||
T::remove_from_seat(&device);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn destroy_sources(&self) {
|
fn destroy_sources(&self) {
|
||||||
|
|
@ -202,20 +186,20 @@ impl<T: IpcVtable> SelectionData<T> {
|
||||||
|
|
||||||
fn seat_removed(&self, id: SeatId) {
|
fn seat_removed(&self, id: SeatId) {
|
||||||
if let Some(offer) = self.active_offer.get() {
|
if let Some(offer) = self.active_offer.get() {
|
||||||
if T::get_offer_seat(&offer.offer).id() == id {
|
if offer.offer.get_seat().id() == id {
|
||||||
self.active_offer.take();
|
self.active_offer.take();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.offers.remove(&id);
|
self.offers.remove(&id);
|
||||||
self.sources.remove(&id);
|
self.sources.remove(&id);
|
||||||
self.devices.remove(&id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct XwmShared {
|
pub struct XwmShared {
|
||||||
data: SelectionData<ClipboardIpc>,
|
devices: CopyHashMap<SeatId, Rc<XIpcDevice>>,
|
||||||
primary_selection: SelectionData<PrimarySelectionIpc>,
|
data: SelectionData<XClipboardIpc>,
|
||||||
|
primary_selection: SelectionData<XPrimarySelectionIpc>,
|
||||||
transfers: CopyHashMap<u64, SpawnedFuture<()>>,
|
transfers: CopyHashMap<u64, SpawnedFuture<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,6 +207,11 @@ impl Drop for XwmShared {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.data.destroy();
|
self.data.destroy();
|
||||||
self.primary_selection.destroy();
|
self.primary_selection.destroy();
|
||||||
|
for (_, device) in self.devices.lock().drain() {
|
||||||
|
destroy_data_device::<XClipboardIpc>(&device);
|
||||||
|
destroy_data_device::<XPrimarySelectionIpc>(&device);
|
||||||
|
device.seat.unset_x_data_device(device.id);
|
||||||
|
}
|
||||||
self.transfers.clear();
|
self.transfers.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -557,27 +546,19 @@ impl Wm {
|
||||||
for seat in removed_seats {
|
for seat in removed_seats {
|
||||||
self.shared.data.seat_removed(seat);
|
self.shared.data.seat_removed(seat);
|
||||||
self.shared.primary_selection.seat_removed(seat);
|
self.shared.primary_selection.seat_removed(seat);
|
||||||
|
self.shared.devices.remove(&seat);
|
||||||
}
|
}
|
||||||
for seat in new_seats {
|
for seat in new_seats {
|
||||||
let dd = Rc::new(WlDataDevice::new(
|
let dd = Rc::new(XIpcDevice {
|
||||||
WlDataDeviceId::NONE,
|
id: self.state.xwayland.ipc_device_ids.next(),
|
||||||
&self.client,
|
clipboard: Default::default(),
|
||||||
1,
|
primary_selection: Default::default(),
|
||||||
&seat,
|
seat: seat.clone(),
|
||||||
true,
|
state: self.state.clone(),
|
||||||
));
|
client: self.client.clone(),
|
||||||
seat.add_data_device(&dd);
|
});
|
||||||
self.shared.data.devices.set(seat.id(), dd);
|
seat.set_x_data_device(&dd);
|
||||||
|
self.shared.devices.set(seat.id(), dd.clone());
|
||||||
let dd = Rc::new(ZwpPrimarySelectionDeviceV1::new(
|
|
||||||
ZwpPrimarySelectionDeviceV1Id::NONE,
|
|
||||||
&self.client,
|
|
||||||
1,
|
|
||||||
&seat,
|
|
||||||
true,
|
|
||||||
));
|
|
||||||
seat.add_primary_selection_device(&dd);
|
|
||||||
self.shared.primary_selection.devices.set(seat.id(), dd);
|
|
||||||
}
|
}
|
||||||
self.known_seats = current_seats;
|
self.known_seats = current_seats;
|
||||||
}
|
}
|
||||||
|
|
@ -611,55 +592,121 @@ impl Wm {
|
||||||
XWaylandEvent::ActivateRoot => self.activate_window(None, Initiator::Wayland).await,
|
XWaylandEvent::ActivateRoot => self.activate_window(None, Initiator::Wayland).await,
|
||||||
XWaylandEvent::Close(window) => self.close_window(&window).await,
|
XWaylandEvent::Close(window) => self.close_window(&window).await,
|
||||||
XWaylandEvent::SeatChanged => self.seats_changed(),
|
XWaylandEvent::SeatChanged => self.seats_changed(),
|
||||||
XWaylandEvent::PrimarySelectionCancelSource(src) => {
|
XWaylandEvent::IpcCancelSource {
|
||||||
self.dd_cancel_source(&self.shared.clone().primary_selection, &src)
|
location,
|
||||||
}
|
seat,
|
||||||
XWaylandEvent::PrimarySelectionSendSource(src, mime_type, fd) => {
|
source,
|
||||||
self.dd_send_source(&self.shared.clone().primary_selection, &src, mime_type, fd)
|
} => match location {
|
||||||
.await;
|
IpcLocation::Clipboard => {
|
||||||
}
|
self.dd_cancel_source::<XClipboardIpc>(&self.shared.clone().data, seat, source)
|
||||||
XWaylandEvent::PrimarySelectionSetOffer(offer) => {
|
}
|
||||||
self.dd_set_offer(&self.shared.clone().primary_selection, offer)
|
IpcLocation::PrimarySelection => self.dd_cancel_source::<XPrimarySelectionIpc>(
|
||||||
.await;
|
&self.shared.clone().primary_selection,
|
||||||
}
|
seat,
|
||||||
XWaylandEvent::PrimarySelectionSetSelection(seat, offer) => {
|
source,
|
||||||
self.dd_set_selection(&self.shared.clone().primary_selection, seat, offer)
|
),
|
||||||
.await;
|
},
|
||||||
}
|
XWaylandEvent::IpcSendSource {
|
||||||
XWaylandEvent::PrimarySelectionAddOfferMimeType(offer, mt) => {
|
location,
|
||||||
self.dd_add_offer_mime_type(&self.shared.clone().primary_selection, offer, mt)
|
seat,
|
||||||
.await;
|
source,
|
||||||
}
|
mime_type,
|
||||||
XWaylandEvent::ClipboardCancelSource(src) => {
|
fd,
|
||||||
self.dd_cancel_source(&self.shared.clone().data, &src)
|
} => match location {
|
||||||
}
|
IpcLocation::Clipboard => {
|
||||||
XWaylandEvent::ClipboardSendSource(src, mime_type, fd) => {
|
self.dd_send_source::<XClipboardIpc>(
|
||||||
self.dd_send_source(&self.shared.clone().data, &src, mime_type, fd)
|
&self.shared.clone().data,
|
||||||
.await;
|
seat,
|
||||||
}
|
source,
|
||||||
XWaylandEvent::ClipboardSetOffer(offer) => {
|
mime_type,
|
||||||
self.dd_set_offer(&self.shared.clone().data, offer).await;
|
fd,
|
||||||
}
|
)
|
||||||
XWaylandEvent::ClipboardSetSelection(seat, offer) => {
|
.await
|
||||||
self.dd_set_selection(&self.shared.clone().data, seat, offer)
|
}
|
||||||
.await;
|
IpcLocation::PrimarySelection => {
|
||||||
}
|
self.dd_send_source::<XPrimarySelectionIpc>(
|
||||||
XWaylandEvent::ClipboardAddOfferMimeType(offer, mt) => {
|
&self.shared.clone().primary_selection,
|
||||||
self.dd_add_offer_mime_type(&self.shared.clone().data, offer, mt)
|
seat,
|
||||||
.await;
|
source,
|
||||||
}
|
mime_type,
|
||||||
|
fd,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
},
|
||||||
|
XWaylandEvent::IpcSetOffer {
|
||||||
|
location,
|
||||||
|
seat,
|
||||||
|
offer,
|
||||||
|
} => match location {
|
||||||
|
IpcLocation::Clipboard => {
|
||||||
|
self.dd_set_offer::<XClipboardIpc>(&self.shared.clone().data, seat, offer)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
IpcLocation::PrimarySelection => {
|
||||||
|
self.dd_set_offer::<XPrimarySelectionIpc>(
|
||||||
|
&self.shared.clone().primary_selection,
|
||||||
|
seat,
|
||||||
|
offer,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
},
|
||||||
|
XWaylandEvent::IpcSetSelection {
|
||||||
|
seat,
|
||||||
|
location,
|
||||||
|
offer,
|
||||||
|
} => match location {
|
||||||
|
IpcLocation::Clipboard => {
|
||||||
|
self.dd_set_selection::<XClipboardIpc>(&self.shared.clone().data, seat, offer)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
IpcLocation::PrimarySelection => {
|
||||||
|
self.dd_set_selection::<XPrimarySelectionIpc>(
|
||||||
|
&self.shared.clone().primary_selection,
|
||||||
|
seat,
|
||||||
|
offer,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
},
|
||||||
|
XWaylandEvent::IpcAddOfferMimeType {
|
||||||
|
location,
|
||||||
|
seat,
|
||||||
|
offer,
|
||||||
|
mime_type,
|
||||||
|
} => match location {
|
||||||
|
IpcLocation::Clipboard => {
|
||||||
|
self.dd_add_offer_mime_type::<XClipboardIpc>(
|
||||||
|
&self.shared.clone().data,
|
||||||
|
seat,
|
||||||
|
offer,
|
||||||
|
mime_type,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
IpcLocation::PrimarySelection => {
|
||||||
|
self.dd_add_offer_mime_type::<XPrimarySelectionIpc>(
|
||||||
|
&self.shared.clone().primary_selection,
|
||||||
|
seat,
|
||||||
|
offer,
|
||||||
|
mime_type,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn dd_add_offer_mime_type<T: IpcVtable>(
|
async fn dd_add_offer_mime_type<T: XIpc>(
|
||||||
&mut self,
|
&mut self,
|
||||||
sd: &SelectionData<T>,
|
sd: &SelectionData<T>,
|
||||||
offer: Rc<T::Offer>,
|
seat: SeatId,
|
||||||
|
offer: DataOfferId,
|
||||||
mt: String,
|
mt: String,
|
||||||
) {
|
) {
|
||||||
let seat = T::get_offer_seat(&offer);
|
let enhanced = match sd.offers.get(&seat) {
|
||||||
let enhanced = match sd.offers.get(&seat.id()) {
|
Some(r) if r.offer.offer_id != offer => {
|
||||||
Some(r) if !rc_eq(&r.offer, &offer) => {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
|
@ -677,20 +724,19 @@ impl Wm {
|
||||||
enhanced.mime_types.borrow_mut().push(mt);
|
enhanced.mime_types.borrow_mut().push(mt);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn dd_set_offer<T: IpcVtable>(&mut self, sd: &SelectionData<T>, offer: Rc<T::Offer>) {
|
async fn dd_set_offer<T: XIpc>(
|
||||||
let seat = T::get_offer_seat(&offer);
|
&mut self,
|
||||||
|
sd: &SelectionData<T>,
|
||||||
|
seat: SeatId,
|
||||||
|
offer: Rc<XDataOffer>,
|
||||||
|
) {
|
||||||
let mut mime_types = vec![];
|
let mut mime_types = vec![];
|
||||||
if let Some(offer) = sd.offers.remove(&seat.id()) {
|
if let Some(offer) = sd.offers.remove(&seat) {
|
||||||
destroy_data_offer::<T>(&offer.offer);
|
destroy_data_offer::<T>(&offer.offer);
|
||||||
mime_types = mem::take(offer.mime_types.borrow_mut().deref_mut());
|
mime_types = mem::take(offer.mime_types.borrow_mut().deref_mut());
|
||||||
}
|
}
|
||||||
match T::get_offer_data(&offer).source() {
|
|
||||||
None => return,
|
|
||||||
Some(s) if T::get_source_data(&s).is_xwm => return,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
sd.offers.set(
|
sd.offers.set(
|
||||||
seat.id(),
|
seat,
|
||||||
Rc::new(EnhancedOffer {
|
Rc::new(EnhancedOffer {
|
||||||
offer,
|
offer,
|
||||||
mime_types: RefCell::new(mime_types),
|
mime_types: RefCell::new(mime_types),
|
||||||
|
|
@ -699,11 +745,11 @@ impl Wm {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn dd_set_selection<T: IpcVtable>(
|
async fn dd_set_selection<T: XIpc>(
|
||||||
&mut self,
|
&mut self,
|
||||||
sd: &SelectionData<T>,
|
sd: &SelectionData<T>,
|
||||||
seat: SeatId,
|
seat: SeatId,
|
||||||
offer: Option<Rc<T::Offer>>,
|
offer: Option<Rc<XDataOffer>>,
|
||||||
) {
|
) {
|
||||||
let offer = match offer {
|
let offer = match offer {
|
||||||
None => {
|
None => {
|
||||||
|
|
@ -794,22 +840,19 @@ impl Wm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn dd_send_source<T: IpcVtable>(
|
async fn dd_send_source<T: XIpc>(
|
||||||
&mut self,
|
&mut self,
|
||||||
sd: &SelectionData<T>,
|
sd: &SelectionData<T>,
|
||||||
src: &Rc<T::Source>,
|
seat: SeatId,
|
||||||
|
src: DataSourceId,
|
||||||
mime_type: String,
|
mime_type: String,
|
||||||
fd: Rc<OwnedFd>,
|
fd: Rc<OwnedFd>,
|
||||||
) {
|
) {
|
||||||
let seat = match T::get_source_data(src).seat.get() {
|
let actual_src = match sd.sources.get(&seat) {
|
||||||
Some(s) => s,
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
let actual_src = match sd.sources.get(&seat.id()) {
|
|
||||||
None => return,
|
None => return,
|
||||||
Some(src) => src,
|
Some(src) => src,
|
||||||
};
|
};
|
||||||
if !rc_eq(src, &actual_src) {
|
if actual_src.source_data().id != src {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mime_type = match self.mime_type_to_atom(mime_type).await {
|
let mime_type = match self.mime_type_to_atom(mime_type).await {
|
||||||
|
|
@ -838,13 +881,16 @@ impl Wm {
|
||||||
.push(PendingTransfer { mime_type, fd });
|
.push(PendingTransfer { mime_type, fd });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dd_cancel_source<T: IpcVtable>(&mut self, sd: &SelectionData<T>, src: &Rc<T::Source>) {
|
fn dd_cancel_source<T: XIpc>(
|
||||||
if let Some(seat) = T::get_source_data(src).seat.get() {
|
&mut self,
|
||||||
if let Some(cur) = sd.sources.get(&seat.id()) {
|
sd: &SelectionData<T>,
|
||||||
if rc_eq(src, &cur) {
|
seat: SeatId,
|
||||||
sd.sources.remove(&seat.id());
|
source: DataSourceId,
|
||||||
destroy_data_source::<T>(&cur);
|
) {
|
||||||
}
|
if let Some(cur) = sd.sources.get(&seat) {
|
||||||
|
if cur.source_data().id == source {
|
||||||
|
sd.sources.remove(&seat);
|
||||||
|
destroy_data_source::<T>(&cur);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1508,7 +1554,7 @@ impl Wm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_xfixes_selection_notify_<T: IpcVtable>(
|
async fn handle_xfixes_selection_notify_<T: XIpc>(
|
||||||
&mut self,
|
&mut self,
|
||||||
sd: &SelectionData<T>,
|
sd: &SelectionData<T>,
|
||||||
event: &XfixesSelectionNotify,
|
event: &XfixesSelectionNotify,
|
||||||
|
|
@ -1562,7 +1608,7 @@ impl Wm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_selection_request_<T: IpcVtable>(
|
async fn handle_selection_request_<T: XIpc>(
|
||||||
&mut self,
|
&mut self,
|
||||||
sd: &SelectionData<T>,
|
sd: &SelectionData<T>,
|
||||||
event: &SelectionRequest,
|
event: &SelectionRequest,
|
||||||
|
|
@ -1663,7 +1709,7 @@ impl Wm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_selection_notify_<T: IpcVtable>(
|
async fn handle_selection_notify_<T: XIpc>(
|
||||||
&mut self,
|
&mut self,
|
||||||
sd: &SelectionData<T>,
|
sd: &SelectionData<T>,
|
||||||
event: &SelectionNotify,
|
event: &SelectionNotify,
|
||||||
|
|
@ -1676,19 +1722,24 @@ impl Wm {
|
||||||
}
|
}
|
||||||
if event.target == self.atoms.TARGETS {
|
if event.target == self.atoms.TARGETS {
|
||||||
let targets = self.get_selection_mime_types(sd.win.get()).await?;
|
let targets = self.get_selection_mime_types(sd.win.get()).await?;
|
||||||
for dev in sd.devices.lock().values() {
|
for dev in self.shared.devices.lock().values() {
|
||||||
let seat = T::get_device_seat(dev);
|
let seat = T::get_device_seat(dev);
|
||||||
if !seat.may_modify_primary_selection(&self.client, None) {
|
if !seat.may_modify_primary_selection(&self.client, None) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let source = Rc::new(T::create_xwm_source(&self.client));
|
let source = Rc::new(XDataSource {
|
||||||
|
state: self.state.clone(),
|
||||||
|
device: dev.clone(),
|
||||||
|
data: SourceData::new(&self.client),
|
||||||
|
location: T::LOCATION,
|
||||||
|
});
|
||||||
|
for target in &targets {
|
||||||
|
add_data_source_mime_type::<T>(&source, target);
|
||||||
|
}
|
||||||
if let Err(e) = T::set_seat_selection(&seat, &source, None) {
|
if let Err(e) = T::set_seat_selection(&seat, &source, None) {
|
||||||
log::error!("Could not set selection: {}", ErrorFmt(e));
|
log::error!("Could not set selection: {}", ErrorFmt(e));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
for target in &targets {
|
|
||||||
add_data_source_mime_type::<T>(&source, target);
|
|
||||||
}
|
|
||||||
sd.sources.set(seat.id(), source);
|
sd.sources.set(seat.id(), source);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
30
wire/zwlr_data_control_device_v1.txt
Normal file
30
wire/zwlr_data_control_device_v1.txt
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# requests
|
||||||
|
|
||||||
|
msg set_selection = 0 {
|
||||||
|
source: id(zwlr_data_control_source_v1),
|
||||||
|
}
|
||||||
|
|
||||||
|
msg destroy = 1 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
msg set_primary_selection = 2 {
|
||||||
|
source: id(zwlr_data_control_source_v1),
|
||||||
|
}
|
||||||
|
|
||||||
|
# events
|
||||||
|
|
||||||
|
msg data_offer = 0 {
|
||||||
|
id: id(zwlr_data_control_offer_v1),
|
||||||
|
}
|
||||||
|
|
||||||
|
msg selection = 1 {
|
||||||
|
id: id(zwlr_data_control_offer_v1),
|
||||||
|
}
|
||||||
|
|
||||||
|
msg finished = 2 {
|
||||||
|
}
|
||||||
|
|
||||||
|
msg primary_selection = 3 {
|
||||||
|
id: id(zwlr_data_control_offer_v1),
|
||||||
|
}
|
||||||
14
wire/zwlr_data_control_manager_v1.txt
Normal file
14
wire/zwlr_data_control_manager_v1.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# requests
|
||||||
|
|
||||||
|
msg create_data_source = 0 {
|
||||||
|
id: id(zwlr_data_control_source_v1),
|
||||||
|
}
|
||||||
|
|
||||||
|
msg get_data_device = 1 {
|
||||||
|
id: id(zwlr_data_control_device_v1),
|
||||||
|
seat: id(wl_seat),
|
||||||
|
}
|
||||||
|
|
||||||
|
msg destroy = 2 {
|
||||||
|
|
||||||
|
}
|
||||||
16
wire/zwlr_data_control_offer_v1.txt
Normal file
16
wire/zwlr_data_control_offer_v1.txt
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# requests
|
||||||
|
|
||||||
|
msg receive = 0 {
|
||||||
|
mime_type: str,
|
||||||
|
fd: fd,
|
||||||
|
}
|
||||||
|
|
||||||
|
msg destroy = 1 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# events
|
||||||
|
|
||||||
|
msg offer = 0 {
|
||||||
|
mime_type: str,
|
||||||
|
}
|
||||||
20
wire/zwlr_data_control_source_v1.txt
Normal file
20
wire/zwlr_data_control_source_v1.txt
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# requests
|
||||||
|
|
||||||
|
msg offer = 0 {
|
||||||
|
mime_type: str,
|
||||||
|
}
|
||||||
|
|
||||||
|
msg destroy = 1 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# events
|
||||||
|
|
||||||
|
msg send = 0 {
|
||||||
|
mime_type: str,
|
||||||
|
fd: fd,
|
||||||
|
}
|
||||||
|
|
||||||
|
msg cancelled = 1 {
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue