Merge pull request #115 from Smithay/data_device
Data device implementation
This commit is contained in:
commit
6d83dff0ee
|
@ -12,6 +12,7 @@ members = [ "anvil" ]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wayland-server = "0.21.1"
|
wayland-server = "0.21.1"
|
||||||
wayland-sys = "0.21.1"
|
wayland-sys = "0.21.1"
|
||||||
|
wayland-commons = "0.21.1"
|
||||||
nix = "0.11"
|
nix = "0.11"
|
||||||
xkbcommon = "0.2.1"
|
xkbcommon = "0.2.1"
|
||||||
tempfile = "2.1.5"
|
tempfile = "2.1.5"
|
||||||
|
|
|
@ -68,9 +68,9 @@ pub fn init_shell(
|
||||||
compositor_token,
|
compositor_token,
|
||||||
move |shell_event| match shell_event {
|
move |shell_event| match shell_event {
|
||||||
XdgRequest::NewToplevel { surface } => {
|
XdgRequest::NewToplevel { surface } => {
|
||||||
// place the window at a random location in the [0;300]x[0;300] square
|
// place the window at a random location in the [0;800]x[0;800] square
|
||||||
use rand::distributions::{IndependentSample, Range};
|
use rand::distributions::{IndependentSample, Range};
|
||||||
let range = Range::new(0, 300);
|
let range = Range::new(0, 800);
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let x = range.ind_sample(&mut rng);
|
let x = range.ind_sample(&mut rng);
|
||||||
let y = range.ind_sample(&mut rng);
|
let y = range.ind_sample(&mut rng);
|
||||||
|
|
|
@ -42,6 +42,7 @@ use smithay::{
|
||||||
input::Libinput,
|
input::Libinput,
|
||||||
wayland::{
|
wayland::{
|
||||||
compositor::CompositorToken,
|
compositor::CompositorToken,
|
||||||
|
data_device::{default_action_chooser, init_data_device, set_data_device_focus},
|
||||||
output::{Mode, Output, PhysicalProperties},
|
output::{Mode, Output, PhysicalProperties},
|
||||||
seat::{Seat, XkbConfig},
|
seat::{Seat, XkbConfig},
|
||||||
shm::init_shm_global,
|
shm::init_shm_global,
|
||||||
|
@ -107,12 +108,20 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger
|
||||||
|
|
||||||
let udev_session_id = notifier.register(&mut udev_backend);
|
let udev_session_id = notifier.register(&mut udev_backend);
|
||||||
|
|
||||||
|
init_data_device(
|
||||||
|
&mut display.borrow_mut(),
|
||||||
|
|_| {},
|
||||||
|
default_action_chooser,
|
||||||
|
log.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
let (mut w_seat, _) = Seat::new(&mut display.borrow_mut(), session.seat(), log.clone());
|
let (mut w_seat, _) = Seat::new(&mut display.borrow_mut(), session.seat(), log.clone());
|
||||||
|
|
||||||
let pointer = w_seat.add_pointer();
|
let pointer = w_seat.add_pointer();
|
||||||
let keyboard = w_seat
|
let keyboard = w_seat
|
||||||
.add_keyboard(XkbConfig::default(), 1000, 500)
|
.add_keyboard(XkbConfig::default(), 1000, 500, |seat, focus| {
|
||||||
.expect("Failed to initialize the keyboard");
|
set_data_device_focus(seat, focus.and_then(|s| s.client()))
|
||||||
|
}).expect("Failed to initialize the keyboard");
|
||||||
|
|
||||||
let (output, _output_global) = Output::new(
|
let (output, _output_global) = Output::new(
|
||||||
&mut display.borrow_mut(),
|
&mut display.borrow_mut(),
|
||||||
|
|
|
@ -11,6 +11,7 @@ use smithay::{
|
||||||
winit,
|
winit,
|
||||||
},
|
},
|
||||||
wayland::{
|
wayland::{
|
||||||
|
data_device::{default_action_chooser, init_data_device, set_data_device_focus},
|
||||||
output::{Mode, Output, PhysicalProperties},
|
output::{Mode, Output, PhysicalProperties},
|
||||||
seat::{Seat, XkbConfig},
|
seat::{Seat, XkbConfig},
|
||||||
shm::init_shm_global,
|
shm::init_shm_global,
|
||||||
|
@ -53,12 +54,16 @@ pub fn run_winit(display: &mut Display, event_loop: &mut EventLoop<()>, log: Log
|
||||||
|
|
||||||
let (compositor_token, _, _, window_map) = init_shell(display, log.clone());
|
let (compositor_token, _, _, window_map) = init_shell(display, log.clone());
|
||||||
|
|
||||||
|
init_data_device(display, |_| {}, default_action_chooser, log.clone());
|
||||||
|
|
||||||
let (mut seat, _) = Seat::new(display, "winit".into(), log.clone());
|
let (mut seat, _) = Seat::new(display, "winit".into(), log.clone());
|
||||||
|
|
||||||
let pointer = seat.add_pointer();
|
let pointer = seat.add_pointer();
|
||||||
|
|
||||||
let keyboard = seat
|
let keyboard = seat
|
||||||
.add_keyboard(XkbConfig::default(), 1000, 500)
|
.add_keyboard(XkbConfig::default(), 1000, 500, |seat, focus| {
|
||||||
.expect("Failed to initialize the keyboard");
|
set_data_device_focus(seat, focus.and_then(|s| s.client()))
|
||||||
|
}).expect("Failed to initialize the keyboard");
|
||||||
|
|
||||||
let (output, _) = Output::new(
|
let (output, _) = Output::new(
|
||||||
display,
|
display,
|
||||||
|
|
|
@ -11,6 +11,7 @@ pub extern crate image;
|
||||||
#[cfg_attr(feature = "backend_session", macro_use)]
|
#[cfg_attr(feature = "backend_session", macro_use)]
|
||||||
extern crate nix;
|
extern crate nix;
|
||||||
extern crate tempfile;
|
extern crate tempfile;
|
||||||
|
pub extern crate wayland_commons;
|
||||||
pub extern crate wayland_protocols;
|
pub extern crate wayland_protocols;
|
||||||
pub extern crate wayland_server;
|
pub extern crate wayland_server;
|
||||||
extern crate wayland_sys;
|
extern crate wayland_sys;
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use wayland_server::{
|
||||||
|
protocol::{
|
||||||
|
wl_data_device_manager::DndAction,
|
||||||
|
wl_data_source::{Request, WlDataSource},
|
||||||
|
},
|
||||||
|
NewResource, Resource,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The metadata describing a data source
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SourceMetadata {
|
||||||
|
/// The MIME types supported by this source
|
||||||
|
pub mime_types: Vec<String>,
|
||||||
|
/// The Drag'n'Drop actions supported by this source
|
||||||
|
pub dnd_action: DndAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn implement_data_source(src: NewResource<WlDataSource>) -> Resource<WlDataSource> {
|
||||||
|
src.implement(
|
||||||
|
|req, me| {
|
||||||
|
let data: &Mutex<SourceMetadata> = me.user_data().unwrap();
|
||||||
|
let mut guard = data.lock().unwrap();
|
||||||
|
match req {
|
||||||
|
Request::Offer { mime_type } => guard.mime_types.push(mime_type),
|
||||||
|
Request::SetActions { dnd_actions } => {
|
||||||
|
guard.dnd_action = DndAction::from_bits_truncate(dnd_actions);
|
||||||
|
}
|
||||||
|
Request::Destroy => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None::<fn(_)>,
|
||||||
|
Mutex::new(SourceMetadata {
|
||||||
|
mime_types: Vec::new(),
|
||||||
|
dnd_action: DndAction::None,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access the metadata of a data source
|
||||||
|
pub fn with_source_metadata<T, F: FnOnce(&SourceMetadata) -> T>(
|
||||||
|
source: &Resource<WlDataSource>,
|
||||||
|
f: F,
|
||||||
|
) -> Result<T, ()> {
|
||||||
|
match source.user_data::<Mutex<SourceMetadata>>() {
|
||||||
|
Some(data) => Ok(f(&data.lock().unwrap())),
|
||||||
|
None => Err(()),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,329 @@
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use wayland_server::{
|
||||||
|
protocol::{
|
||||||
|
wl_data_device, wl_data_device_manager::DndAction, wl_data_offer, wl_data_source, wl_pointer,
|
||||||
|
wl_surface,
|
||||||
|
},
|
||||||
|
NewResource, Resource,
|
||||||
|
};
|
||||||
|
|
||||||
|
use wayland::seat::{AxisFrame, PointerGrab, PointerInnerHandle, Seat};
|
||||||
|
|
||||||
|
use super::{with_source_metadata, DataDeviceData, SeatData};
|
||||||
|
|
||||||
|
pub(crate) struct DnDGrab {
|
||||||
|
data_source: Option<Resource<wl_data_source::WlDataSource>>,
|
||||||
|
current_focus: Option<Resource<wl_surface::WlSurface>>,
|
||||||
|
pending_offers: Vec<Resource<wl_data_offer::WlDataOffer>>,
|
||||||
|
offer_data: Option<Arc<Mutex<OfferData>>>,
|
||||||
|
origin: Resource<wl_surface::WlSurface>,
|
||||||
|
seat: Seat,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DnDGrab {
|
||||||
|
pub(crate) fn new(
|
||||||
|
source: Option<Resource<wl_data_source::WlDataSource>>,
|
||||||
|
origin: Resource<wl_surface::WlSurface>,
|
||||||
|
seat: Seat,
|
||||||
|
) -> DnDGrab {
|
||||||
|
DnDGrab {
|
||||||
|
data_source: source,
|
||||||
|
current_focus: None,
|
||||||
|
pending_offers: Vec::with_capacity(1),
|
||||||
|
offer_data: None,
|
||||||
|
origin,
|
||||||
|
seat,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PointerGrab for DnDGrab {
|
||||||
|
fn motion(
|
||||||
|
&mut self,
|
||||||
|
_handle: &mut PointerInnerHandle,
|
||||||
|
location: (f64, f64),
|
||||||
|
focus: Option<(Resource<wl_surface::WlSurface>, (f64, f64))>,
|
||||||
|
serial: u32,
|
||||||
|
time: u32,
|
||||||
|
) {
|
||||||
|
let (x, y) = location;
|
||||||
|
let seat_data = self
|
||||||
|
.seat
|
||||||
|
.user_data()
|
||||||
|
.get::<Mutex<SeatData>>()
|
||||||
|
.unwrap()
|
||||||
|
.lock()
|
||||||
|
.unwrap();
|
||||||
|
if focus.as_ref().map(|&(ref s, _)| s) != self.current_focus.as_ref() {
|
||||||
|
// focus changed, we need to make a leave if appropriate
|
||||||
|
if let Some(surface) = self.current_focus.take() {
|
||||||
|
// only leave if there is a data source or we are on the original client
|
||||||
|
if self.data_source.is_some() || self.origin.same_client_as(&surface) {
|
||||||
|
for device in &seat_data.known_devices {
|
||||||
|
if device.same_client_as(&surface) {
|
||||||
|
device.send(wl_data_device::Event::Leave);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// disable the offers
|
||||||
|
self.pending_offers.clear();
|
||||||
|
if let Some(offer_data) = self.offer_data.take() {
|
||||||
|
offer_data.lock().unwrap().active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((surface, (sx, sy))) = focus {
|
||||||
|
// early return if the surface is no longer valid
|
||||||
|
let client = match surface.client() {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
if self.current_focus.is_none() {
|
||||||
|
// We entered a new surface, send the data offer if appropriate
|
||||||
|
if let Some(ref source) = self.data_source {
|
||||||
|
let offer_data = Arc::new(Mutex::new(OfferData {
|
||||||
|
active: true,
|
||||||
|
dropped: false,
|
||||||
|
accepted: true,
|
||||||
|
chosen_action: DndAction::empty(),
|
||||||
|
}));
|
||||||
|
for device in seat_data
|
||||||
|
.known_devices
|
||||||
|
.iter()
|
||||||
|
.filter(|d| d.same_client_as(&surface))
|
||||||
|
{
|
||||||
|
let action_choice = device
|
||||||
|
.user_data::<DataDeviceData>()
|
||||||
|
.unwrap()
|
||||||
|
.action_choice
|
||||||
|
.clone();
|
||||||
|
// create a data offer
|
||||||
|
let offer = client
|
||||||
|
.create_resource::<wl_data_offer::WlDataOffer>(device.version())
|
||||||
|
.map(|offer| {
|
||||||
|
implement_dnd_data_offer(
|
||||||
|
offer,
|
||||||
|
source.clone(),
|
||||||
|
offer_data.clone(),
|
||||||
|
action_choice,
|
||||||
|
)
|
||||||
|
}).unwrap();
|
||||||
|
// advertize the offer to the client
|
||||||
|
device.send(wl_data_device::Event::DataOffer { id: offer.clone() });
|
||||||
|
with_source_metadata(source, |meta| {
|
||||||
|
for mime_type in meta.mime_types.iter().cloned() {
|
||||||
|
offer.send(wl_data_offer::Event::Offer { mime_type })
|
||||||
|
}
|
||||||
|
offer.send(wl_data_offer::Event::SourceActions {
|
||||||
|
source_actions: meta.dnd_action.to_raw(),
|
||||||
|
});
|
||||||
|
}).unwrap();
|
||||||
|
device.send(wl_data_device::Event::Enter {
|
||||||
|
serial,
|
||||||
|
x: x - sx,
|
||||||
|
y: y - sy,
|
||||||
|
surface: surface.clone(),
|
||||||
|
id: Some(offer.clone()),
|
||||||
|
});
|
||||||
|
self.pending_offers.push(offer);
|
||||||
|
}
|
||||||
|
self.offer_data = Some(offer_data);
|
||||||
|
} else {
|
||||||
|
// only send if we are on a surface of the same client
|
||||||
|
if self.origin.same_client_as(&surface) {
|
||||||
|
for device in &seat_data.known_devices {
|
||||||
|
if device.same_client_as(&surface) {
|
||||||
|
device.send(wl_data_device::Event::Enter {
|
||||||
|
serial,
|
||||||
|
x: x - sx,
|
||||||
|
y: y - sy,
|
||||||
|
surface: surface.clone(),
|
||||||
|
id: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.current_focus = Some(surface);
|
||||||
|
} else {
|
||||||
|
// make a move
|
||||||
|
if self.data_source.is_some() || self.origin.same_client_as(&surface) {
|
||||||
|
for device in &seat_data.known_devices {
|
||||||
|
if device.same_client_as(&surface) {
|
||||||
|
device.send(wl_data_device::Event::Motion {
|
||||||
|
time,
|
||||||
|
x: x - sx,
|
||||||
|
y: y - sy,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(
|
||||||
|
&mut self,
|
||||||
|
handle: &mut PointerInnerHandle,
|
||||||
|
_button: u32,
|
||||||
|
_state: wl_pointer::ButtonState,
|
||||||
|
serial: u32,
|
||||||
|
time: u32,
|
||||||
|
) {
|
||||||
|
if handle.current_pressed().len() == 0 {
|
||||||
|
// the user dropped, proceed to the drop
|
||||||
|
let seat_data = self
|
||||||
|
.seat
|
||||||
|
.user_data()
|
||||||
|
.get::<Mutex<SeatData>>()
|
||||||
|
.unwrap()
|
||||||
|
.lock()
|
||||||
|
.unwrap();
|
||||||
|
let validated = if let Some(ref data) = self.offer_data {
|
||||||
|
let data = data.lock().unwrap();
|
||||||
|
data.accepted && (!data.chosen_action.is_empty())
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
if let Some(ref surface) = self.current_focus {
|
||||||
|
if self.data_source.is_some() || self.origin.same_client_as(&surface) {
|
||||||
|
for device in &seat_data.known_devices {
|
||||||
|
if device.same_client_as(surface) {
|
||||||
|
if validated {
|
||||||
|
device.send(wl_data_device::Event::Drop);
|
||||||
|
} else {
|
||||||
|
device.send(wl_data_device::Event::Leave);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(ref offer_data) = self.offer_data {
|
||||||
|
let mut data = offer_data.lock().unwrap();
|
||||||
|
if validated {
|
||||||
|
data.dropped = true;
|
||||||
|
} else {
|
||||||
|
data.active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(ref source) = self.data_source {
|
||||||
|
source.send(wl_data_source::Event::DndDropPerformed);
|
||||||
|
if !validated {
|
||||||
|
source.send(wl_data_source::Event::Cancelled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// in all cases abandon the drop
|
||||||
|
// no more buttons are pressed, release the grab
|
||||||
|
handle.unset_grab(serial, time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(&mut self, handle: &mut PointerInnerHandle, details: AxisFrame) {
|
||||||
|
// we just forward the axis events as is
|
||||||
|
handle.axis(details);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OfferData {
|
||||||
|
active: bool,
|
||||||
|
dropped: bool,
|
||||||
|
accepted: bool,
|
||||||
|
chosen_action: DndAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn implement_dnd_data_offer(
|
||||||
|
offer: NewResource<wl_data_offer::WlDataOffer>,
|
||||||
|
source: Resource<wl_data_source::WlDataSource>,
|
||||||
|
offer_data: Arc<Mutex<OfferData>>,
|
||||||
|
action_choice: Arc<Mutex<FnMut(DndAction, DndAction) -> DndAction + Send + 'static>>,
|
||||||
|
) -> Resource<wl_data_offer::WlDataOffer> {
|
||||||
|
use self::wl_data_offer::Request;
|
||||||
|
offer.implement(
|
||||||
|
move |req, offer| {
|
||||||
|
let mut data = offer_data.lock().unwrap();
|
||||||
|
match req {
|
||||||
|
Request::Accept { serial: _, mime_type } => {
|
||||||
|
if let Some(mtype) = mime_type {
|
||||||
|
if let Err(()) = with_source_metadata(&source, |meta| {
|
||||||
|
data.accepted = meta.mime_types.contains(&mtype);
|
||||||
|
}) {
|
||||||
|
data.accepted = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data.accepted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Request::Receive { mime_type, fd } => {
|
||||||
|
// check if the source and associated mime type is still valid
|
||||||
|
let valid = with_source_metadata(&source, |meta| meta.mime_types.contains(&mime_type))
|
||||||
|
.unwrap_or(false)
|
||||||
|
&& source.is_alive()
|
||||||
|
&& data.active;
|
||||||
|
if valid {
|
||||||
|
source.send(wl_data_source::Event::Send { mime_type, fd });
|
||||||
|
}
|
||||||
|
let _ = ::nix::unistd::close(fd);
|
||||||
|
}
|
||||||
|
Request::Destroy => {}
|
||||||
|
Request::Finish => {
|
||||||
|
if !data.active {
|
||||||
|
offer.post_error(
|
||||||
|
wl_data_offer::Error::InvalidFinish as u32,
|
||||||
|
"Cannot finish a data offer that is no longer active.".into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !data.accepted {
|
||||||
|
offer.post_error(
|
||||||
|
wl_data_offer::Error::InvalidFinish as u32,
|
||||||
|
"Cannot finish a data offer that has not been accepted.".into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !data.dropped {
|
||||||
|
offer.post_error(
|
||||||
|
wl_data_offer::Error::InvalidFinish as u32,
|
||||||
|
"Cannot finish a data offer that has not been dropped.".into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if data.chosen_action.is_empty() {
|
||||||
|
offer.post_error(
|
||||||
|
wl_data_offer::Error::InvalidFinish as u32,
|
||||||
|
"Cannot finish a data offer with no valid action.".into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
source.send(wl_data_source::Event::DndFinished);
|
||||||
|
data.active = false;
|
||||||
|
}
|
||||||
|
Request::SetActions {
|
||||||
|
dnd_actions,
|
||||||
|
preferred_action,
|
||||||
|
} => {
|
||||||
|
let preferred_action = DndAction::from_bits_truncate(preferred_action);
|
||||||
|
if ![DndAction::Move, DndAction::Copy, DndAction::Ask].contains(&preferred_action) {
|
||||||
|
offer.post_error(
|
||||||
|
wl_data_offer::Error::InvalidAction as u32,
|
||||||
|
"Invalid preferred action.".into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let source_actions =
|
||||||
|
with_source_metadata(&source, |meta| meta.dnd_action).unwrap_or(DndAction::empty());
|
||||||
|
let possible_actions = source_actions & DndAction::from_bits_truncate(dnd_actions);
|
||||||
|
data.chosen_action =
|
||||||
|
(&mut *action_choice.lock().unwrap())(possible_actions, preferred_action);
|
||||||
|
// check that the user provided callback respects that one precise action should be chosen
|
||||||
|
debug_assert!(
|
||||||
|
[DndAction::Move, DndAction::Copy, DndAction::Ask].contains(&data.chosen_action)
|
||||||
|
);
|
||||||
|
offer.send(wl_data_offer::Event::Action {
|
||||||
|
dnd_action: data.chosen_action.to_raw(),
|
||||||
|
});
|
||||||
|
source.send(wl_data_source::Event::Action {
|
||||||
|
dnd_action: data.chosen_action.to_raw(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None::<fn(_)>,
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,468 @@
|
||||||
|
//! Utilities for manipulating the data devices
|
||||||
|
//!
|
||||||
|
//! The data device is wayland's abstraction to represent both selection (copy/paste) and
|
||||||
|
//! drag'n'drop actions. This module provides logic to handle this part of the protocol.
|
||||||
|
//! Selection and drag'n'drop are per-seat notions.
|
||||||
|
//!
|
||||||
|
//! This module provides 2 main freestanding functions:
|
||||||
|
//!
|
||||||
|
//! - `init_data_device`: this function must be called during the compositor startup to initialize
|
||||||
|
//! the data device logic
|
||||||
|
//! - `set_data_device_focus`: this function sets the data device focus for a given seat; you'd
|
||||||
|
//! typically call it whenever the keyboard focus changes, to follow it (for example in the focus
|
||||||
|
//! hook of your keyboards)
|
||||||
|
//!
|
||||||
|
//! Using these two functions is enough for your clients to be able to interact with each other using
|
||||||
|
//! the data devices.
|
||||||
|
//!
|
||||||
|
//! The module also provides additionnal mechanisms allowing your compositor to see and interact with
|
||||||
|
//! the contents of the data device:
|
||||||
|
//!
|
||||||
|
//! - You can provide a callback closure to `init_data_device` to peek into the the actions of your clients
|
||||||
|
//! - the freestanding function `set_data_device_selection` allows you to set the contents of the selection
|
||||||
|
//! for your clients
|
||||||
|
//! - the freestanding function `start_dnd` allows you to initiate a drag'n'drop event from the compositor
|
||||||
|
//! itself and receive interactions of clients with it via an other dedicated callback.
|
||||||
|
//!
|
||||||
|
//! ## Initialization
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # extern crate wayland_server;
|
||||||
|
//! # #[macro_use] extern crate smithay;
|
||||||
|
//! use smithay::wayland::data_device::{init_data_device, default_action_chooser};
|
||||||
|
//!
|
||||||
|
//! # fn main(){
|
||||||
|
//! # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap();
|
||||||
|
//! # let mut display = wayland_server::Display::new(event_loop.handle());
|
||||||
|
//! // init the data device:
|
||||||
|
//! init_data_device(
|
||||||
|
//! &mut display, // the display
|
||||||
|
//! |dnd_event| { /* a callback to react to client DnD/selection actions */ },
|
||||||
|
//! default_action_chooser, // a closure to choose the DnD action depending on clients
|
||||||
|
//! // negociation
|
||||||
|
//! None // insert a logger here
|
||||||
|
//! );
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use std::os::unix::io::RawFd;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use wayland_server::{
|
||||||
|
protocol::{
|
||||||
|
wl_data_device,
|
||||||
|
wl_data_device_manager::{self, DndAction},
|
||||||
|
wl_data_offer, wl_data_source,
|
||||||
|
},
|
||||||
|
Client, Display, Global, NewResource, Resource,
|
||||||
|
};
|
||||||
|
|
||||||
|
use wayland::seat::Seat;
|
||||||
|
|
||||||
|
mod data_source;
|
||||||
|
mod dnd_grab;
|
||||||
|
mod server_dnd_grab;
|
||||||
|
|
||||||
|
pub use self::data_source::{with_source_metadata, SourceMetadata};
|
||||||
|
pub use self::server_dnd_grab::ServerDndEvent;
|
||||||
|
|
||||||
|
/// Events that are generated by interactions of the clients with the data device
|
||||||
|
pub enum DataDeviceEvent {
|
||||||
|
/// A client has set the selection
|
||||||
|
NewSelection(Option<Resource<wl_data_source::WlDataSource>>),
|
||||||
|
/// A client started a drag'n'drop as response to a user pointer action
|
||||||
|
DnDStarted(Option<Resource<wl_data_source::WlDataSource>>),
|
||||||
|
/// A client requested to read the server-set selection
|
||||||
|
SendSelection {
|
||||||
|
/// the requested mime type
|
||||||
|
mime_type: String,
|
||||||
|
/// the fd to write into
|
||||||
|
fd: RawFd,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Selection {
|
||||||
|
Empty,
|
||||||
|
Client(Resource<wl_data_source::WlDataSource>),
|
||||||
|
Compositor(SourceMetadata),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SeatData {
|
||||||
|
known_devices: Vec<Resource<wl_data_device::WlDataDevice>>,
|
||||||
|
selection: Selection,
|
||||||
|
log: ::slog::Logger,
|
||||||
|
current_focus: Option<Client>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SeatData {
|
||||||
|
fn set_selection(&mut self, new_selection: Selection) {
|
||||||
|
self.selection = new_selection;
|
||||||
|
self.send_selection();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_focus(&mut self, new_focus: Option<Client>) {
|
||||||
|
self.current_focus = new_focus;
|
||||||
|
self.send_selection();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_selection(&mut self) {
|
||||||
|
let client = match self.current_focus.as_ref() {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
// first sanitize the selection, reseting it to null if the client holding
|
||||||
|
// it dropped it
|
||||||
|
let cleanup = if let Selection::Client(ref data_source) = self.selection {
|
||||||
|
!data_source.is_alive()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
if cleanup {
|
||||||
|
self.selection = Selection::Empty;
|
||||||
|
}
|
||||||
|
// then send it if appropriate
|
||||||
|
match self.selection {
|
||||||
|
Selection::Empty => {
|
||||||
|
// send an empty selection
|
||||||
|
for dd in &self.known_devices {
|
||||||
|
// skip data devices not belonging to our client
|
||||||
|
if dd.client().map(|c| !c.equals(client)).unwrap_or(true) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
dd.send(wl_data_device::Event::Selection { id: None });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Selection::Client(ref data_source) => {
|
||||||
|
for dd in &self.known_devices {
|
||||||
|
// skip data devices not belonging to our client
|
||||||
|
if dd.client().map(|c| !c.equals(client)).unwrap_or(true) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let source = data_source.clone();
|
||||||
|
let log = self.log.clone();
|
||||||
|
// create a corresponding data offer
|
||||||
|
let offer = client
|
||||||
|
.create_resource::<wl_data_offer::WlDataOffer>(dd.version())
|
||||||
|
.unwrap()
|
||||||
|
.implement(
|
||||||
|
move |req, _offer| match req {
|
||||||
|
wl_data_offer::Request::Receive { fd, mime_type } => {
|
||||||
|
// check if the source and associated mime type is still valid
|
||||||
|
let valid = with_source_metadata(&source, |meta| {
|
||||||
|
meta.mime_types.contains(&mime_type)
|
||||||
|
}).unwrap_or(false)
|
||||||
|
&& source.is_alive();
|
||||||
|
if !valid {
|
||||||
|
// deny the receive
|
||||||
|
debug!(log, "Denying a wl_data_offer.receive with invalid source.");
|
||||||
|
} else {
|
||||||
|
source.send(wl_data_source::Event::Send { mime_type, fd });
|
||||||
|
}
|
||||||
|
let _ = ::nix::unistd::close(fd);
|
||||||
|
}
|
||||||
|
_ => { /* seleciton data offers only care about the `receive` event */ }
|
||||||
|
},
|
||||||
|
None::<fn(_)>,
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
// advertize the offer to the client
|
||||||
|
dd.send(wl_data_device::Event::DataOffer { id: offer.clone() });
|
||||||
|
with_source_metadata(data_source, |meta| {
|
||||||
|
for mime_type in meta.mime_types.iter().cloned() {
|
||||||
|
offer.send(wl_data_offer::Event::Offer { mime_type })
|
||||||
|
}
|
||||||
|
}).unwrap();
|
||||||
|
dd.send(wl_data_device::Event::Selection { id: Some(offer) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Selection::Compositor(ref meta) => {
|
||||||
|
for dd in &self.known_devices {
|
||||||
|
// skip data devices not belonging to our client
|
||||||
|
if dd.client().map(|c| !c.equals(client)).unwrap_or(true) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let log = self.log.clone();
|
||||||
|
let offer_meta = meta.clone();
|
||||||
|
let callback = dd.user_data::<DataDeviceData>().unwrap().callback.clone();
|
||||||
|
// create a corresponding data offer
|
||||||
|
let offer = client
|
||||||
|
.create_resource::<wl_data_offer::WlDataOffer>(dd.version())
|
||||||
|
.unwrap()
|
||||||
|
.implement(
|
||||||
|
move |req, _offer| match req {
|
||||||
|
wl_data_offer::Request::Receive { fd, mime_type } => {
|
||||||
|
// check if the associated mime type is valid
|
||||||
|
if !offer_meta.mime_types.contains(&mime_type) {
|
||||||
|
// deny the receive
|
||||||
|
debug!(log, "Denying a wl_data_offer.receive with invalid source.");
|
||||||
|
let _ = ::nix::unistd::close(fd);
|
||||||
|
} else {
|
||||||
|
(&mut *callback.lock().unwrap())(DataDeviceEvent::SendSelection {
|
||||||
|
mime_type,
|
||||||
|
fd,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => { /* seleciton data offers only care about the `receive` event */ }
|
||||||
|
},
|
||||||
|
None::<fn(_)>,
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
// advertize the offer to the client
|
||||||
|
dd.send(wl_data_device::Event::DataOffer { id: offer.clone() });
|
||||||
|
for mime_type in meta.mime_types.iter().cloned() {
|
||||||
|
offer.send(wl_data_offer::Event::Offer { mime_type })
|
||||||
|
}
|
||||||
|
dd.send(wl_data_device::Event::Selection { id: Some(offer) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SeatData {
|
||||||
|
fn new(log: ::slog::Logger) -> SeatData {
|
||||||
|
SeatData {
|
||||||
|
known_devices: Vec::new(),
|
||||||
|
selection: Selection::Empty,
|
||||||
|
log,
|
||||||
|
current_focus: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the data device global
|
||||||
|
///
|
||||||
|
/// You can provide a callback to peek into the actions of your clients over the data devices
|
||||||
|
/// (allowing you to retrieve the current selection buffer, or intercept DnD data). See the
|
||||||
|
/// `DataDeviceEvent` type for details about what notifications you can receive. Note that this
|
||||||
|
/// closure will not receive notifications about dnd actions the compositor initiated, see
|
||||||
|
/// `start_dnd` for details about that.
|
||||||
|
///
|
||||||
|
/// You also need to provide a `(DndAction, DndAction) -> DndAction` closure that will arbitrate
|
||||||
|
/// the choice of action resulting from a drag'n'drop session. Its first argument is the set of
|
||||||
|
/// available actions (which is the intersection of the actions supported by the source and targets)
|
||||||
|
/// and the second argument is the preferred action reported by the target. If no action should be
|
||||||
|
/// chosen (and thus the drag'n'drop should abort on drop), return `DndAction::empty()`.
|
||||||
|
pub fn init_data_device<F, C, L>(
|
||||||
|
display: &mut Display,
|
||||||
|
callback: C,
|
||||||
|
action_choice: F,
|
||||||
|
logger: L,
|
||||||
|
) -> Global<wl_data_device_manager::WlDataDeviceManager>
|
||||||
|
where
|
||||||
|
F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static,
|
||||||
|
C: FnMut(DataDeviceEvent) + Send + 'static,
|
||||||
|
L: Into<Option<::slog::Logger>>,
|
||||||
|
{
|
||||||
|
let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "data_device_mgr"));
|
||||||
|
let action_choice = Arc::new(Mutex::new(action_choice));
|
||||||
|
let callback = Arc::new(Mutex::new(callback));
|
||||||
|
let global = display.create_global(3, move |new_ddm, _version| {
|
||||||
|
implement_ddm(new_ddm, callback.clone(), action_choice.clone(), log.clone());
|
||||||
|
});
|
||||||
|
|
||||||
|
global
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the data device focus to a certain client for a given seat
|
||||||
|
pub fn set_data_device_focus(seat: &Seat, client: Option<Client>) {
|
||||||
|
// ensure the seat user_data is ready
|
||||||
|
// TODO: find a better way to retrieve a logger without requiring the user
|
||||||
|
// to provide one ?
|
||||||
|
// This should be a rare path anyway, it is unlikely that a client gets focus
|
||||||
|
// before initializing its data device, which would already init the user_data.
|
||||||
|
seat.user_data().insert_if_missing(|| {
|
||||||
|
Mutex::new(SeatData::new(
|
||||||
|
seat.arc.log.new(o!("smithay_module" => "data_device_mgr")),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
let seat_data = seat.user_data().get::<Mutex<SeatData>>().unwrap();
|
||||||
|
seat_data.lock().unwrap().set_focus(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a compositor-provided selection for this seat
|
||||||
|
///
|
||||||
|
/// You need to provide the available mime types for this selection.
|
||||||
|
///
|
||||||
|
/// Whenever a client requests to read the selection, your callback will
|
||||||
|
/// receive a `DataDeviceEvent::SendSelection` event.
|
||||||
|
pub fn set_data_device_selection(seat: &Seat, mime_types: Vec<String>) {
|
||||||
|
// TODO: same question as in set_data_device_focus
|
||||||
|
seat.user_data().insert_if_missing(|| {
|
||||||
|
Mutex::new(SeatData::new(
|
||||||
|
seat.arc.log.new(o!("smithay_module" => "data_device_mgr")),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
let seat_data = seat.user_data().get::<Mutex<SeatData>>().unwrap();
|
||||||
|
seat_data
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.set_selection(Selection::Compositor(SourceMetadata {
|
||||||
|
mime_types,
|
||||||
|
dnd_action: DndAction::empty(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a drag'n'drop from a ressource controlled by the compositor
|
||||||
|
///
|
||||||
|
/// You'll receive events generated by the interaction of clients with your
|
||||||
|
/// drag'n'drop in the provided callback. See `SeverDndEvent` for details about
|
||||||
|
/// which events can be generated and what response is expected from you to them.
|
||||||
|
pub fn start_dnd<C>(seat: &Seat, serial: u32, metadata: SourceMetadata, callback: C)
|
||||||
|
where
|
||||||
|
C: FnMut(ServerDndEvent) + Send + 'static,
|
||||||
|
{
|
||||||
|
// TODO: same question as in set_data_device_focus
|
||||||
|
seat.user_data().insert_if_missing(|| {
|
||||||
|
Mutex::new(SeatData::new(
|
||||||
|
seat.arc.log.new(o!("smithay_module" => "data_device_mgr")),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
if let Some(pointer) = seat.get_pointer() {
|
||||||
|
pointer.set_grab(
|
||||||
|
server_dnd_grab::ServerDnDGrab::new(metadata, seat.clone(), Arc::new(Mutex::new(callback))),
|
||||||
|
serial,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn implement_ddm<F, C>(
|
||||||
|
new_ddm: NewResource<wl_data_device_manager::WlDataDeviceManager>,
|
||||||
|
callback: Arc<Mutex<C>>,
|
||||||
|
action_choice: Arc<Mutex<F>>,
|
||||||
|
log: ::slog::Logger,
|
||||||
|
) -> Resource<wl_data_device_manager::WlDataDeviceManager>
|
||||||
|
where
|
||||||
|
F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static,
|
||||||
|
C: FnMut(DataDeviceEvent) + Send + 'static,
|
||||||
|
{
|
||||||
|
use self::wl_data_device_manager::Request;
|
||||||
|
new_ddm.implement(
|
||||||
|
move |req, _ddm| match req {
|
||||||
|
Request::CreateDataSource { id } => {
|
||||||
|
self::data_source::implement_data_source(id);
|
||||||
|
}
|
||||||
|
Request::GetDataDevice { id, seat } => match Seat::from_resource(&seat) {
|
||||||
|
Some(seat) => {
|
||||||
|
// ensure the seat user_data is ready
|
||||||
|
seat.user_data()
|
||||||
|
.insert_if_missing(|| Mutex::new(SeatData::new(log.clone())));
|
||||||
|
let seat_data = seat.user_data().get::<Mutex<SeatData>>().unwrap();
|
||||||
|
let data_device = implement_data_device(
|
||||||
|
id,
|
||||||
|
seat.clone(),
|
||||||
|
callback.clone(),
|
||||||
|
action_choice.clone(),
|
||||||
|
log.clone(),
|
||||||
|
);
|
||||||
|
seat_data.lock().unwrap().known_devices.push(data_device);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
error!(log, "Unmanaged seat given to a data device.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
None::<fn(_)>,
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DataDeviceData {
|
||||||
|
callback: Arc<Mutex<FnMut(DataDeviceEvent) + Send + 'static>>,
|
||||||
|
action_choice: Arc<Mutex<FnMut(DndAction, DndAction) -> DndAction + Send + 'static>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn implement_data_device<F, C>(
|
||||||
|
new_dd: NewResource<wl_data_device::WlDataDevice>,
|
||||||
|
seat: Seat,
|
||||||
|
callback: Arc<Mutex<C>>,
|
||||||
|
action_choice: Arc<Mutex<F>>,
|
||||||
|
log: ::slog::Logger,
|
||||||
|
) -> Resource<wl_data_device::WlDataDevice>
|
||||||
|
where
|
||||||
|
F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static,
|
||||||
|
C: FnMut(DataDeviceEvent) + Send + 'static,
|
||||||
|
{
|
||||||
|
use self::wl_data_device::Request;
|
||||||
|
let dd_data = DataDeviceData {
|
||||||
|
callback: callback.clone(),
|
||||||
|
action_choice,
|
||||||
|
};
|
||||||
|
new_dd.implement(
|
||||||
|
move |req, dd| match req {
|
||||||
|
Request::StartDrag {
|
||||||
|
source,
|
||||||
|
origin,
|
||||||
|
icon: _,
|
||||||
|
serial,
|
||||||
|
} => {
|
||||||
|
/* TODO: handle the icon */
|
||||||
|
if let Some(pointer) = seat.get_pointer() {
|
||||||
|
if pointer.has_grab(serial) {
|
||||||
|
// The StartDrag is in response to a pointer implicit grab, all is good
|
||||||
|
(&mut *callback.lock().unwrap())(DataDeviceEvent::DnDStarted(source.clone()));
|
||||||
|
pointer.set_grab(dnd_grab::DnDGrab::new(source, origin, seat.clone()), serial);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug!(log, "denying drag from client without implicit grab");
|
||||||
|
}
|
||||||
|
Request::SetSelection { source, serial: _ } => {
|
||||||
|
if let Some(keyboard) = seat.get_keyboard() {
|
||||||
|
if dd
|
||||||
|
.client()
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| keyboard.has_focus(c))
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
let seat_data = seat.user_data().get::<Mutex<SeatData>>().unwrap();
|
||||||
|
(&mut *callback.lock().unwrap())(DataDeviceEvent::NewSelection(source.clone()));
|
||||||
|
// The client has kbd focus, it can set the selection
|
||||||
|
seat_data
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.set_selection(source.map(Selection::Client).unwrap_or(Selection::Empty));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug!(log, "denying setting selection by a non-focused client");
|
||||||
|
}
|
||||||
|
Request::Release => {
|
||||||
|
// Clean up the known devices
|
||||||
|
seat.user_data()
|
||||||
|
.get::<Mutex<SeatData>>()
|
||||||
|
.unwrap()
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.known_devices
|
||||||
|
.retain(|ndd| ndd.is_alive() && (!ndd.equals(&dd)))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None::<fn(_)>,
|
||||||
|
dd_data,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A simple action chooser for DnD negociation
|
||||||
|
///
|
||||||
|
/// If the preferred action is available, it'll pick it. Otherwise, it'll pick the first
|
||||||
|
/// available in the following order: Ask, Copy, Move.
|
||||||
|
pub fn default_action_chooser(available: DndAction, preferred: DndAction) -> DndAction {
|
||||||
|
// if the preferred action is valid (a single action) and in the available actions, use it
|
||||||
|
// otherwise, follow a fallback stategy
|
||||||
|
if [DndAction::Move, DndAction::Copy, DndAction::Ask].contains(&preferred)
|
||||||
|
&& available.contains(preferred)
|
||||||
|
{
|
||||||
|
preferred
|
||||||
|
} else if available.contains(DndAction::Ask) {
|
||||||
|
DndAction::Ask
|
||||||
|
} else if available.contains(DndAction::Copy) {
|
||||||
|
DndAction::Copy
|
||||||
|
} else if available.contains(DndAction::Move) {
|
||||||
|
DndAction::Move
|
||||||
|
} else {
|
||||||
|
DndAction::empty()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,320 @@
|
||||||
|
use std::os::unix::io::RawFd;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use wayland_server::{
|
||||||
|
protocol::{wl_data_device, wl_data_device_manager::DndAction, wl_data_offer, wl_pointer, wl_surface},
|
||||||
|
NewResource, Resource,
|
||||||
|
};
|
||||||
|
|
||||||
|
use wayland::seat::{AxisFrame, PointerGrab, PointerInnerHandle, Seat};
|
||||||
|
|
||||||
|
use super::{DataDeviceData, SeatData};
|
||||||
|
|
||||||
|
/// Event generated by the interactions of clients with a server innitiated drag'n'drop
|
||||||
|
pub enum ServerDndEvent {
|
||||||
|
/// The client chose an action
|
||||||
|
Action(DndAction),
|
||||||
|
/// The DnD resource was dropped by the user
|
||||||
|
///
|
||||||
|
/// After that, the client can still interract with your ressource
|
||||||
|
Dropped,
|
||||||
|
/// The Dnd was cancelled
|
||||||
|
///
|
||||||
|
/// The client can no longer interact
|
||||||
|
Cancelled,
|
||||||
|
/// The client requested for data to be sent
|
||||||
|
Send {
|
||||||
|
/// The requested mime type
|
||||||
|
mime_type: String,
|
||||||
|
/// The FD to write into
|
||||||
|
fd: RawFd,
|
||||||
|
},
|
||||||
|
/// The client has finished interacting with the resource
|
||||||
|
///
|
||||||
|
/// This can only happen after the resource was dropped.
|
||||||
|
Finished,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct ServerDnDGrab<C: 'static> {
|
||||||
|
metadata: super::SourceMetadata,
|
||||||
|
current_focus: Option<Resource<wl_surface::WlSurface>>,
|
||||||
|
pending_offers: Vec<Resource<wl_data_offer::WlDataOffer>>,
|
||||||
|
offer_data: Option<Arc<Mutex<OfferData>>>,
|
||||||
|
seat: Seat,
|
||||||
|
callback: Arc<Mutex<C>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: 'static> ServerDnDGrab<C> {
|
||||||
|
pub(crate) fn new(
|
||||||
|
metadata: super::SourceMetadata,
|
||||||
|
seat: Seat,
|
||||||
|
callback: Arc<Mutex<C>>,
|
||||||
|
) -> ServerDnDGrab<C> {
|
||||||
|
ServerDnDGrab {
|
||||||
|
metadata,
|
||||||
|
current_focus: None,
|
||||||
|
pending_offers: Vec::with_capacity(1),
|
||||||
|
offer_data: None,
|
||||||
|
seat,
|
||||||
|
callback,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> PointerGrab for ServerDnDGrab<C>
|
||||||
|
where
|
||||||
|
C: FnMut(ServerDndEvent) + Send + 'static,
|
||||||
|
{
|
||||||
|
fn motion(
|
||||||
|
&mut self,
|
||||||
|
_handle: &mut PointerInnerHandle,
|
||||||
|
location: (f64, f64),
|
||||||
|
focus: Option<(Resource<wl_surface::WlSurface>, (f64, f64))>,
|
||||||
|
serial: u32,
|
||||||
|
time: u32,
|
||||||
|
) {
|
||||||
|
let (x, y) = location;
|
||||||
|
let seat_data = self
|
||||||
|
.seat
|
||||||
|
.user_data()
|
||||||
|
.get::<Mutex<SeatData>>()
|
||||||
|
.unwrap()
|
||||||
|
.lock()
|
||||||
|
.unwrap();
|
||||||
|
if focus.as_ref().map(|&(ref s, _)| s) != self.current_focus.as_ref() {
|
||||||
|
// focus changed, we need to make a leave if appropriate
|
||||||
|
if let Some(surface) = self.current_focus.take() {
|
||||||
|
for device in &seat_data.known_devices {
|
||||||
|
if device.same_client_as(&surface) {
|
||||||
|
device.send(wl_data_device::Event::Leave);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// disable the offers
|
||||||
|
self.pending_offers.clear();
|
||||||
|
if let Some(offer_data) = self.offer_data.take() {
|
||||||
|
offer_data.lock().unwrap().active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((surface, (sx, sy))) = focus {
|
||||||
|
// early return if the surface is no longer valid
|
||||||
|
let client = match surface.client() {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
if self.current_focus.is_none() {
|
||||||
|
// We entered a new surface, send the data offer
|
||||||
|
let offer_data = Arc::new(Mutex::new(OfferData {
|
||||||
|
active: true,
|
||||||
|
dropped: false,
|
||||||
|
accepted: true,
|
||||||
|
chosen_action: DndAction::empty(),
|
||||||
|
}));
|
||||||
|
for device in seat_data
|
||||||
|
.known_devices
|
||||||
|
.iter()
|
||||||
|
.filter(|d| d.same_client_as(&surface))
|
||||||
|
{
|
||||||
|
let action_choice = device
|
||||||
|
.user_data::<DataDeviceData>()
|
||||||
|
.unwrap()
|
||||||
|
.action_choice
|
||||||
|
.clone();
|
||||||
|
// create a data offer
|
||||||
|
let offer = client
|
||||||
|
.create_resource::<wl_data_offer::WlDataOffer>(device.version())
|
||||||
|
.map(|offer| {
|
||||||
|
implement_dnd_data_offer(
|
||||||
|
offer,
|
||||||
|
self.metadata.clone(),
|
||||||
|
offer_data.clone(),
|
||||||
|
self.callback.clone(),
|
||||||
|
action_choice,
|
||||||
|
)
|
||||||
|
}).unwrap();
|
||||||
|
// advertize the offer to the client
|
||||||
|
device.send(wl_data_device::Event::DataOffer { id: offer.clone() });
|
||||||
|
for mime_type in self.metadata.mime_types.iter().cloned() {
|
||||||
|
offer.send(wl_data_offer::Event::Offer { mime_type })
|
||||||
|
}
|
||||||
|
offer.send(wl_data_offer::Event::SourceActions {
|
||||||
|
source_actions: self.metadata.dnd_action.to_raw(),
|
||||||
|
});
|
||||||
|
device.send(wl_data_device::Event::Enter {
|
||||||
|
serial,
|
||||||
|
x: x - sx,
|
||||||
|
y: y - sy,
|
||||||
|
surface: surface.clone(),
|
||||||
|
id: Some(offer.clone()),
|
||||||
|
});
|
||||||
|
self.pending_offers.push(offer);
|
||||||
|
}
|
||||||
|
self.offer_data = Some(offer_data);
|
||||||
|
self.current_focus = Some(surface);
|
||||||
|
} else {
|
||||||
|
// make a move
|
||||||
|
for device in &seat_data.known_devices {
|
||||||
|
if device.same_client_as(&surface) {
|
||||||
|
device.send(wl_data_device::Event::Motion {
|
||||||
|
time,
|
||||||
|
x: x - sx,
|
||||||
|
y: y - sy,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(
|
||||||
|
&mut self,
|
||||||
|
handle: &mut PointerInnerHandle,
|
||||||
|
_button: u32,
|
||||||
|
_state: wl_pointer::ButtonState,
|
||||||
|
serial: u32,
|
||||||
|
time: u32,
|
||||||
|
) {
|
||||||
|
if handle.current_pressed().len() == 0 {
|
||||||
|
// the user dropped, proceed to the drop
|
||||||
|
let seat_data = self
|
||||||
|
.seat
|
||||||
|
.user_data()
|
||||||
|
.get::<Mutex<SeatData>>()
|
||||||
|
.unwrap()
|
||||||
|
.lock()
|
||||||
|
.unwrap();
|
||||||
|
let validated = if let Some(ref data) = self.offer_data {
|
||||||
|
let data = data.lock().unwrap();
|
||||||
|
data.accepted && (!data.chosen_action.is_empty())
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
if let Some(ref surface) = self.current_focus {
|
||||||
|
for device in &seat_data.known_devices {
|
||||||
|
if device.same_client_as(surface) {
|
||||||
|
if validated {
|
||||||
|
device.send(wl_data_device::Event::Drop);
|
||||||
|
} else {
|
||||||
|
device.send(wl_data_device::Event::Leave);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(ref offer_data) = self.offer_data {
|
||||||
|
let mut data = offer_data.lock().unwrap();
|
||||||
|
if validated {
|
||||||
|
data.dropped = true;
|
||||||
|
} else {
|
||||||
|
data.active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut callback = self.callback.lock().unwrap();
|
||||||
|
(&mut *callback)(ServerDndEvent::Dropped);
|
||||||
|
if !validated {
|
||||||
|
(&mut *callback)(ServerDndEvent::Cancelled);
|
||||||
|
}
|
||||||
|
// in all cases abandon the drop
|
||||||
|
// no more buttons are pressed, release the grab
|
||||||
|
handle.unset_grab(serial, time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(&mut self, handle: &mut PointerInnerHandle, details: AxisFrame) {
|
||||||
|
// we just forward the axis events as is
|
||||||
|
handle.axis(details);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OfferData {
|
||||||
|
active: bool,
|
||||||
|
dropped: bool,
|
||||||
|
accepted: bool,
|
||||||
|
chosen_action: DndAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn implement_dnd_data_offer<C>(
|
||||||
|
offer: NewResource<wl_data_offer::WlDataOffer>,
|
||||||
|
metadata: super::SourceMetadata,
|
||||||
|
offer_data: Arc<Mutex<OfferData>>,
|
||||||
|
callback: Arc<Mutex<C>>,
|
||||||
|
action_choice: Arc<Mutex<FnMut(DndAction, DndAction) -> DndAction + Send + 'static>>,
|
||||||
|
) -> Resource<wl_data_offer::WlDataOffer>
|
||||||
|
where
|
||||||
|
C: FnMut(ServerDndEvent) + Send + 'static,
|
||||||
|
{
|
||||||
|
use self::wl_data_offer::Request;
|
||||||
|
offer.implement(
|
||||||
|
move |req, offer| {
|
||||||
|
let mut data = offer_data.lock().unwrap();
|
||||||
|
match req {
|
||||||
|
Request::Accept { serial: _, mime_type } => {
|
||||||
|
if let Some(mtype) = mime_type {
|
||||||
|
data.accepted = metadata.mime_types.contains(&mtype);
|
||||||
|
} else {
|
||||||
|
data.accepted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Request::Receive { mime_type, fd } => {
|
||||||
|
// check if the source and associated mime type is still valid
|
||||||
|
if metadata.mime_types.contains(&mime_type) && data.active {
|
||||||
|
(&mut *callback.lock().unwrap())(ServerDndEvent::Send { mime_type, fd });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Request::Destroy => {}
|
||||||
|
Request::Finish => {
|
||||||
|
if !data.active {
|
||||||
|
offer.post_error(
|
||||||
|
wl_data_offer::Error::InvalidFinish as u32,
|
||||||
|
"Cannot finish a data offer that is no longer active.".into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !data.accepted {
|
||||||
|
offer.post_error(
|
||||||
|
wl_data_offer::Error::InvalidFinish as u32,
|
||||||
|
"Cannot finish a data offer that has not been accepted.".into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !data.dropped {
|
||||||
|
offer.post_error(
|
||||||
|
wl_data_offer::Error::InvalidFinish as u32,
|
||||||
|
"Cannot finish a data offer that has not been dropped.".into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if data.chosen_action.is_empty() {
|
||||||
|
offer.post_error(
|
||||||
|
wl_data_offer::Error::InvalidFinish as u32,
|
||||||
|
"Cannot finish a data offer with no valid action.".into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
(&mut *callback.lock().unwrap())(ServerDndEvent::Finished);
|
||||||
|
data.active = false;
|
||||||
|
}
|
||||||
|
Request::SetActions {
|
||||||
|
dnd_actions,
|
||||||
|
preferred_action,
|
||||||
|
} => {
|
||||||
|
let preferred_action = DndAction::from_bits_truncate(preferred_action);
|
||||||
|
if ![DndAction::Move, DndAction::Copy, DndAction::Ask].contains(&preferred_action) {
|
||||||
|
offer.post_error(
|
||||||
|
wl_data_offer::Error::InvalidAction as u32,
|
||||||
|
"Invalid preferred action.".into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let possible_actions = metadata.dnd_action & DndAction::from_bits_truncate(dnd_actions);
|
||||||
|
data.chosen_action =
|
||||||
|
(&mut *action_choice.lock().unwrap())(possible_actions, preferred_action);
|
||||||
|
// check that the user provided callback respects that one precise action should be chosen
|
||||||
|
debug_assert!(
|
||||||
|
[DndAction::Move, DndAction::Copy, DndAction::Ask].contains(&data.chosen_action)
|
||||||
|
);
|
||||||
|
offer.send(wl_data_offer::Event::Action {
|
||||||
|
dnd_action: data.chosen_action.to_raw(),
|
||||||
|
});
|
||||||
|
(&mut *callback.lock().unwrap())(ServerDndEvent::Action(data.chosen_action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None::<fn(_)>,
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
}
|
|
@ -15,6 +15,7 @@
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
pub mod compositor;
|
pub mod compositor;
|
||||||
|
pub mod data_device;
|
||||||
pub mod output;
|
pub mod output;
|
||||||
pub mod seat;
|
pub mod seat;
|
||||||
pub mod shell;
|
pub mod shell;
|
||||||
|
|
|
@ -11,7 +11,7 @@ use wayland_server::{
|
||||||
wl_keyboard::{Event, KeyState as WlKeyState, KeymapFormat, Request, WlKeyboard},
|
wl_keyboard::{Event, KeyState as WlKeyState, KeymapFormat, Request, WlKeyboard},
|
||||||
wl_surface::WlSurface,
|
wl_surface::WlSurface,
|
||||||
},
|
},
|
||||||
NewResource, Resource,
|
Client, NewResource, Resource,
|
||||||
};
|
};
|
||||||
use xkbcommon::xkb;
|
use xkbcommon::xkb;
|
||||||
pub use xkbcommon::xkb::{keysyms, Keysym};
|
pub use xkbcommon::xkb::{keysyms, Keysym};
|
||||||
|
@ -112,6 +112,7 @@ struct KbdInternal {
|
||||||
state: xkb::State,
|
state: xkb::State,
|
||||||
repeat_rate: i32,
|
repeat_rate: i32,
|
||||||
repeat_delay: i32,
|
repeat_delay: i32,
|
||||||
|
focus_hook: Box<FnMut(Option<&Resource<WlSurface>>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is OK because all parts of `xkb` will remain on the
|
// This is OK because all parts of `xkb` will remain on the
|
||||||
|
@ -119,7 +120,12 @@ struct KbdInternal {
|
||||||
unsafe impl Send for KbdInternal {}
|
unsafe impl Send for KbdInternal {}
|
||||||
|
|
||||||
impl KbdInternal {
|
impl KbdInternal {
|
||||||
fn new(xkb_config: XkbConfig, repeat_rate: i32, repeat_delay: i32) -> Result<KbdInternal, ()> {
|
fn new(
|
||||||
|
xkb_config: XkbConfig,
|
||||||
|
repeat_rate: i32,
|
||||||
|
repeat_delay: i32,
|
||||||
|
focus_hook: Box<FnMut(Option<&Resource<WlSurface>>)>,
|
||||||
|
) -> Result<KbdInternal, ()> {
|
||||||
// we create a new contex for each keyboard because libxkbcommon is actually NOT threadsafe
|
// we create a new contex for each keyboard because libxkbcommon is actually NOT threadsafe
|
||||||
// so confining it inside the KbdInternal allows us to use Rusts mutability rules to make
|
// so confining it inside the KbdInternal allows us to use Rusts mutability rules to make
|
||||||
// sure nothing goes wrong.
|
// sure nothing goes wrong.
|
||||||
|
@ -146,6 +152,7 @@ impl KbdInternal {
|
||||||
state,
|
state,
|
||||||
repeat_rate,
|
repeat_rate,
|
||||||
repeat_delay,
|
repeat_delay,
|
||||||
|
focus_hook,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,21 +226,26 @@ pub enum Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a keyboard handler from a set of RMLVO rules
|
/// Create a keyboard handler from a set of RMLVO rules
|
||||||
pub(crate) fn create_keyboard_handler(
|
pub(crate) fn create_keyboard_handler<F>(
|
||||||
xkb_config: XkbConfig,
|
xkb_config: XkbConfig,
|
||||||
repeat_delay: i32,
|
repeat_delay: i32,
|
||||||
repeat_rate: i32,
|
repeat_rate: i32,
|
||||||
logger: &::slog::Logger,
|
logger: &::slog::Logger,
|
||||||
) -> Result<KeyboardHandle, Error> {
|
focus_hook: F,
|
||||||
|
) -> Result<KeyboardHandle, Error>
|
||||||
|
where
|
||||||
|
F: FnMut(Option<&Resource<WlSurface>>) + 'static,
|
||||||
|
{
|
||||||
let log = logger.new(o!("smithay_module" => "xkbcommon_handler"));
|
let log = logger.new(o!("smithay_module" => "xkbcommon_handler"));
|
||||||
info!(log, "Initializing a xkbcommon handler with keymap query";
|
info!(log, "Initializing a xkbcommon handler with keymap query";
|
||||||
"rules" => xkb_config.rules, "model" => xkb_config.model, "layout" => xkb_config.layout,
|
"rules" => xkb_config.rules, "model" => xkb_config.model, "layout" => xkb_config.layout,
|
||||||
"variant" => xkb_config.variant, "options" => &xkb_config.options
|
"variant" => xkb_config.variant, "options" => &xkb_config.options
|
||||||
);
|
);
|
||||||
let internal = KbdInternal::new(xkb_config, repeat_rate, repeat_delay).map_err(|_| {
|
let internal =
|
||||||
debug!(log, "Loading keymap failed");
|
KbdInternal::new(xkb_config, repeat_rate, repeat_delay, Box::new(focus_hook)).map_err(|_| {
|
||||||
Error::BadKeymap
|
debug!(log, "Loading keymap failed");
|
||||||
})?;
|
Error::BadKeymap
|
||||||
|
})?;
|
||||||
|
|
||||||
info!(log, "Loaded Keymap"; "name" => internal.keymap.layouts().next());
|
info!(log, "Loaded Keymap"; "name" => internal.keymap.layouts().next());
|
||||||
|
|
||||||
|
@ -382,6 +394,14 @@ impl KeyboardHandle {
|
||||||
keys: keys.clone(),
|
keys: keys.clone(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
{
|
||||||
|
let KbdInternal {
|
||||||
|
ref focus,
|
||||||
|
ref mut focus_hook,
|
||||||
|
..
|
||||||
|
} = *guard;
|
||||||
|
focus_hook(focus.as_ref());
|
||||||
|
}
|
||||||
if guard.focus.is_some() {
|
if guard.focus.is_some() {
|
||||||
trace!(self.arc.logger, "Focus set to new surface");
|
trace!(self.arc.logger, "Focus set to new surface");
|
||||||
} else {
|
} else {
|
||||||
|
@ -392,6 +412,19 @@ impl KeyboardHandle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if given client currently has keyboard focus
|
||||||
|
pub fn has_focus(&self, client: &Client) -> bool {
|
||||||
|
self.arc
|
||||||
|
.internal
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.focus
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|f| f.client())
|
||||||
|
.map(|c| c.equals(client))
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
/// Register a new keyboard to this handler
|
/// Register a new keyboard to this handler
|
||||||
///
|
///
|
||||||
/// The keymap will automatically be sent to it
|
/// The keymap will automatically be sent to it
|
||||||
|
|
|
@ -64,16 +64,26 @@ pub use self::{
|
||||||
pointer::{AxisFrame, PointerGrab, PointerHandle, PointerInnerHandle},
|
pointer::{AxisFrame, PointerGrab, PointerHandle, PointerInnerHandle},
|
||||||
};
|
};
|
||||||
|
|
||||||
use wayland_server::{protocol::wl_seat, Display, Global, NewResource, Resource};
|
use wayland_commons::utils::UserDataMap;
|
||||||
|
|
||||||
|
use wayland_server::{
|
||||||
|
protocol::{wl_seat, wl_surface},
|
||||||
|
Display, Global, NewResource, Resource,
|
||||||
|
};
|
||||||
|
|
||||||
struct Inner {
|
struct Inner {
|
||||||
log: ::slog::Logger,
|
|
||||||
name: String,
|
|
||||||
pointer: Option<PointerHandle>,
|
pointer: Option<PointerHandle>,
|
||||||
keyboard: Option<KeyboardHandle>,
|
keyboard: Option<KeyboardHandle>,
|
||||||
known_seats: Vec<Resource<wl_seat::WlSeat>>,
|
known_seats: Vec<Resource<wl_seat::WlSeat>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct SeatArc {
|
||||||
|
inner: Mutex<Inner>,
|
||||||
|
user_data: UserDataMap,
|
||||||
|
pub(crate) log: ::slog::Logger,
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl Inner {
|
impl Inner {
|
||||||
fn compute_caps(&self) -> wl_seat::Capability {
|
fn compute_caps(&self) -> wl_seat::Capability {
|
||||||
let mut caps = wl_seat::Capability::empty();
|
let mut caps = wl_seat::Capability::empty();
|
||||||
|
@ -101,9 +111,13 @@ impl Inner {
|
||||||
///
|
///
|
||||||
/// It is directly inserted in the event loop by its `new` method.
|
/// It is directly inserted in the event loop by its `new` method.
|
||||||
///
|
///
|
||||||
|
/// This is an handle to the inner logic, it can be cloned and shared accross
|
||||||
|
/// threads.
|
||||||
|
///
|
||||||
/// See module-level documentation for details of use.
|
/// See module-level documentation for details of use.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Seat {
|
pub struct Seat {
|
||||||
inner: Arc<Mutex<Inner>>,
|
pub(crate) arc: Arc<SeatArc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Seat {
|
impl Seat {
|
||||||
|
@ -120,20 +134,23 @@ impl Seat {
|
||||||
L: Into<Option<::slog::Logger>>,
|
L: Into<Option<::slog::Logger>>,
|
||||||
{
|
{
|
||||||
let log = ::slog_or_stdlog(logger);
|
let log = ::slog_or_stdlog(logger);
|
||||||
let inner = Arc::new(Mutex::new(Inner {
|
let arc = Arc::new(SeatArc {
|
||||||
|
inner: Mutex::new(Inner {
|
||||||
|
pointer: None,
|
||||||
|
keyboard: None,
|
||||||
|
known_seats: Vec::new(),
|
||||||
|
}),
|
||||||
log: log.new(o!("smithay_module" => "seat_handler", "seat_name" => name.clone())),
|
log: log.new(o!("smithay_module" => "seat_handler", "seat_name" => name.clone())),
|
||||||
name,
|
name,
|
||||||
pointer: None,
|
user_data: UserDataMap::new(),
|
||||||
keyboard: None,
|
});
|
||||||
known_seats: Vec::new(),
|
let seat = Seat { arc: arc.clone() };
|
||||||
}));
|
|
||||||
let seat = Seat { inner: inner.clone() };
|
|
||||||
let global = display.create_global(5, move |new_seat, _version| {
|
let global = display.create_global(5, move |new_seat, _version| {
|
||||||
let seat = implement_seat(new_seat, inner.clone());
|
let seat = implement_seat(new_seat, arc.clone());
|
||||||
let mut inner = inner.lock().unwrap();
|
let mut inner = arc.inner.lock().unwrap();
|
||||||
if seat.version() >= 2 {
|
if seat.version() >= 2 {
|
||||||
seat.send(wl_seat::Event::Name {
|
seat.send(wl_seat::Event::Name {
|
||||||
name: inner.name.clone(),
|
name: arc.name.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
seat.send(wl_seat::Event::Capabilities {
|
seat.send(wl_seat::Event::Capabilities {
|
||||||
|
@ -144,6 +161,16 @@ impl Seat {
|
||||||
(seat, global)
|
(seat, global)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempt to retrieve a `Seat` from an existing resource
|
||||||
|
pub fn from_resource(seat: &Resource<wl_seat::WlSeat>) -> Option<Seat> {
|
||||||
|
seat.user_data::<Arc<SeatArc>>().cloned().map(|arc| Seat { arc })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Acces the `UserDataMap` associated with this `Seat`
|
||||||
|
pub fn user_data(&self) -> &UserDataMap {
|
||||||
|
&self.arc.user_data
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds the pointer capability to this seat
|
/// Adds the pointer capability to this seat
|
||||||
///
|
///
|
||||||
/// You are provided a `PointerHandle`, which allows you to send input events
|
/// You are provided a `PointerHandle`, which allows you to send input events
|
||||||
|
@ -153,7 +180,7 @@ impl Seat {
|
||||||
/// will overwrite it, and will be seen by the clients as if the
|
/// will overwrite it, and will be seen by the clients as if the
|
||||||
/// mouse was unplugged and a new one was plugged.
|
/// mouse was unplugged and a new one was plugged.
|
||||||
pub fn add_pointer(&mut self) -> PointerHandle {
|
pub fn add_pointer(&mut self) -> PointerHandle {
|
||||||
let mut inner = self.inner.lock().unwrap();
|
let mut inner = self.arc.inner.lock().unwrap();
|
||||||
let pointer = self::pointer::create_pointer_handler();
|
let pointer = self::pointer::create_pointer_handler();
|
||||||
if inner.pointer.is_some() {
|
if inner.pointer.is_some() {
|
||||||
// there is already a pointer, remove it and notify the clients
|
// there is already a pointer, remove it and notify the clients
|
||||||
|
@ -166,11 +193,16 @@ impl Seat {
|
||||||
pointer
|
pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Access the pointer of this seat if any
|
||||||
|
pub fn get_pointer(&self) -> Option<PointerHandle> {
|
||||||
|
self.arc.inner.lock().unwrap().pointer.clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove the pointer capability from this seat
|
/// Remove the pointer capability from this seat
|
||||||
///
|
///
|
||||||
/// Clients will be appropriately notified.
|
/// Clients will be appropriately notified.
|
||||||
pub fn remove_pointer(&mut self) {
|
pub fn remove_pointer(&mut self) {
|
||||||
let mut inner = self.inner.lock().unwrap();
|
let mut inner = self.arc.inner.lock().unwrap();
|
||||||
if inner.pointer.is_some() {
|
if inner.pointer.is_some() {
|
||||||
inner.pointer = None;
|
inner.pointer = None;
|
||||||
inner.send_all_caps();
|
inner.send_all_caps();
|
||||||
|
@ -205,18 +237,32 @@ impl Seat {
|
||||||
/// },
|
/// },
|
||||||
/// 1000,
|
/// 1000,
|
||||||
/// 500,
|
/// 500,
|
||||||
|
/// |seat, focus| {
|
||||||
|
/// /* This closure is called whenever the keyboard focus
|
||||||
|
/// * changes, with the new focus as argument */
|
||||||
|
/// }
|
||||||
/// )
|
/// )
|
||||||
/// .expect("Failed to initialize the keyboard");
|
/// .expect("Failed to initialize the keyboard");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn add_keyboard(
|
pub fn add_keyboard<F>(
|
||||||
&mut self,
|
&mut self,
|
||||||
xkb_config: keyboard::XkbConfig,
|
xkb_config: keyboard::XkbConfig,
|
||||||
repeat_delay: i32,
|
repeat_delay: i32,
|
||||||
repeat_rate: i32,
|
repeat_rate: i32,
|
||||||
) -> Result<KeyboardHandle, KeyboardError> {
|
mut focus_hook: F,
|
||||||
let mut inner = self.inner.lock().unwrap();
|
) -> Result<KeyboardHandle, KeyboardError>
|
||||||
let keyboard =
|
where
|
||||||
self::keyboard::create_keyboard_handler(xkb_config, repeat_delay, repeat_rate, &inner.log)?;
|
F: FnMut(&Seat, Option<&Resource<wl_surface::WlSurface>>) + 'static,
|
||||||
|
{
|
||||||
|
let me = self.clone();
|
||||||
|
let mut inner = self.arc.inner.lock().unwrap();
|
||||||
|
let keyboard = self::keyboard::create_keyboard_handler(
|
||||||
|
xkb_config,
|
||||||
|
repeat_delay,
|
||||||
|
repeat_rate,
|
||||||
|
&self.arc.log,
|
||||||
|
move |focus| focus_hook(&me, focus),
|
||||||
|
)?;
|
||||||
if inner.keyboard.is_some() {
|
if inner.keyboard.is_some() {
|
||||||
// there is already a keyboard, remove it and notify the clients
|
// there is already a keyboard, remove it and notify the clients
|
||||||
// of the change
|
// of the change
|
||||||
|
@ -228,11 +274,16 @@ impl Seat {
|
||||||
Ok(keyboard)
|
Ok(keyboard)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Access the keyboard of this seat if any
|
||||||
|
pub fn get_keyboard(&self) -> Option<KeyboardHandle> {
|
||||||
|
self.arc.inner.lock().unwrap().keyboard.clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove the keyboard capability from this seat
|
/// Remove the keyboard capability from this seat
|
||||||
///
|
///
|
||||||
/// Clients will be appropriately notified.
|
/// Clients will be appropriately notified.
|
||||||
pub fn remove_keyboard(&mut self) {
|
pub fn remove_keyboard(&mut self) {
|
||||||
let mut inner = self.inner.lock().unwrap();
|
let mut inner = self.arc.inner.lock().unwrap();
|
||||||
if inner.keyboard.is_some() {
|
if inner.keyboard.is_some() {
|
||||||
inner.keyboard = None;
|
inner.keyboard = None;
|
||||||
inner.send_all_caps();
|
inner.send_all_caps();
|
||||||
|
@ -241,19 +292,23 @@ impl Seat {
|
||||||
|
|
||||||
/// Checks whether a given `WlSeat` is associated with this `Seat`
|
/// Checks whether a given `WlSeat` is associated with this `Seat`
|
||||||
pub fn owns(&self, seat: &Resource<wl_seat::WlSeat>) -> bool {
|
pub fn owns(&self, seat: &Resource<wl_seat::WlSeat>) -> bool {
|
||||||
let inner = self.inner.lock().unwrap();
|
let inner = self.arc.inner.lock().unwrap();
|
||||||
inner.known_seats.iter().any(|s| s.equals(seat))
|
inner.known_seats.iter().any(|s| s.equals(seat))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn implement_seat(
|
impl ::std::cmp::PartialEq for Seat {
|
||||||
new_seat: NewResource<wl_seat::WlSeat>,
|
fn eq(&self, other: &Seat) -> bool {
|
||||||
inner: Arc<Mutex<Inner>>,
|
Arc::ptr_eq(&self.arc, &other.arc)
|
||||||
) -> Resource<wl_seat::WlSeat> {
|
}
|
||||||
let dest_inner = inner.clone();
|
}
|
||||||
|
|
||||||
|
fn implement_seat(new_seat: NewResource<wl_seat::WlSeat>, arc: Arc<SeatArc>) -> Resource<wl_seat::WlSeat> {
|
||||||
|
let dest_arc = arc.clone();
|
||||||
new_seat.implement(
|
new_seat.implement(
|
||||||
move |request, _seat| {
|
move |request, seat| {
|
||||||
let inner = inner.lock().unwrap();
|
let arc = seat.user_data::<Arc<SeatArc>>().unwrap();
|
||||||
|
let inner = arc.inner.lock().unwrap();
|
||||||
match request {
|
match request {
|
||||||
wl_seat::Request::GetPointer { id } => {
|
wl_seat::Request::GetPointer { id } => {
|
||||||
let pointer = self::pointer::implement_pointer(id, inner.pointer.as_ref());
|
let pointer = self::pointer::implement_pointer(id, inner.pointer.as_ref());
|
||||||
|
@ -281,12 +336,13 @@ fn implement_seat(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(move |seat| {
|
Some(move |seat| {
|
||||||
dest_inner
|
dest_arc
|
||||||
|
.inner
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.known_seats
|
.known_seats
|
||||||
.retain(|s| !s.equals(&seat));
|
.retain(|s| !s.equals(&seat));
|
||||||
}),
|
}),
|
||||||
(),
|
arc,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,10 @@ enum GrabStatus {
|
||||||
struct PointerInternal {
|
struct PointerInternal {
|
||||||
known_pointers: Vec<Resource<WlPointer>>,
|
known_pointers: Vec<Resource<WlPointer>>,
|
||||||
focus: Option<(Resource<WlSurface>, (f64, f64))>,
|
focus: Option<(Resource<WlSurface>, (f64, f64))>,
|
||||||
|
pending_focus: Option<(Resource<WlSurface>, (f64, f64))>,
|
||||||
location: (f64, f64),
|
location: (f64, f64),
|
||||||
grab: GrabStatus,
|
grab: GrabStatus,
|
||||||
|
pressed_buttons: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PointerInternal {
|
impl PointerInternal {
|
||||||
|
@ -27,8 +29,10 @@ impl PointerInternal {
|
||||||
PointerInternal {
|
PointerInternal {
|
||||||
known_pointers: Vec::new(),
|
known_pointers: Vec::new(),
|
||||||
focus: None,
|
focus: None,
|
||||||
|
pending_focus: None,
|
||||||
location: (0.0, 0.0),
|
location: (0.0, 0.0),
|
||||||
grab: GrabStatus::None,
|
grab: GrabStatus::None,
|
||||||
|
pressed_buttons: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +140,9 @@ impl PointerHandle {
|
||||||
serial: u32,
|
serial: u32,
|
||||||
time: u32,
|
time: u32,
|
||||||
) {
|
) {
|
||||||
self.inner.lock().unwrap().with_grab(move |mut handle, grab| {
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.pending_focus = focus.clone();
|
||||||
|
inner.with_grab(move |mut handle, grab| {
|
||||||
grab.motion(&mut handle, location, focus, serial, time);
|
grab.motion(&mut handle, location, focus, serial, time);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -146,7 +152,16 @@ impl PointerHandle {
|
||||||
/// This will internally send the appropriate button event to the client
|
/// This will internally send the appropriate button event to the client
|
||||||
/// objects matching with the currently focused surface.
|
/// objects matching with the currently focused surface.
|
||||||
pub fn button(&self, button: u32, state: ButtonState, serial: u32, time: u32) {
|
pub fn button(&self, button: u32, state: ButtonState, serial: u32, time: u32) {
|
||||||
self.inner.lock().unwrap().with_grab(|mut handle, grab| {
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
match state {
|
||||||
|
ButtonState::Pressed => {
|
||||||
|
inner.pressed_buttons.push(button);
|
||||||
|
}
|
||||||
|
ButtonState::Released => {
|
||||||
|
inner.pressed_buttons.retain(|b| *b != button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inner.with_grab(|mut handle, grab| {
|
||||||
grab.button(&mut handle, button, state, serial, time);
|
grab.button(&mut handle, button, state, serial, time);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -215,8 +230,14 @@ impl<'a> PointerInnerHandle<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove any current grab on this pointer, resetting it to the default behavior
|
/// Remove any current grab on this pointer, resetting it to the default behavior
|
||||||
pub fn unset_grab(&mut self) {
|
///
|
||||||
|
/// This will also restore the focus of the underlying pointer
|
||||||
|
pub fn unset_grab(&mut self, serial: u32, time: u32) {
|
||||||
self.inner.grab = GrabStatus::None;
|
self.inner.grab = GrabStatus::None;
|
||||||
|
// restore the focus
|
||||||
|
let location = self.current_location();
|
||||||
|
let focus = self.inner.pending_focus.take();
|
||||||
|
self.motion(location, focus, serial, time);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the current focus of this pointer
|
/// Access the current focus of this pointer
|
||||||
|
@ -229,6 +250,14 @@ impl<'a> PointerInnerHandle<'a> {
|
||||||
self.inner.location
|
self.inner.location
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A list of the currently physically pressed buttons
|
||||||
|
///
|
||||||
|
/// This still includes buttons that your grab have intercepted and not sent
|
||||||
|
/// to the client.
|
||||||
|
pub fn current_pressed(&self) -> &[u32] {
|
||||||
|
&self.inner.pressed_buttons
|
||||||
|
}
|
||||||
|
|
||||||
/// Notify that the pointer moved
|
/// Notify that the pointer moved
|
||||||
///
|
///
|
||||||
/// You provide the new location of the pointer, in the form of:
|
/// You provide the new location of the pointer, in the form of:
|
||||||
|
@ -542,7 +571,6 @@ impl PointerGrab for DefaultGrab {
|
||||||
handle.set_grab(
|
handle.set_grab(
|
||||||
serial,
|
serial,
|
||||||
ClickGrab {
|
ClickGrab {
|
||||||
buttons: vec![button],
|
|
||||||
current_focus,
|
current_focus,
|
||||||
pending_focus: None,
|
pending_focus: None,
|
||||||
},
|
},
|
||||||
|
@ -559,7 +587,6 @@ impl PointerGrab for DefaultGrab {
|
||||||
// In case the user maintains several simultaneous clicks, release
|
// In case the user maintains several simultaneous clicks, release
|
||||||
// the grab once all are released.
|
// the grab once all are released.
|
||||||
struct ClickGrab {
|
struct ClickGrab {
|
||||||
buttons: Vec<u32>,
|
|
||||||
current_focus: Option<(Resource<WlSurface>, (f64, f64))>,
|
current_focus: Option<(Resource<WlSurface>, (f64, f64))>,
|
||||||
pending_focus: Option<(Resource<WlSurface>, (f64, f64))>,
|
pending_focus: Option<(Resource<WlSurface>, (f64, f64))>,
|
||||||
}
|
}
|
||||||
|
@ -585,17 +612,10 @@ impl PointerGrab for ClickGrab {
|
||||||
serial: u32,
|
serial: u32,
|
||||||
time: u32,
|
time: u32,
|
||||||
) {
|
) {
|
||||||
match state {
|
|
||||||
ButtonState::Pressed => self.buttons.push(button),
|
|
||||||
ButtonState::Released => self.buttons.retain(|b| *b != button),
|
|
||||||
}
|
|
||||||
handle.button(button, state, serial, time);
|
handle.button(button, state, serial, time);
|
||||||
if self.buttons.is_empty() {
|
if handle.current_pressed().len() == 0 {
|
||||||
// no more buttons are pressed, release the grab
|
// no more buttons are pressed, release the grab
|
||||||
handle.unset_grab();
|
handle.unset_grab(serial, time);
|
||||||
// restore the focus
|
|
||||||
let location = handle.current_location();
|
|
||||||
handle.motion(location, self.pending_focus.clone(), serial, time);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn axis(&mut self, handle: &mut PointerInnerHandle, details: AxisFrame) {
|
fn axis(&mut self, handle: &mut PointerInnerHandle, details: AxisFrame) {
|
||||||
|
|
Loading…
Reference in New Issue