Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ rwh_06 = { package = "raw-window-handle", version = "0.6", features = [
serde = { workspace = true, optional = true }
smol_str = "0.2.0"
tracing = { version = "0.1.40", default-features = false }
url = "2.5.4"

[dev-dependencies]
image = { version = "0.25.0", default-features = false, features = ["png"] }
Expand Down
1 change: 1 addition & 0 deletions src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ on how to add them:
- On X11, add `Window::even_more_rare_api`.
- On Wayland, add `Window::common_api`.
- On Windows, add `Window::some_rare_api`.
- On Wayland, add support for `DroppedFile`, `HoveredFile` and `HoveredFileCancelled` window events.
```

When the change requires non-trivial amount of work for users to comply
Expand Down
178 changes: 178 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,178 @@
use sctk::data_device_manager::data_device::DataDeviceHandler;
use sctk::data_device_manager::data_offer::{DataOfferHandler, DragOffer};
use sctk::data_device_manager::data_source::DataSourceHandler;
use sctk::data_device_manager::WritePipe;
use wayland_client::protocol::wl_data_device::WlDataDevice;
use wayland_client::protocol::wl_data_device_manager::DndAction;
use wayland_client::protocol::wl_data_source::WlDataSource;
use wayland_client::protocol::wl_surface::WlSurface;
use wayland_client::{Connection, Proxy, QueueHandle};

use crate::event::WindowEvent;
use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland::types::dnd::DndOfferState;
use crate::platform_impl::wayland::{self};

const SUPPORTED_MIME_TYPES: &[&str] = &["text/uri-list"];

fn filter_mime(mime_types: &[String]) -> Option<String> {
for mime in mime_types {
if SUPPORTED_MIME_TYPES.contains(&mime.as_str()) {
return Some(mime.clone());
}
}

None
}

impl DataDeviceHandler for WinitState {
fn enter(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
wl_data_device: &WlDataDevice,
_: f64,
_: f64,
_: &WlSurface,
) {
let data_device = self.get_data_device(wl_data_device);

if let Some(data_device) = data_device {
if let Some(offer) = data_device.data().drag_offer() {
if let Some(mime_type) = offer.with_mime_types(filter_mime) {
offer.accept_mime_type(offer.serial, Some(mime_type.clone()));
offer.set_actions(DndAction::Copy, DndAction::Copy);

if let Ok(read_pipe) = offer.receive(mime_type) {
let data_device_id = data_device.inner().id();
let surface = offer.surface;
let window_id = wayland::make_wid(&surface);

self.read_file_paths(read_pipe, move |state, path| {
state.dnd_offers.insert(data_device_id.clone(), DndOfferState {
surface: surface.clone(),
path: path.clone(),
});

state
.events_sink
.push_window_event(WindowEvent::HoveredFile(path), window_id);
});
}
}
}
}
}

fn leave(&mut self, _: &Connection, _: &QueueHandle<Self>, wl_data_device: &WlDataDevice) {
let data_device = self.get_data_device(wl_data_device);

if let Some(data_device) = data_device {
if let Some(dnd_offer) = self.dnd_offers.remove(&data_device.inner().id()) {
let window_id = wayland::make_wid(&dnd_offer.surface);
self.events_sink.push_window_event(WindowEvent::HoveredFileCancelled, window_id);
}
}
}

fn motion(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
wl_data_device: &WlDataDevice,
_: f64,
_: f64,
) {
let data_device = self.get_data_device(wl_data_device);

if let Some(data_device) = data_device {
if let Some(offer) = data_device.data().drag_offer() {
let window_id = wayland::make_wid(&offer.surface);

if let Some(dnd_offer) = self.dnd_offers.get(&data_device.inner().id()) {
self.events_sink.push_window_event(
WindowEvent::HoveredFile(dnd_offer.path.to_path_buf()),
window_id,
);
}
}
}
}

fn selection(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataDevice) {}

fn drop_performed(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
wl_data_device: &WlDataDevice,
) {
let data_device = self.get_data_device(wl_data_device);

if let Some(data_device) = data_device {
if let Some(offer) = data_device.data().drag_offer() {
let window_id = wayland::make_wid(&offer.surface);

if let Some(dnd_offer) = self.dnd_offers.remove(&data_device.inner().id()) {
self.events_sink
.push_window_event(WindowEvent::DroppedFile(dnd_offer.path), window_id);

offer.finish();
offer.destroy();
}
}
}
}
}

impl DataSourceHandler for WinitState {
fn accept_mime(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlDataSource,
_: Option<String>,
) {
}

fn send_request(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlDataSource,
_: String,
_: WritePipe,
) {
}

fn cancelled(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataSource) {}

fn dnd_dropped(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataSource) {}

fn dnd_finished(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataSource) {}

fn action(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataSource, _: DndAction) {}
}

impl DataOfferHandler for WinitState {
fn source_actions(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
offer: &mut DragOffer,
_: DndAction,
) {
offer.set_actions(DndAction::Copy, DndAction::Copy);
}

fn selected_action(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &mut DragOffer,
_: DndAction,
) {
}
}

sctk::delegate_data_device!(WinitState);
51 changes: 51 additions & 0 deletions src/platform_impl/linux/wayland/seat/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
//! Seat handling.

use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use std::sync::Arc;

use ahash::AHashMap;
use calloop::PostAction;
use sctk::data_device_manager::data_device::DataDevice;
use sctk::data_device_manager::ReadPipe;
use tracing::warn;

use sctk::reexports::client::backend::ObjectId;
Expand All @@ -14,11 +20,14 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::

use sctk::seat::pointer::{ThemeSpec, ThemedPointer};
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
use url::Url;
use wayland_client::protocol::wl_data_device::WlDataDevice;

use crate::event::WindowEvent;
use crate::keyboard::ModifiersState;
use crate::platform_impl::wayland::state::WinitState;

mod data_device;
mod keyboard;
mod pointer;
mod text_input;
Expand Down Expand Up @@ -57,6 +66,9 @@ pub struct WinitSeatState {

/// Whether we have pending modifiers.
modifiers_pending: bool,

/// The current data device.
data_device: Option<DataDevice>,
}

impl WinitSeatState {
Expand Down Expand Up @@ -141,6 +153,11 @@ impl SeatHandler for WinitState {
TextInputData::default(),
)));
}

if seat_state.data_device.is_none() {
let data_device = self.data_device_manager_state.get_data_device(queue_handle, &seat);
seat_state.data_device = Some(data_device);
}
}

fn remove_capability(
Expand Down Expand Up @@ -230,6 +247,40 @@ impl WinitState {
}
}
}

fn get_data_device(&self, wl_data_device: &WlDataDevice) -> Option<&DataDevice> {
self.seats.values().find_map(|seat| {
let data_device = seat.data_device.as_ref()?;
(data_device.inner() == wl_data_device).then_some(data_device)
})
}

fn read_file_paths<F: Fn(&mut Self, PathBuf) + 'static>(
&self,
read_pipe: ReadPipe,
callback: F,
) {
self.loop_handle
.insert_source(read_pipe, move |_, file, state| {
let mut data = String::new();

let file: &mut File = unsafe { file.get_mut() };
if file.read_to_string(&mut data).is_ok() {
if let Some(line) = data.lines().next() {
if let Ok(url) = Url::parse(line) {
if url.scheme() == "file" {
if let Ok(path) = url.to_file_path() {
callback(state, path)
}
}
}
}
}

PostAction::Remove
})
.ok();
}
}

sctk::delegate_seat!(WinitState);
15 changes: 15 additions & 0 deletions src/platform_impl/linux/wayland/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::sync::{Arc, Mutex};

use ahash::AHashMap;

use sctk::data_device_manager::DataDeviceManagerState;
use sctk::reexports::calloop::LoopHandle;
use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::globals::GlobalList;
Expand All @@ -29,6 +30,7 @@ use crate::platform_impl::wayland::seat::{
PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData,
WinitPointerDataExt, WinitSeatState,
};
use crate::platform_impl::wayland::types::dnd::DndOfferState;
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
use crate::platform_impl::wayland::types::wp_fractional_scaling::FractionalScalingManager;
use crate::platform_impl::wayland::types::wp_viewporter::ViewporterState;
Expand All @@ -54,6 +56,12 @@ pub struct WinitState {
/// The seat state responsible for all sorts of input.
pub seat_state: SeatState,

// The state of the data device manager.
pub data_device_manager_state: DataDeviceManagerState,

// The active drag and drop offers.
pub dnd_offers: AHashMap<ObjectId, DndOfferState>,

/// The shm for software buffers, such as cursors.
pub shm: Shm,

Expand Down Expand Up @@ -141,6 +149,9 @@ impl WinitState {
let output_state = OutputState::new(globals, queue_handle);
let monitors = output_state.outputs().map(MonitorHandle::new).collect();

let data_device_manager_state =
DataDeviceManagerState::bind(globals, queue_handle).map_err(WaylandError::Bind)?;

let seat_state = SeatState::new(globals, queue_handle);

let mut seats = AHashMap::default();
Expand All @@ -164,6 +175,10 @@ impl WinitState {
subcompositor_state: subcompositor_state.map(Arc::new),
output_state,
seat_state,

data_device_manager_state,
dnd_offers: AHashMap::default(),

shm,
custom_cursor_pool,

Expand Down
8 changes: 8 additions & 0 deletions src/platform_impl/linux/wayland/types/dnd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use std::path::PathBuf;

use wayland_client::protocol::wl_surface::WlSurface;

pub struct DndOfferState {
pub surface: WlSurface,
pub path: PathBuf,
}
1 change: 1 addition & 0 deletions src/platform_impl/linux/wayland/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Wayland protocol implementation boilerplate.

pub mod cursor;
pub mod dnd;
pub mod kwin_blur;
pub mod wp_fractional_scaling;
pub mod wp_viewporter;
Expand Down
Loading