mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-26 09:52:51 +03:00
wayland: File drag and drop (#10817)
Implements file drag and drop on Wayland https://github.com/zed-industries/zed/assets/71973804/febcfbfe-3a23-4593-8dd3-e85254e58eb5 Release Notes: - N/A
This commit is contained in:
parent
029eb67043
commit
ae3c641bbe
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -3799,6 +3799,17 @@ dependencies = [
|
|||||||
"util",
|
"util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filedescriptor"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"thiserror",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filetime"
|
name = "filetime"
|
||||||
version = "0.2.22"
|
version = "0.2.22"
|
||||||
@ -4482,6 +4493,7 @@ dependencies = [
|
|||||||
"derive_more",
|
"derive_more",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"etagere",
|
"etagere",
|
||||||
|
"filedescriptor",
|
||||||
"flume",
|
"flume",
|
||||||
"font-kit",
|
"font-kit",
|
||||||
"foreign-types 0.5.0",
|
"foreign-types 0.5.0",
|
||||||
|
@ -115,6 +115,7 @@ wayland-protocols = { version = "0.31.2", features = [
|
|||||||
] }
|
] }
|
||||||
oo7 = "0.3.0"
|
oo7 = "0.3.0"
|
||||||
open = "5.1.2"
|
open = "5.1.2"
|
||||||
|
filedescriptor = "0.8.2"
|
||||||
x11rb = { version = "0.13.0", features = ["allow-unsafe-code", "xkb", "randr"] }
|
x11rb = { version = "0.13.0", features = ["allow-unsafe-code", "xkb", "randr"] }
|
||||||
xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
|
xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
|
||||||
|
|
||||||
|
@ -3,7 +3,10 @@
|
|||||||
use std::any::{type_name, Any};
|
use std::any::{type_name, Any};
|
||||||
use std::cell::{self, RefCell};
|
use std::cell::{self, RefCell};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::os::fd::{AsRawFd, FromRawFd};
|
||||||
use std::panic::Location;
|
use std::panic::Location;
|
||||||
use std::{
|
use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@ -19,6 +22,7 @@ use async_task::Runnable;
|
|||||||
use calloop::channel::Channel;
|
use calloop::channel::Channel;
|
||||||
use calloop::{EventLoop, LoopHandle, LoopSignal};
|
use calloop::{EventLoop, LoopHandle, LoopSignal};
|
||||||
use copypasta::ClipboardProvider;
|
use copypasta::ClipboardProvider;
|
||||||
|
use filedescriptor::FileDescriptor;
|
||||||
use flume::{Receiver, Sender};
|
use flume::{Receiver, Sender};
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
@ -484,6 +488,19 @@ pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bo
|
|||||||
diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE
|
diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) unsafe fn read_fd(mut fd: FileDescriptor) -> Result<String> {
|
||||||
|
let mut file = File::from_raw_fd(fd.as_raw_fd());
|
||||||
|
|
||||||
|
let mut buffer = String::new();
|
||||||
|
file.read_to_string(&mut buffer)?;
|
||||||
|
|
||||||
|
// Normalize the text to unix line endings, otherwise
|
||||||
|
// copying from eg: firefox inserts a lot of blank
|
||||||
|
// lines, and that is super annoying.
|
||||||
|
let result = buffer.replace("\r\n", "\n");
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
impl Keystroke {
|
impl Keystroke {
|
||||||
pub(super) fn from_xkb(state: &State, modifiers: Modifiers, keycode: Keycode) -> Self {
|
pub(super) fn from_xkb(state: &State, modifiers: Modifiers, keycode: Keycode) -> Self {
|
||||||
let mut modifiers = modifiers;
|
let mut modifiers = modifiers;
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
|
use core::hash;
|
||||||
use std::cell::{RefCell, RefMut};
|
use std::cell::{RefCell, RefMut};
|
||||||
|
use std::os::fd::{AsRawFd, BorrowedFd};
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::rc::{Rc, Weak};
|
use std::rc::{Rc, Weak};
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use async_task::Runnable;
|
use async_task::Runnable;
|
||||||
@ -9,13 +13,19 @@ use calloop_wayland_source::WaylandSource;
|
|||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use copypasta::wayland_clipboard::{create_clipboards_from_external, Clipboard, Primary};
|
use copypasta::wayland_clipboard::{create_clipboards_from_external, Clipboard, Primary};
|
||||||
use copypasta::ClipboardProvider;
|
use copypasta::ClipboardProvider;
|
||||||
|
use filedescriptor::Pipe;
|
||||||
|
use smallvec::SmallVec;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use wayland_backend::client::ObjectId;
|
use wayland_backend::client::ObjectId;
|
||||||
use wayland_backend::protocol::WEnum;
|
use wayland_backend::protocol::WEnum;
|
||||||
|
use wayland_client::event_created_child;
|
||||||
use wayland_client::globals::{registry_queue_init, GlobalList, GlobalListContents};
|
use wayland_client::globals::{registry_queue_init, GlobalList, GlobalListContents};
|
||||||
use wayland_client::protocol::wl_callback::{self, WlCallback};
|
use wayland_client::protocol::wl_callback::{self, WlCallback};
|
||||||
use wayland_client::protocol::wl_output;
|
use wayland_client::protocol::wl_data_device_manager::DndAction;
|
||||||
use wayland_client::protocol::wl_pointer::{AxisRelativeDirection, AxisSource};
|
use wayland_client::protocol::wl_pointer::{AxisRelativeDirection, AxisSource};
|
||||||
|
use wayland_client::protocol::{
|
||||||
|
wl_data_device, wl_data_device_manager, wl_data_offer, wl_data_source, wl_output,
|
||||||
|
};
|
||||||
use wayland_client::{
|
use wayland_client::{
|
||||||
delegate_noop,
|
delegate_noop,
|
||||||
protocol::{
|
protocol::{
|
||||||
@ -35,14 +45,14 @@ use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_ba
|
|||||||
use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1;
|
use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1;
|
||||||
use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS};
|
use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS};
|
||||||
|
|
||||||
use super::super::DOUBLE_CLICK_INTERVAL;
|
use super::super::{read_fd, DOUBLE_CLICK_INTERVAL};
|
||||||
use super::window::{WaylandWindowState, WaylandWindowStatePtr};
|
use super::window::{WaylandWindowState, WaylandWindowStatePtr};
|
||||||
use crate::platform::linux::is_within_click_distance;
|
use crate::platform::linux::is_within_click_distance;
|
||||||
use crate::platform::linux::wayland::cursor::Cursor;
|
use crate::platform::linux::wayland::cursor::Cursor;
|
||||||
use crate::platform::linux::wayland::window::WaylandWindow;
|
use crate::platform::linux::wayland::window::WaylandWindow;
|
||||||
use crate::platform::linux::LinuxClient;
|
use crate::platform::linux::LinuxClient;
|
||||||
use crate::platform::PlatformWindow;
|
use crate::platform::PlatformWindow;
|
||||||
use crate::{point, px, ForegroundExecutor, MouseExitEvent};
|
use crate::{point, px, FileDropEvent, ForegroundExecutor, MouseExitEvent};
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyWindowHandle, CursorStyle, DisplayId, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers,
|
AnyWindowHandle, CursorStyle, DisplayId, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers,
|
||||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||||
@ -58,6 +68,7 @@ const MIN_KEYCODE: u32 = 8;
|
|||||||
pub struct Globals {
|
pub struct Globals {
|
||||||
pub qh: QueueHandle<WaylandClientStatePtr>,
|
pub qh: QueueHandle<WaylandClientStatePtr>,
|
||||||
pub compositor: wl_compositor::WlCompositor,
|
pub compositor: wl_compositor::WlCompositor,
|
||||||
|
pub data_device_manager: Option<wl_data_device_manager::WlDataDeviceManager>,
|
||||||
pub wm_base: xdg_wm_base::XdgWmBase,
|
pub wm_base: xdg_wm_base::XdgWmBase,
|
||||||
pub shm: wl_shm::WlShm,
|
pub shm: wl_shm::WlShm,
|
||||||
pub viewporter: Option<wp_viewporter::WpViewporter>,
|
pub viewporter: Option<wp_viewporter::WpViewporter>,
|
||||||
@ -82,6 +93,13 @@ impl Globals {
|
|||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
data_device_manager: globals
|
||||||
|
.bind(
|
||||||
|
&qh,
|
||||||
|
WL_DATA_DEVICE_MANAGER_VERSION..=WL_DATA_DEVICE_MANAGER_VERSION,
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
.ok(),
|
||||||
shm: globals.bind(&qh, 1..=1, ()).unwrap(),
|
shm: globals.bind(&qh, 1..=1, ()).unwrap(),
|
||||||
wm_base: globals.bind(&qh, 1..=1, ()).unwrap(),
|
wm_base: globals.bind(&qh, 1..=1, ()).unwrap(),
|
||||||
viewporter: globals.bind(&qh, 1..=1, ()).ok(),
|
viewporter: globals.bind(&qh, 1..=1, ()).ok(),
|
||||||
@ -94,13 +112,16 @@ impl Globals {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct WaylandClientState {
|
pub(crate) struct WaylandClientState {
|
||||||
|
serial: u32,
|
||||||
globals: Globals,
|
globals: Globals,
|
||||||
wl_pointer: Option<wl_pointer::WlPointer>,
|
wl_pointer: Option<wl_pointer::WlPointer>,
|
||||||
|
data_device: Option<wl_data_device::WlDataDevice>,
|
||||||
// Surface to Window mapping
|
// Surface to Window mapping
|
||||||
windows: HashMap<ObjectId, WaylandWindowStatePtr>,
|
windows: HashMap<ObjectId, WaylandWindowStatePtr>,
|
||||||
// Output to scale mapping
|
// Output to scale mapping
|
||||||
output_scales: HashMap<ObjectId, i32>,
|
output_scales: HashMap<ObjectId, i32>,
|
||||||
keymap_state: Option<xkb::State>,
|
keymap_state: Option<xkb::State>,
|
||||||
|
drag: DragState,
|
||||||
click: ClickState,
|
click: ClickState,
|
||||||
repeat: KeyRepeat,
|
repeat: KeyRepeat,
|
||||||
modifiers: Modifiers,
|
modifiers: Modifiers,
|
||||||
@ -124,6 +145,12 @@ pub(crate) struct WaylandClientState {
|
|||||||
common: LinuxCommon,
|
common: LinuxCommon,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct DragState {
|
||||||
|
data_offer: Option<wl_data_offer::WlDataOffer>,
|
||||||
|
window: Option<WaylandWindowStatePtr>,
|
||||||
|
position: Point<Pixels>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ClickState {
|
pub struct ClickState {
|
||||||
last_click: Instant,
|
last_click: Instant,
|
||||||
last_location: Point<Pixels>,
|
last_location: Point<Pixels>,
|
||||||
@ -167,6 +194,12 @@ impl WaylandClientStatePtr {
|
|||||||
// Drop the clipboard to prevent a seg fault after we've closed all Wayland connections.
|
// Drop the clipboard to prevent a seg fault after we've closed all Wayland connections.
|
||||||
state.clipboard = None;
|
state.clipboard = None;
|
||||||
state.primary = None;
|
state.primary = None;
|
||||||
|
if let Some(wl_pointer) = &state.wl_pointer {
|
||||||
|
wl_pointer.release();
|
||||||
|
}
|
||||||
|
if let Some(data_device) = &state.data_device {
|
||||||
|
data_device.release();
|
||||||
|
}
|
||||||
state.common.signal.stop();
|
state.common.signal.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,6 +208,7 @@ impl WaylandClientStatePtr {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WaylandClient(Rc<RefCell<WaylandClientState>>);
|
pub struct WaylandClient(Rc<RefCell<WaylandClientState>>);
|
||||||
|
|
||||||
|
const WL_DATA_DEVICE_MANAGER_VERSION: u32 = 3;
|
||||||
const WL_OUTPUT_VERSION: u32 = 2;
|
const WL_OUTPUT_VERSION: u32 = 2;
|
||||||
|
|
||||||
fn wl_seat_version(version: u32) -> u32 {
|
fn wl_seat_version(version: u32) -> u32 {
|
||||||
@ -199,18 +233,20 @@ impl WaylandClient {
|
|||||||
let (globals, mut event_queue) =
|
let (globals, mut event_queue) =
|
||||||
registry_queue_init::<WaylandClientStatePtr>(&conn).unwrap();
|
registry_queue_init::<WaylandClientStatePtr>(&conn).unwrap();
|
||||||
let qh = event_queue.handle();
|
let qh = event_queue.handle();
|
||||||
let mut outputs = HashMap::default();
|
|
||||||
|
|
||||||
|
let mut seat: Option<wl_seat::WlSeat> = None;
|
||||||
|
let mut outputs = HashMap::default();
|
||||||
globals.contents().with_list(|list| {
|
globals.contents().with_list(|list| {
|
||||||
for global in list {
|
for global in list {
|
||||||
match &global.interface[..] {
|
match &global.interface[..] {
|
||||||
"wl_seat" => {
|
"wl_seat" => {
|
||||||
globals.registry().bind::<wl_seat::WlSeat, _, _>(
|
// TODO: multi-seat support
|
||||||
|
seat = Some(globals.registry().bind::<wl_seat::WlSeat, _, _>(
|
||||||
global.name,
|
global.name,
|
||||||
wl_seat_version(global.version),
|
wl_seat_version(global.version),
|
||||||
&qh,
|
&qh,
|
||||||
(),
|
(),
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
"wl_output" => {
|
"wl_output" => {
|
||||||
let output = globals.registry().bind::<wl_output::WlOutput, _, _>(
|
let output = globals.registry().bind::<wl_output::WlOutput, _, _>(
|
||||||
@ -227,34 +263,47 @@ impl WaylandClient {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let display = conn.backend().display_ptr() as *mut std::ffi::c_void;
|
let display = conn.backend().display_ptr() as *mut std::ffi::c_void;
|
||||||
let (primary, clipboard) = unsafe { create_clipboards_from_external(display) };
|
|
||||||
|
|
||||||
let event_loop = EventLoop::<WaylandClientStatePtr>::try_new().unwrap();
|
let event_loop = EventLoop::<WaylandClientStatePtr>::try_new().unwrap();
|
||||||
|
|
||||||
let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
|
let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
|
||||||
|
|
||||||
let handle = event_loop.handle();
|
let handle = event_loop.handle();
|
||||||
|
|
||||||
handle.insert_source(main_receiver, |event, _, _: &mut WaylandClientStatePtr| {
|
handle.insert_source(main_receiver, |event, _, _: &mut WaylandClientStatePtr| {
|
||||||
if let calloop::channel::Event::Msg(runnable) = event {
|
if let calloop::channel::Event::Msg(runnable) = event {
|
||||||
runnable.run();
|
runnable.run();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let globals = Globals::new(globals, common.foreground_executor.clone(), qh);
|
let seat = seat.unwrap();
|
||||||
|
let globals = Globals::new(globals, common.foreground_executor.clone(), qh.clone());
|
||||||
|
|
||||||
|
let data_device = globals
|
||||||
|
.data_device_manager
|
||||||
|
.as_ref()
|
||||||
|
.map(|data_device_manager| data_device_manager.get_data_device(&seat, &qh, ()));
|
||||||
|
|
||||||
|
let (primary, clipboard) = unsafe { create_clipboards_from_external(display) };
|
||||||
|
|
||||||
let cursor = Cursor::new(&conn, &globals, 24);
|
let cursor = Cursor::new(&conn, &globals, 24);
|
||||||
|
|
||||||
let mut state = Rc::new(RefCell::new(WaylandClientState {
|
let mut state = Rc::new(RefCell::new(WaylandClientState {
|
||||||
|
serial: 0,
|
||||||
globals,
|
globals,
|
||||||
wl_pointer: None,
|
wl_pointer: None,
|
||||||
|
data_device,
|
||||||
output_scales: outputs,
|
output_scales: outputs,
|
||||||
windows: HashMap::default(),
|
windows: HashMap::default(),
|
||||||
common,
|
common,
|
||||||
keymap_state: None,
|
keymap_state: None,
|
||||||
|
drag: DragState {
|
||||||
|
data_offer: None,
|
||||||
|
window: None,
|
||||||
|
position: Point::default(),
|
||||||
|
},
|
||||||
click: ClickState {
|
click: ClickState {
|
||||||
last_click: Instant::now(),
|
last_click: Instant::now(),
|
||||||
last_location: Point::new(px(0.0), px(0.0)),
|
last_location: Point::default(),
|
||||||
current_count: 0,
|
current_count: 0,
|
||||||
},
|
},
|
||||||
repeat: KeyRepeat {
|
repeat: KeyRepeat {
|
||||||
@ -467,6 +516,7 @@ impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
delegate_noop!(WaylandClientStatePtr: ignore wl_compositor::WlCompositor);
|
delegate_noop!(WaylandClientStatePtr: ignore wl_compositor::WlCompositor);
|
||||||
|
delegate_noop!(WaylandClientStatePtr: ignore wl_data_device_manager::WlDataDeviceManager);
|
||||||
delegate_noop!(WaylandClientStatePtr: ignore wl_shm::WlShm);
|
delegate_noop!(WaylandClientStatePtr: ignore wl_shm::WlShm);
|
||||||
delegate_noop!(WaylandClientStatePtr: ignore wl_shm_pool::WlShmPool);
|
delegate_noop!(WaylandClientStatePtr: ignore wl_shm_pool::WlShmPool);
|
||||||
delegate_noop!(WaylandClientStatePtr: ignore wl_buffer::WlBuffer);
|
delegate_noop!(WaylandClientStatePtr: ignore wl_buffer::WlBuffer);
|
||||||
@ -599,7 +649,7 @@ impl Dispatch<xdg_toplevel::XdgToplevel, ObjectId> for WaylandClientStatePtr {
|
|||||||
|
|
||||||
impl Dispatch<xdg_wm_base::XdgWmBase, ()> for WaylandClientStatePtr {
|
impl Dispatch<xdg_wm_base::XdgWmBase, ()> for WaylandClientStatePtr {
|
||||||
fn event(
|
fn event(
|
||||||
_: &mut Self,
|
this: &mut Self,
|
||||||
wm_base: &xdg_wm_base::XdgWmBase,
|
wm_base: &xdg_wm_base::XdgWmBase,
|
||||||
event: <xdg_wm_base::XdgWmBase as Proxy>::Event,
|
event: <xdg_wm_base::XdgWmBase as Proxy>::Event,
|
||||||
_: &(),
|
_: &(),
|
||||||
@ -607,6 +657,9 @@ impl Dispatch<xdg_wm_base::XdgWmBase, ()> for WaylandClientStatePtr {
|
|||||||
_: &QueueHandle<Self>,
|
_: &QueueHandle<Self>,
|
||||||
) {
|
) {
|
||||||
if let xdg_wm_base::Event::Ping { serial } = event {
|
if let xdg_wm_base::Event::Ping { serial } = event {
|
||||||
|
let client = this.get_client();
|
||||||
|
let mut state = client.borrow_mut();
|
||||||
|
state.serial = serial;
|
||||||
wm_base.pong(serial);
|
wm_base.pong(serial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -678,7 +731,10 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
|||||||
};
|
};
|
||||||
state.keymap_state = Some(xkb::State::new(&keymap));
|
state.keymap_state = Some(xkb::State::new(&keymap));
|
||||||
}
|
}
|
||||||
wl_keyboard::Event::Enter { surface, .. } => {
|
wl_keyboard::Event::Enter {
|
||||||
|
serial, surface, ..
|
||||||
|
} => {
|
||||||
|
state.serial = serial;
|
||||||
state.keyboard_focused_window = get_window(&mut state, &surface.id());
|
state.keyboard_focused_window = get_window(&mut state, &surface.id());
|
||||||
|
|
||||||
if let Some(window) = state.keyboard_focused_window.clone() {
|
if let Some(window) = state.keyboard_focused_window.clone() {
|
||||||
@ -686,7 +742,10 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
|||||||
window.set_focused(true);
|
window.set_focused(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wl_keyboard::Event::Leave { surface, .. } => {
|
wl_keyboard::Event::Leave {
|
||||||
|
serial, surface, ..
|
||||||
|
} => {
|
||||||
|
state.serial = serial;
|
||||||
let keyboard_focused_window = get_window(&mut state, &surface.id());
|
let keyboard_focused_window = get_window(&mut state, &surface.id());
|
||||||
state.keyboard_focused_window = None;
|
state.keyboard_focused_window = None;
|
||||||
|
|
||||||
@ -696,12 +755,14 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
wl_keyboard::Event::Modifiers {
|
wl_keyboard::Event::Modifiers {
|
||||||
|
serial,
|
||||||
mods_depressed,
|
mods_depressed,
|
||||||
mods_latched,
|
mods_latched,
|
||||||
mods_locked,
|
mods_locked,
|
||||||
group,
|
group,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
|
state.serial = serial;
|
||||||
let focused_window = state.keyboard_focused_window.clone();
|
let focused_window = state.keyboard_focused_window.clone();
|
||||||
let Some(focused_window) = focused_window else {
|
let Some(focused_window) = focused_window else {
|
||||||
return;
|
return;
|
||||||
@ -721,8 +782,11 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
|||||||
wl_keyboard::Event::Key {
|
wl_keyboard::Event::Key {
|
||||||
key,
|
key,
|
||||||
state: WEnum::Value(key_state),
|
state: WEnum::Value(key_state),
|
||||||
|
serial,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
|
state.serial = serial;
|
||||||
|
|
||||||
let focused_window = state.keyboard_focused_window.clone();
|
let focused_window = state.keyboard_focused_window.clone();
|
||||||
let Some(focused_window) = focused_window else {
|
let Some(focused_window) = focused_window else {
|
||||||
return;
|
return;
|
||||||
@ -833,6 +897,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
|
|||||||
surface_y,
|
surface_y,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
|
state.serial = serial;
|
||||||
state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
|
state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
|
||||||
|
|
||||||
if let Some(window) = get_window(&mut state, &surface.id()) {
|
if let Some(window) = get_window(&mut state, &surface.id()) {
|
||||||
@ -885,10 +950,12 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
wl_pointer::Event::Button {
|
wl_pointer::Event::Button {
|
||||||
|
serial,
|
||||||
button,
|
button,
|
||||||
state: WEnum::Value(button_state),
|
state: WEnum::Value(button_state),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
|
state.serial = serial;
|
||||||
let button = linux_button_to_gpui(button);
|
let button = linux_button_to_gpui(button);
|
||||||
let Some(button) = button else { return };
|
let Some(button) = button else { return };
|
||||||
if state.mouse_focused_window.is_none() {
|
if state.mouse_focused_window.is_none() {
|
||||||
@ -1123,3 +1190,163 @@ impl Dispatch<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1, ObjectId>
|
|||||||
window.handle_toplevel_decoration_event(event);
|
window.handle_toplevel_decoration_event(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FILE_LIST_MIME_TYPE: &str = "text/uri-list";
|
||||||
|
|
||||||
|
impl Dispatch<wl_data_device::WlDataDevice, ()> for WaylandClientStatePtr {
|
||||||
|
fn event(
|
||||||
|
this: &mut Self,
|
||||||
|
_: &wl_data_device::WlDataDevice,
|
||||||
|
event: wl_data_device::Event,
|
||||||
|
_: &(),
|
||||||
|
_: &Connection,
|
||||||
|
_: &QueueHandle<Self>,
|
||||||
|
) {
|
||||||
|
let client = this.get_client();
|
||||||
|
let mut state = client.borrow_mut();
|
||||||
|
|
||||||
|
match event {
|
||||||
|
wl_data_device::Event::Enter {
|
||||||
|
serial,
|
||||||
|
surface,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
id: data_offer,
|
||||||
|
} => {
|
||||||
|
state.serial = serial;
|
||||||
|
if let Some(data_offer) = data_offer {
|
||||||
|
let Some(drag_window) = get_window(&mut state, &surface.id()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ACTIONS: DndAction = DndAction::Copy;
|
||||||
|
data_offer.set_actions(ACTIONS, ACTIONS);
|
||||||
|
|
||||||
|
let pipe = Pipe::new().unwrap();
|
||||||
|
data_offer.receive(FILE_LIST_MIME_TYPE.to_string(), unsafe {
|
||||||
|
BorrowedFd::borrow_raw(pipe.write.as_raw_fd())
|
||||||
|
});
|
||||||
|
let fd = pipe.read;
|
||||||
|
drop(pipe.write);
|
||||||
|
|
||||||
|
let read_task = state
|
||||||
|
.common
|
||||||
|
.background_executor
|
||||||
|
.spawn(async { unsafe { read_fd(fd) } });
|
||||||
|
|
||||||
|
let this = this.clone();
|
||||||
|
state
|
||||||
|
.common
|
||||||
|
.foreground_executor
|
||||||
|
.spawn(async move {
|
||||||
|
let file_list = match read_task.await {
|
||||||
|
Ok(list) => list,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("error reading drag and drop pipe: {err:?}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let paths: SmallVec<[_; 2]> = file_list
|
||||||
|
.lines()
|
||||||
|
.map(|path| PathBuf::from(path.replace("file://", "")))
|
||||||
|
.collect();
|
||||||
|
let position = Point::new(x.into(), y.into());
|
||||||
|
|
||||||
|
// Prevent dropping text from other programs.
|
||||||
|
if paths.is_empty() {
|
||||||
|
data_offer.finish();
|
||||||
|
data_offer.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let input = PlatformInput::FileDrop(FileDropEvent::Entered {
|
||||||
|
position,
|
||||||
|
paths: crate::ExternalPaths(paths),
|
||||||
|
});
|
||||||
|
|
||||||
|
let client = this.get_client();
|
||||||
|
let mut state = client.borrow_mut();
|
||||||
|
state.drag.data_offer = Some(data_offer);
|
||||||
|
state.drag.window = Some(drag_window.clone());
|
||||||
|
state.drag.position = position;
|
||||||
|
|
||||||
|
drop(state);
|
||||||
|
drag_window.handle_input(input);
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wl_data_device::Event::Motion { x, y, .. } => {
|
||||||
|
let Some(drag_window) = state.drag.window.clone() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let position = Point::new(x.into(), y.into());
|
||||||
|
state.drag.position = position;
|
||||||
|
|
||||||
|
let input = PlatformInput::FileDrop(FileDropEvent::Pending { position });
|
||||||
|
drop(state);
|
||||||
|
drag_window.handle_input(input);
|
||||||
|
}
|
||||||
|
wl_data_device::Event::Leave => {
|
||||||
|
let Some(drag_window) = state.drag.window.clone() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let data_offer = state.drag.data_offer.clone().unwrap();
|
||||||
|
data_offer.destroy();
|
||||||
|
|
||||||
|
state.drag.data_offer = None;
|
||||||
|
state.drag.window = None;
|
||||||
|
|
||||||
|
let input = PlatformInput::FileDrop(FileDropEvent::Exited {});
|
||||||
|
drop(state);
|
||||||
|
drag_window.handle_input(input);
|
||||||
|
}
|
||||||
|
wl_data_device::Event::Drop => {
|
||||||
|
let Some(drag_window) = state.drag.window.clone() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let data_offer = state.drag.data_offer.clone().unwrap();
|
||||||
|
data_offer.finish();
|
||||||
|
data_offer.destroy();
|
||||||
|
|
||||||
|
state.drag.data_offer = None;
|
||||||
|
state.drag.window = None;
|
||||||
|
|
||||||
|
let input = PlatformInput::FileDrop(FileDropEvent::Submit {
|
||||||
|
position: state.drag.position,
|
||||||
|
});
|
||||||
|
drop(state);
|
||||||
|
drag_window.handle_input(input);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event_created_child!(WaylandClientStatePtr, wl_data_device::WlDataDevice, [
|
||||||
|
wl_data_device::EVT_DATA_OFFER_OPCODE => (wl_data_offer::WlDataOffer, ()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dispatch<wl_data_offer::WlDataOffer, ()> for WaylandClientStatePtr {
|
||||||
|
fn event(
|
||||||
|
this: &mut Self,
|
||||||
|
data_offer: &wl_data_offer::WlDataOffer,
|
||||||
|
event: wl_data_offer::Event,
|
||||||
|
_: &(),
|
||||||
|
_: &Connection,
|
||||||
|
_: &QueueHandle<Self>,
|
||||||
|
) {
|
||||||
|
let client = this.get_client();
|
||||||
|
let mut state = client.borrow_mut();
|
||||||
|
|
||||||
|
match event {
|
||||||
|
wl_data_offer::Event::Offer { mime_type } => {
|
||||||
|
if mime_type == FILE_LIST_MIME_TYPE {
|
||||||
|
data_offer.accept(state.serial, Some(mime_type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user