-
Notifications
You must be signed in to change notification settings - Fork 920
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
wayland: register drag & drop events
- Loading branch information
Showing
4 changed files
with
357 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
329 changes: 329 additions & 0 deletions
329
src/platform_impl/linux/wayland/seat/data_device/mod.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.