mirror of
https://github.com/zed-industries/zed.git
synced 2025-02-03 17:44:30 +00:00
x11: Implement Drag and Drop (#17491)
Closes #16225 Release Notes: - x11: Implemented Drag and Drop.
This commit is contained in:
parent
12dde17608
commit
59be07ad90
3 changed files with 226 additions and 5 deletions
|
@ -1,3 +1,4 @@
|
|||
use core::str;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashSet;
|
||||
use std::ops::Deref;
|
||||
|
@ -9,6 +10,8 @@ use calloop::generic::{FdWrapper, Generic};
|
|||
use calloop::{EventLoop, LoopHandle, RegistrationToken};
|
||||
|
||||
use collections::HashMap;
|
||||
use http_client::Url;
|
||||
use smallvec::SmallVec;
|
||||
use util::ResultExt;
|
||||
|
||||
use x11rb::connection::{Connection, RequestConnection};
|
||||
|
@ -17,9 +20,13 @@ use x11rb::errors::ConnectionError;
|
|||
use x11rb::protocol::randr::ConnectionExt as _;
|
||||
use x11rb::protocol::xinput::ConnectionExt;
|
||||
use x11rb::protocol::xkb::ConnectionExt as _;
|
||||
use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _, KeyPressEvent};
|
||||
use x11rb::protocol::xproto::{
|
||||
AtomEnum, ChangeWindowAttributesAux, ClientMessageData, ClientMessageEvent, ConnectionExt as _,
|
||||
EventMask, KeyPressEvent,
|
||||
};
|
||||
use x11rb::protocol::{randr, render, xinput, xkb, xproto, Event};
|
||||
use x11rb::resource_manager::Database;
|
||||
use x11rb::wrapper::ConnectionExt as _;
|
||||
use x11rb::xcb_ffi::XCBConnection;
|
||||
use xim::{x11rb::X11rbClient, Client};
|
||||
use xim::{AttributeName, InputStyle};
|
||||
|
@ -30,8 +37,8 @@ use crate::platform::linux::LinuxClient;
|
|||
use crate::platform::{LinuxCommon, PlatformWindow};
|
||||
use crate::{
|
||||
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
|
||||
DisplayId, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, Platform, PlatformDisplay,
|
||||
PlatformInput, Point, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
|
||||
DisplayId, FileDropEvent, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, Platform,
|
||||
PlatformDisplay, PlatformInput, Point, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
|
||||
};
|
||||
|
||||
use super::{button_of_key, modifiers_from_state, pressed_button_from_mask};
|
||||
|
@ -101,6 +108,14 @@ struct XKBStateNotiy {
|
|||
locked_layout: LayoutIndex,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Xdnd {
|
||||
other_window: xproto::Window,
|
||||
drag_type: u32,
|
||||
retrieved: bool,
|
||||
position: Point<Pixels>,
|
||||
}
|
||||
|
||||
pub struct X11ClientState {
|
||||
pub(crate) loop_handle: LoopHandle<'static, X11Client>,
|
||||
pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,
|
||||
|
@ -142,6 +157,7 @@ pub struct X11ClientState {
|
|||
pub(crate) common: LinuxCommon,
|
||||
pub(crate) clipboard: x11_clipboard::Clipboard,
|
||||
pub(crate) clipboard_item: Option<ClipboardItem>,
|
||||
pub(crate) xdnd_state: Xdnd,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -423,6 +439,7 @@ impl X11Client {
|
|||
|
||||
clipboard,
|
||||
clipboard_item: None,
|
||||
xdnd_state: Xdnd::default(),
|
||||
})))
|
||||
}
|
||||
|
||||
|
@ -611,7 +628,7 @@ impl X11Client {
|
|||
match event {
|
||||
Event::ClientMessage(event) => {
|
||||
let window = self.get_window(event.window)?;
|
||||
let [atom, _arg1, arg2, arg3, _arg4] = event.data.as_data32();
|
||||
let [atom, arg1, arg2, arg3, arg4] = event.data.as_data32();
|
||||
let mut state = self.0.borrow_mut();
|
||||
|
||||
if atom == state.atoms.WM_DELETE_WINDOW {
|
||||
|
@ -627,6 +644,106 @@ impl X11Client {
|
|||
hi: arg3 as i32,
|
||||
})
|
||||
}
|
||||
|
||||
if event.type_ == state.atoms.XdndEnter {
|
||||
state.xdnd_state.other_window = atom;
|
||||
if (arg1 & 0x1) == 0x1 {
|
||||
state.xdnd_state.drag_type = xdnd_get_supported_atom(
|
||||
&state.xcb_connection,
|
||||
&state.atoms,
|
||||
state.xdnd_state.other_window,
|
||||
);
|
||||
} else {
|
||||
if let Some(atom) = [arg2, arg3, arg4]
|
||||
.into_iter()
|
||||
.find(|atom| xdnd_is_atom_supported(*atom, &state.atoms))
|
||||
{
|
||||
state.xdnd_state.drag_type = atom;
|
||||
}
|
||||
}
|
||||
} else if event.type_ == state.atoms.XdndLeave {
|
||||
window.handle_input(PlatformInput::FileDrop(FileDropEvent::Pending {
|
||||
position: state.xdnd_state.position,
|
||||
}));
|
||||
window.handle_input(PlatformInput::FileDrop(FileDropEvent::Exited {}));
|
||||
state.xdnd_state = Xdnd::default();
|
||||
} else if event.type_ == state.atoms.XdndPosition {
|
||||
if let Ok(pos) = state
|
||||
.xcb_connection
|
||||
.query_pointer(event.window)
|
||||
.unwrap()
|
||||
.reply()
|
||||
{
|
||||
state.xdnd_state.position =
|
||||
Point::new(Pixels(pos.win_x as f32), Pixels(pos.win_y as f32));
|
||||
}
|
||||
if !state.xdnd_state.retrieved {
|
||||
state
|
||||
.xcb_connection
|
||||
.convert_selection(
|
||||
event.window,
|
||||
state.atoms.XdndSelection,
|
||||
state.xdnd_state.drag_type,
|
||||
state.atoms.XDND_DATA,
|
||||
arg3,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
xdnd_send_status(
|
||||
&state.xcb_connection,
|
||||
&state.atoms,
|
||||
event.window,
|
||||
state.xdnd_state.other_window,
|
||||
arg4,
|
||||
);
|
||||
window.handle_input(PlatformInput::FileDrop(FileDropEvent::Pending {
|
||||
position: state.xdnd_state.position,
|
||||
}));
|
||||
} else if event.type_ == state.atoms.XdndDrop {
|
||||
xdnd_send_finished(
|
||||
&state.xcb_connection,
|
||||
&state.atoms,
|
||||
event.window,
|
||||
state.xdnd_state.other_window,
|
||||
);
|
||||
window.handle_input(PlatformInput::FileDrop(FileDropEvent::Submit {
|
||||
position: state.xdnd_state.position,
|
||||
}));
|
||||
state.xdnd_state = Xdnd::default();
|
||||
}
|
||||
}
|
||||
Event::SelectionNotify(event) => {
|
||||
let window = self.get_window(event.requestor)?;
|
||||
let mut state = self.0.borrow_mut();
|
||||
let property = state.xcb_connection.get_property(
|
||||
false,
|
||||
event.requestor,
|
||||
state.atoms.XDND_DATA,
|
||||
AtomEnum::ANY,
|
||||
0,
|
||||
1024,
|
||||
);
|
||||
if property.as_ref().log_err().is_none() {
|
||||
return Some(());
|
||||
}
|
||||
if let Ok(reply) = property.unwrap().reply() {
|
||||
match str::from_utf8(&reply.value) {
|
||||
Ok(file_list) => {
|
||||
let paths: SmallVec<[_; 2]> = file_list
|
||||
.lines()
|
||||
.filter_map(|path| Url::parse(path).log_err())
|
||||
.filter_map(|url| url.to_file_path().log_err())
|
||||
.collect();
|
||||
let input = PlatformInput::FileDrop(FileDropEvent::Entered {
|
||||
position: state.xdnd_state.position,
|
||||
paths: crate::ExternalPaths(paths),
|
||||
});
|
||||
window.handle_input(input);
|
||||
state.xdnd_state.retrieved = true;
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::ConfigureNotify(event) => {
|
||||
let bounds = Bounds {
|
||||
|
@ -1179,6 +1296,16 @@ impl LinuxClient for X11Client {
|
|||
state.scale_factor,
|
||||
state.common.appearance,
|
||||
)?;
|
||||
state
|
||||
.xcb_connection
|
||||
.change_property32(
|
||||
xproto::PropMode::REPLACE,
|
||||
x_window,
|
||||
state.atoms.XdndAware,
|
||||
state.atoms.XA_ATOM,
|
||||
&[5],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let screen_resources = state
|
||||
.xcb_connection
|
||||
|
@ -1540,3 +1667,78 @@ fn check_gtk_frame_extents_supported(
|
|||
|
||||
supported_atoms.contains(&atoms._GTK_FRAME_EXTENTS)
|
||||
}
|
||||
|
||||
fn xdnd_is_atom_supported(atom: u32, atoms: &XcbAtoms) -> bool {
|
||||
return atom == atoms.TEXT
|
||||
|| atom == atoms.STRING
|
||||
|| atom == atoms.UTF8_STRING
|
||||
|| atom == atoms.TEXT_PLAIN
|
||||
|| atom == atoms.TEXT_PLAIN_UTF8
|
||||
|| atom == atoms.TextUriList;
|
||||
}
|
||||
|
||||
fn xdnd_get_supported_atom(
|
||||
xcb_connection: &XCBConnection,
|
||||
supported_atoms: &XcbAtoms,
|
||||
target: xproto::Window,
|
||||
) -> u32 {
|
||||
let property = xcb_connection
|
||||
.get_property(
|
||||
false,
|
||||
target,
|
||||
supported_atoms.XdndTypeList,
|
||||
AtomEnum::ANY,
|
||||
0,
|
||||
1024,
|
||||
)
|
||||
.unwrap();
|
||||
if let Ok(reply) = property.reply() {
|
||||
if let Some(atoms) = reply.value32() {
|
||||
for atom in atoms {
|
||||
if xdnd_is_atom_supported(atom, &supported_atoms) {
|
||||
return atom;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn xdnd_send_finished(
|
||||
xcb_connection: &XCBConnection,
|
||||
atoms: &XcbAtoms,
|
||||
source: xproto::Window,
|
||||
target: xproto::Window,
|
||||
) {
|
||||
let message = ClientMessageEvent {
|
||||
format: 32,
|
||||
window: target,
|
||||
type_: atoms.XdndFinished,
|
||||
data: ClientMessageData::from([source, 1, atoms.XdndActionCopy, 0, 0]),
|
||||
sequence: 0,
|
||||
response_type: xproto::CLIENT_MESSAGE_EVENT,
|
||||
};
|
||||
xcb_connection
|
||||
.send_event(false, target, EventMask::default(), message)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn xdnd_send_status(
|
||||
xcb_connection: &XCBConnection,
|
||||
atoms: &XcbAtoms,
|
||||
source: xproto::Window,
|
||||
target: xproto::Window,
|
||||
action: u32,
|
||||
) {
|
||||
let message = ClientMessageEvent {
|
||||
format: 32,
|
||||
window: target,
|
||||
type_: atoms.XdndStatus,
|
||||
data: ClientMessageData::from([source, 1, 0, 0, action]),
|
||||
sequence: 0,
|
||||
response_type: xproto::CLIENT_MESSAGE_EVENT,
|
||||
};
|
||||
xcb_connection
|
||||
.send_event(false, target, EventMask::default(), message)
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
@ -32,7 +32,24 @@ use std::{
|
|||
use super::{X11Display, XINPUT_MASTER_DEVICE};
|
||||
x11rb::atom_manager! {
|
||||
pub XcbAtoms: AtomsCookie {
|
||||
XA_ATOM,
|
||||
XdndAware,
|
||||
XdndStatus,
|
||||
XdndEnter,
|
||||
XdndLeave,
|
||||
XdndPosition,
|
||||
XdndSelection,
|
||||
XdndDrop,
|
||||
XdndFinished,
|
||||
XdndTypeList,
|
||||
XdndActionCopy,
|
||||
TextUriList: b"text/uri-list",
|
||||
UTF8_STRING,
|
||||
TEXT,
|
||||
STRING,
|
||||
TEXT_PLAIN_UTF8: b"text/plain;charset=utf-8",
|
||||
TEXT_PLAIN: b"text/plain",
|
||||
XDND_DATA,
|
||||
WM_PROTOCOLS,
|
||||
WM_DELETE_WINDOW,
|
||||
WM_CHANGE_STATE,
|
||||
|
|
|
@ -56,6 +56,8 @@ extend-ignore-re = [
|
|||
"rename = \"sesssion_id\"",
|
||||
"doas",
|
||||
# ProtoLS crate with tree-sitter Protobuf grammar.
|
||||
"protols"
|
||||
"protols",
|
||||
# x11rb SelectionNotifyEvent struct field
|
||||
"requestor"
|
||||
]
|
||||
check-filename = true
|
||||
|
|
Loading…
Reference in a new issue