🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
//! `wl_data_device_manager` — schowek (selekcja) między klientami.
//!
//! Zakres MVP: **kopiuj/wklej** (selekcja). Drag & drop oraz selekcja pierwotna
//! (`primary selection`) — później.
//!
//! Przepływ: klient-źródło tworzy `wl_data_source`, oferuje typy MIME i wywołuje
//! `wl_data_device.set_selection`. Kompozytor rozsyła pozostałym klientom `wl_data_offer`
//! (`data_offer` → `offer` typów → `selection`). Gdy odbiorca zażąda danych
//! (`wl_data_offer.receive`), przekazujemy żądanie do `wl_data_source.send`.
use std::os::fd::AsFd;
use std::sync::Mutex;
use wayland_server::protocol::wl_data_device::{
Event as DeviceEvent, Request as DeviceRequest, WlDataDevice,
};
use wayland_server::protocol::wl_data_device_manager::{
Request as ManagerRequest, WlDataDeviceManager,
};
use wayland_server::protocol::wl_data_offer::{
Event as OfferEvent, Request as OfferRequest, WlDataOffer,
};
use wayland_server::protocol::wl_data_source::{
Event as SourceEvent, Request as SourceRequest, WlDataSource,
};
use wayland_server::{Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource};
use crate::state::State;
use crate::sync::LockExt;
/// Górny limit typów MIME oferowanych przez jedno źródło danych (ochrona przed OOM).
const MAX_MIME_TYPES: usize = 64;
/// Dane `wl_data_source`: oferowane typy MIME.
#[derive(Default)]
pub struct DataSourceData {
pub mimes: Mutex<Vec<String>>,
}
/// Dane `wl_data_offer`: źródło, z którego oferta czerpie dane.
pub struct DataOfferData {
pub source: Mutex<Option<WlDataSource>>,
}
/// Dane `wl_data_device` (na razie puste — urządzenie jest per-seat).
#[derive(Default)]
pub struct DataDeviceData {}
impl GlobalDispatch<WlDataDeviceManager, ()> for State {
fn bind(
_state: &mut Self,
_handle: &DisplayHandle,
_client: &Client,
resource: New<WlDataDeviceManager>,
_data: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<WlDataDeviceManager, ()> for State {
fn request(
state: &mut Self,
_client: &Client,
_resource: &WlDataDeviceManager,
request: ManagerRequest,
_data: &(),
_handle: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
match request {
ManagerRequest::CreateDataSource { id } => {
data_init.init(id, DataSourceData::default());
}
ManagerRequest::GetDataDevice { id, seat: _ } => {
let device = data_init.init(id, DataDeviceData::default());
state.data_devices.push(device);
}
_ => {}
}
}
}
impl Dispatch<WlDataSource, DataSourceData> for State {
fn request(
state: &mut Self,
_client: &Client,
resource: &WlDataSource,
request: SourceRequest,
data: &DataSourceData,
_handle: &DisplayHandle,
_data_init: &mut DataInit<'_, Self>,
) {
match request {
SourceRequest::Offer { mime_type } => {
let mut mimes = data.mimes.lock_or_recover();
// Limit liczby oferowanych typów MIME (ochrona przed OOM).
if mimes.len() < MAX_MIME_TYPES {
mimes.push(mime_type);
}
}
SourceRequest::Destroy => {
// Jeśli to była bieżąca selekcja — wyczyść.
if let Some(cur) = state.selection.as_ref() {
if cur.id() == resource.id() {
state.selection = None;
}
}
}
_ => {}
}
}
}
/// Stan trwającego przeciągania (drag & drop).
#[derive(Default)]
pub struct DndState {
/// Źródło danych przeciągania (jeśli klient je podał).
pub source: Option<WlDataSource>,
/// Okno docelowe pod kursorem.
pub slot: Option<usize>,
/// Oferta przekazana bieżącemu celowi.
pub offer: Option<WlDataOffer>,
}
impl Dispatch<WlDataDevice, DataDeviceData> for State {
fn request(
state: &mut Self,
_client: &Client,
resource: &WlDataDevice,
request: DeviceRequest,
_data: &DataDeviceData,
handle: &DisplayHandle,
_data_init: &mut DataInit<'_, Self>,
) {
match request {
DeviceRequest::SetSelection {
source,
serial: _serial,
} => {
state.selection = source.clone();
broadcast_selection(state, handle, resource, source.as_ref());
}
DeviceRequest::StartDrag {
source,
origin: _,
icon: _,
serial: _,
} => {
// Zaczynamy przeciąganie; cel wybierzemy przy ruchu wskaźnika.
state.dnd = Some(DndState {
source,
..DndState::default()
});
}
DeviceRequest::Release => {
state.data_devices.retain(|d| d.id() != resource.id());
}
_ => {}
}
}
}
impl Dispatch<WlDataOffer, DataOfferData> for State {
fn request(
state: &mut Self,
_client: &Client,
resource: &WlDataOffer,
request: OfferRequest,
data: &DataOfferData,
_handle: &DisplayHandle,
_data_init: &mut DataInit<'_, Self>,
) {
match request {
OfferRequest::Receive { mime_type, fd } => {
// Przekaż żądanie danych do źródła.
if let Some(src) = data.source.lock_or_recover().clone() {
let _ = src.send_event(SourceEvent::Send {
mime_type,
fd: fd.as_fd(),
});
}
}
OfferRequest::Finish => {
// Cel zakończył transfer — domknij przeciąganie (jeśli to jego oferta).
let is_dnd = state
.dnd
.as_ref()
.and_then(|d| d.offer.as_ref())
.map(|o| o.id() == resource.id())
.unwrap_or(false);
if is_dnd {
finish_dnd(state);
}
}
_ => {}
}
}
}
/// Ruch wskaźnika w trakcie przeciągania: aktualizuje cel (`enter`/`leave`/`motion`).
pub fn dnd_motion(state: &mut State, handle: &DisplayHandle, pos: (f64, f64)) {
if state.dnd.is_none() {
return;
}
let target = crate::seat::window_at(state, pos);
let changed = state.dnd.as_ref().and_then(|d| d.slot) != target;
if !changed {
if let Some(slot) = target {
send_motion(state, slot, pos);
}
return;
}
// Wyjście z poprzedniego celu.
let previous = state.dnd.as_mut().and_then(|d| {
d.offer = None;
d.slot
});
if let Some(prev) = previous {
if let Some(dev) = target_device(state, prev) {
let _ = dev.send_event(DeviceEvent::Leave);
}
}
// Wejście do nowego celu.
if let Some(dnd) = state.dnd.as_mut() {
dnd.slot = target;
}
if let Some(slot) = target {
enter_target(state, handle, slot, pos);
}
}
/// Upuszczenie w trakcie przeciągania: `drop` do celu, powiadomienie źródła.
pub fn dnd_drop(state: &mut State, _handle: &DisplayHandle) {
let Some(dnd) = state.dnd.take() else {
return;
};
match dnd.slot {
Some(slot) => {
if let Some(dev) = target_device(state, slot) {
let _ = dev.send_event(DeviceEvent::Drop);
}
// Źródło dowiaduje się o wykonaniu (bez negocjacji akcji).
if let Some(src) = dnd.source {
let _ = src.send_event(SourceEvent::DndDropPerformed);
}
}
None => {
// Upuszczenie poza celem = anulowanie.
if let Some(src) = dnd.source {
let _ = src.send_event(SourceEvent::Cancelled);
}
}
}
}
fn finish_dnd(state: &mut State) {
if let Some(dnd) = state.dnd.take() {
if let Some(src) = dnd.source {
let _ = src.send_event(SourceEvent::DndFinished);
}
}
}
/// Urządzenie schowka klienta będącego właścicielem powierzchni okna `slot`.
fn target_device(state: &State, slot: usize) -> Option<WlDataDevice> {
let surface = state.slots.get(slot)?.clone();
let client_id = surface.client()?.id();
state
.data_devices
.iter()
.find(|d| d.client().map(|c| c.id() == client_id).unwrap_or(false))
.cloned()
}
/// Wysyła `motion` do bieżącego celu.
fn send_motion(state: &State, slot: usize, pos: (f64, f64)) {
let Some(dev) = target_device(state, slot) else {
return;
};
let (lx, ly) = local_coords(state, slot, pos);
let _ = dev.send_event(DeviceEvent::Motion {
time: 0,
x: lx,
y: ly,
});
}
/// Tworzy ofertę i wysyła `data_offer` + `enter` nowemu celowi.
fn enter_target(state: &mut State, handle: &DisplayHandle, slot: usize, pos: (f64, f64)) {
let Some(dev) = target_device(state, slot) else {
return;
};
let Some(client) = dev.client() else { return };
let Some(surface) = state.slots.get(slot).cloned() else {
return;
};
let source = state.dnd.as_ref().and_then(|d| d.source.clone());
let mimes = source
.as_ref()
.and_then(|s| s.data::<DataSourceData>())
.map(|d| d.mimes.lock_or_recover().clone())
.unwrap_or_default();
let offer_data = DataOfferData {
source: Mutex::new(source),
};
let Ok(offer) =
client.create_resource::<WlDataOffer, DataOfferData, State>(handle, 3, offer_data)
else {
return;
};
let _ = dev.send_event(DeviceEvent::DataOffer { id: offer.clone() });
for mime in mimes {
let _ = offer.send_event(OfferEvent::Offer { mime_type: mime });
}
let serial = state.next_serial();
let (lx, ly) = local_coords(state, slot, pos);
let _ = dev.send_event(DeviceEvent::Enter {
serial,
surface,
x: lx,
y: ly,
id: Some(offer.clone()),
});
if let Some(dnd) = state.dnd.as_mut() {
dnd.offer = Some(offer);
}
}
/// Współrzędne powierzchniowe (lokalne dla treści okna `slot`).
fn local_coords(state: &State, slot: usize, pos: (f64, f64)) -> (f64, f64) {
match state.windows.lock_or_recover().get(slot) {
Some(w) => {
let c = w.content();
(pos.0 - c.x(), pos.1 - c.y())
}
None => pos,
}
}
/// Rozsyła bieżącą selekcję (`data_offer` + `offer` MIME + `selection`) do urządzeń klientów.
fn broadcast_selection(
state: &State,
handle: &DisplayHandle,
requester: &WlDataDevice,
source: Option<&WlDataSource>,
) {
for device in state.data_devices.clone() {
// Pomijamy urządzenie, które ustawiło selekcję (jego klient nie potrzebuje oferty).
if device.id() == requester.id() {
continue;
}
let Some(client) = device.client() else {
continue;
};
match source {
Some(src) => {
let mimes = src
.data::<DataSourceData>()
.map(|d| d.mimes.lock_or_recover().clone())
.unwrap_or_default();
let offer_data = DataOfferData {
source: Mutex::new(Some(src.clone())),
};
let Ok(offer) = client
.create_resource::<WlDataOffer, DataOfferData, State>(handle, 3, offer_data)
else {
continue;
};
let _ = device.send_event(DeviceEvent::DataOffer { id: offer.clone() });
for mime in mimes {
let _ = offer.send_event(OfferEvent::Offer { mime_type: mime });
}
let _ = device.send_event(DeviceEvent::Selection { id: Some(offer) });
}
None => {
let _ = device.send_event(DeviceEvent::Selection { id: None });
}
}
}
}