Skip to content

Commit

Permalink
wayland: register drag & drop events
Browse files Browse the repository at this point in the history
  • Loading branch information
PeakKS committed Nov 27, 2024
1 parent fc6cf89 commit 825fa3f
Show file tree
Hide file tree
Showing 4 changed files with 357 additions and 6 deletions.
12 changes: 9 additions & 3 deletions examples/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,12 +540,18 @@ impl ApplicationHandler for Application {
WindowEvent::DoubleTapGesture { .. } => {
info!("Smart zoom");
},
WindowEvent::HoveredFile(path) => {
info!("File hover initiated: {path:?}");
},
WindowEvent::HoveredFileCancelled => {
info!("File hover canceled");
},
WindowEvent::DroppedFile(path) => {
info!("File dropped: {path:?}");
},
WindowEvent::TouchpadPressure { .. }
| WindowEvent::HoveredFileCancelled
| WindowEvent::KeyboardInput { .. }
| WindowEvent::PointerEntered { .. }
| WindowEvent::DroppedFile(_)
| WindowEvent::HoveredFile(_)
| WindowEvent::Destroyed
| WindowEvent::Moved(_) => (),
}
Expand Down
329 changes: 329 additions & 0 deletions src/platform_impl/linux/wayland/seat/data_device/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
use std::io::{BufRead, BufReader};
use std::os::fd::AsFd;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};

use rustix::pipe::{pipe_with, PipeFlags};

use calloop::{LoopHandle, PostAction, RegistrationToken};
use sctk::{data_device_manager::{
ReadPipe,
data_source::DataSourceData,
}, globals::GlobalData};

use sctk::reexports::client::delegate_dispatch;
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_data_device_manager::WlDataDeviceManager;
use sctk::reexports::client::protocol::wl_data_device::{WlDataDevice, Event as WlDataDeviceEvent};
use sctk::reexports::client::protocol::wl_data_offer::{WlDataOffer, Event as WlDataOfferEvent};
use sctk::reexports::client::protocol::wl_data_source::WlDataSource;
use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle};
use tracing::{warn, info};
use wayland_client::event_created_child;
use wayland_client::protocol::wl_data_device_manager::DndAction;
use wayland_client::protocol::wl_seat::WlSeat;

use crate::event::WindowEvent;
use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland;
use crate::window::WindowId;

pub struct DataDeviceManager {
manager: WlDataDeviceManager,
}

impl DataDeviceManager {
pub fn new(
globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> {
let manager = globals.bind(queue_handle, 1..=3, GlobalData)?;
Ok(Self { manager })
}

pub fn data_device(
&self,
seat: &WlSeat,
queue_handle: &QueueHandle<WinitState>,
) -> WlDataDevice {
let data = DataDeviceData { seat: seat.clone() };
self.manager.get_data_device(seat, queue_handle, data)
}
}

impl Dispatch<WlDataDeviceManager, GlobalData, WinitState> for DataDeviceState {
fn event(
_state: &mut WinitState,
_proxy: &WlDataDeviceManager,
_event: <WlDataDeviceManager as Proxy>::Event,
_data: &GlobalData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {

}
}

struct DataDeviceData {
seat: WlSeat
}



impl Dispatch<WlDataDevice, DataDeviceData, WinitState> for DataDeviceState {
event_created_child!(WinitState, WlDataDevice, [
0 => (WlDataOffer, Default::default())
]);

fn event(
state: &mut WinitState,
wl_data_device: &WlDataDevice,
event: <WlDataDevice as Proxy>::Event,
data: &DataDeviceData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
let seat_state = match state.seats.get_mut(&data.seat.id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received data device event {event:?} without seat");
return;
},
};

let data_device_state = match seat_state.data_device_state.as_mut() {
Some(data_device_state) => data_device_state,
None => {
warn!("Received data device event {event:?} without data device state");
return;
},
};

match event {
WlDataDeviceEvent::Enter { serial, surface, x, y, id } => {
let Some(wl_data_offer) = id else {
warn!("Received data device enter event without id");
return;
};

let Some(data_offer) = wl_data_offer.data::<DataOfferData>() else {
warn!("Received data device enter event without data offer");
return;
};

let Some(mime_type) = (match data_offer.mime_types.lock() {
Ok(mime_types) => {
if let Some(mime) = mime_types.iter().find(|&mime| mime == FILE_TRANSFER_MIME_TYPE) {
Some(mime.clone())
} else {
None
}
},
Err(e) => {
warn!("Failed to lock MIME type vector: {e}");
None
}
}) else {
warn!("Data deviced entered with no valid MIME type");
return;
};

wl_data_offer.accept(serial, Some(mime_type.clone()));
let (read_fd, write_fd) = match pipe_with(PipeFlags::CLOEXEC) {
Ok((read_fd, write_fd)) => (read_fd, write_fd),
Err(e) => {
warn!("Failed to create pipe to read data offer from: {e}");
return;
}
};

wl_data_offer.set_actions(DndAction::Copy, DndAction::Copy);
wl_data_offer.receive(mime_type, write_fd.as_fd());
let read_pipe = ReadPipe::from(read_fd);

if let Some(token) = data_device_state.read_token.take() {
warn!("Cancelling previous data device enter read");
data_device_state.loop_handle.remove(token);
}

let window_id = wayland::make_wid(&surface);
data_device_state.hovered_window = Some(window_id);
data_device_state.offer = Some(wl_data_offer);
data_device_state.read_buffer.clear();

let wl_data_device = wl_data_device.clone();
data_device_state.read_token = data_device_state.loop_handle.insert_source(read_pipe, move |_, f, state| {
state.dispatched_events = true;
let data = wl_data_device.data::<DataDeviceData>().unwrap();

let seat_state = match state.seats.get_mut(&data.seat.id()) {
Some(seat_state) => seat_state,
None => return PostAction::Remove,
};

let data_device_state = match seat_state.data_device_state.as_mut() {
Some(data_device_state) => data_device_state,
None => return PostAction::Remove,
};

let f: &mut std::fs::File = unsafe {f.get_mut()};
let mut reader = BufReader::new(f);

let consumed = match reader.fill_buf() {
Ok(buf) => {
if buf.is_empty() {
if let Some(hovered) = data_device_state.hovered_window {
if let Ok(string) = String::from_utf8(data_device_state.read_buffer.clone()) {
if let Some(noprefix) = string.trim_end().strip_prefix("file://") {
data_device_state.path = PathBuf::from(noprefix);
state.events_sink.push_window_event(WindowEvent::HoveredFile(data_device_state.path.clone()), hovered);
}
} else {
warn!("Failed to parse hovered file's path");
}
} else {
warn!("No window specified to push HoveredFile to");
}
return PostAction::Remove;
} else {
data_device_state.read_buffer.extend_from_slice(buf);
}
buf.len()
},
Err(e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => {
return PostAction::Continue;
},
Err(_) => {
return PostAction::Remove;
}
};
reader.consume(consumed);

PostAction::Continue
}).ok();
},
WlDataDeviceEvent::Motion { time, x, y } => {

},
WlDataDeviceEvent::Drop => {
if let Some(hovered_window) = data_device_state.hovered_window {
state.events_sink.push_window_event(WindowEvent::DroppedFile(data_device_state.path.clone()), hovered_window);
data_device_state.hovered_window = None;
if let Some(offer) = &data_device_state.offer {
offer.finish();
offer.destroy();
data_device_state.offer = None;
}
if let Some(token) = data_device_state.read_token.take() {
data_device_state.loop_handle.remove(token);
}
}
},
WlDataDeviceEvent::Leave => {
if let Some(hovered_window) = data_device_state.hovered_window {
state.events_sink.push_window_event(WindowEvent::HoveredFileCancelled, hovered_window);
data_device_state.hovered_window = None;
if let Some(offer) = &data_device_state.offer {
offer.finish();
offer.destroy();
data_device_state.offer = None;
}
if let Some(token) = data_device_state.read_token.take() {
data_device_state.loop_handle.remove(token);
}
}
},
WlDataDeviceEvent::DataOffer { id } => {

},
WlDataDeviceEvent::Selection { id } => {

}
_ => unreachable!(),
}


}
}

#[derive(Default)]
struct DataOfferData {
pub mime_types: Arc<Mutex<Vec<String>>>
}

impl Dispatch<WlDataOffer, DataOfferData, WinitState> for DataDeviceState {
fn event(
_state: &mut WinitState,
_wl_data_offer: &WlDataOffer,
event: <WlDataOffer as Proxy>::Event,
data: &DataOfferData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {

match event {
WlDataOfferEvent::SourceActions { source_actions: _ } => {

},
WlDataOfferEvent::Action { dnd_action: _ } => {

},
WlDataOfferEvent::Offer { mime_type } => {
match data.mime_types.lock() {
Ok(ref mut mime_types) => {
mime_types.push(mime_type);
},
Err(e) => {
warn!("Failed to lock data offer mime types: {e}");
}
}
},
_ => unreachable!(),
}
}
}

impl Dispatch<WlDataSource, DataSourceData, WinitState> for DataDeviceState {
fn event(
_state: &mut WinitState,
_proxy: &WlDataSource,
_event: <WlDataSource as Proxy>::Event,
_data: &DataSourceData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
}
}

#[derive(Debug)]
pub struct DataDeviceState {
pub wl_data_device: WlDataDevice,
pub loop_handle: LoopHandle<'static, WinitState>,
pub hovered_window: Option<WindowId>,
pub offer: Option<WlDataOffer>,
pub read_token: Option<RegistrationToken>,
pub read_buffer: Vec<u8>,
pub path: PathBuf,
}

impl DataDeviceState {
pub fn new(loop_handle: LoopHandle<'static, WinitState>, wl_data_device: WlDataDevice) -> Self {
Self {
wl_data_device,
loop_handle,
hovered_window: None,
offer: None,
read_token: None,
read_buffer: Vec::new(),
path: PathBuf::new(),
}
}
}

//const FILE_TRANSFER_MIME_TYPE: &str = "application/vnd.portal.filetransfer";
const FILE_TRANSFER_MIME_TYPE: &str = "text/uri-list";

delegate_dispatch!(WinitState: [WlDataDeviceManager: GlobalData] => DataDeviceState);
delegate_dispatch!(WinitState: [WlDataDevice: DataDeviceData] => DataDeviceState);
delegate_dispatch!(WinitState: [WlDataOffer: DataOfferData] => DataDeviceState);
delegate_dispatch!(WinitState: [WlDataSource: DataSourceData] => DataDeviceState);
11 changes: 11 additions & 0 deletions src/platform_impl/linux/wayland/seat/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ use crate::event::WindowEvent;
use crate::keyboard::ModifiersState;
use crate::platform_impl::wayland::state::WinitState;

pub mod data_device;
mod keyboard;
mod pointer;
mod text_input;
mod touch;

use data_device::DataDeviceState;
use keyboard::{KeyboardData, KeyboardState};
pub use pointer::relative_pointer::RelativePointerState;
pub use pointer::{PointerConstraintsState, WinitPointerData, WinitPointerDataExt};
Expand Down Expand Up @@ -52,6 +54,8 @@ pub struct WinitSeatState {
/// The keyboard bound on the seat.
keyboard_state: Option<KeyboardState>,

data_device_state: Option<DataDeviceState>,

/// The current modifiers state on the seat.
modifiers: ModifiersState,

Expand Down Expand Up @@ -85,6 +89,13 @@ impl SeatHandler for WinitState {
},
};

if seat_state.data_device_state.is_none() {
if let Some(data_device_manager) = &self.data_device_manager {
let data_device = data_device_manager.data_device(&seat, queue_handle);
seat_state.data_device_state = Some(DataDeviceState::new(self.loop_handle.clone(), data_device));
}
}

match capability {
SeatCapability::Touch if seat_state.touch.is_none() => {
seat_state.touch = self.seat_state.get_touch(queue_handle, &seat).ok();
Expand Down
Loading

0 comments on commit 825fa3f

Please sign in to comment.