mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-10 04:30:12 +02:00
Allow cancelling the drag-and-drop with Escape
This commit is contained in:
@@ -82,6 +82,13 @@ void CALLBACK dnd_winevent_callback(HWINEVENTHOOK hWinEventHook,
|
|||||||
DWORD idEventThread,
|
DWORD idEventThread,
|
||||||
DWORD dwmsEventTime);
|
DWORD dwmsEventTime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the key code belonging to the Escape X11 keysym. If the keyboard somehow
|
||||||
|
* doesn't have an escape key, then this will return an nullopt.
|
||||||
|
*/
|
||||||
|
std::optional<xcb_keycode_t> find_escape_keycode(
|
||||||
|
xcb_connection_t& x11_connection);
|
||||||
|
|
||||||
ProxyWindow::ProxyWindow(std::shared_ptr<xcb_connection_t> x11_connection)
|
ProxyWindow::ProxyWindow(std::shared_ptr<xcb_connection_t> x11_connection)
|
||||||
: x11_connection(x11_connection),
|
: x11_connection(x11_connection),
|
||||||
window(xcb_generate_id(x11_connection.get())) {
|
window(xcb_generate_id(x11_connection.get())) {
|
||||||
@@ -196,6 +203,9 @@ void WineXdndProxy::begin_xdnd(const boost::container::small_vector_base<
|
|||||||
throw std::runtime_error("A drag-and-drop operation is already active");
|
throw std::runtime_error("A drag-and-drop operation is already active");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const xcb_setup_t* x11_setup = xcb_get_setup(x11_connection.get());
|
||||||
|
root_window = xcb_setup_roots_iterator(x11_setup).data->root;
|
||||||
|
|
||||||
// When XDND starts, we need to start listening for mouse events so we can
|
// When XDND starts, we need to start listening for mouse events so we can
|
||||||
// react when the mouse cursor hovers over a target that supports XDND. The
|
// react when the mouse cursor hovers over a target that supports XDND. The
|
||||||
// actual file contents will be transferred over X11 selections. See the
|
// actual file contents will be transferred over X11 selections. See the
|
||||||
@@ -203,6 +213,20 @@ void WineXdndProxy::begin_xdnd(const boost::container::small_vector_base<
|
|||||||
// https://www.freedesktop.org/wiki/Specifications/XDND/#atomsandproperties
|
// https://www.freedesktop.org/wiki/Specifications/XDND/#atomsandproperties
|
||||||
xcb_set_selection_owner(x11_connection.get(), proxy_window.window,
|
xcb_set_selection_owner(x11_connection.get(), proxy_window.window,
|
||||||
xcb_xdnd_selection, XCB_CURRENT_TIME);
|
xcb_xdnd_selection, XCB_CURRENT_TIME);
|
||||||
|
|
||||||
|
// Escape key presses are supposed to cancel the drag-and-drop operation, so
|
||||||
|
// we will try to grab this key since Wine actually isn't doing that (they
|
||||||
|
// only listen for key pressed on their own windows). If we can't grab the
|
||||||
|
// keyboard, then it's not a huge deal. Oh and we also need to figure out
|
||||||
|
// what keycode the escape key corresponds to first.
|
||||||
|
if (!escape_keycode) {
|
||||||
|
escape_keycode = find_escape_keycode(*x11_connection);
|
||||||
|
}
|
||||||
|
if (escape_keycode) {
|
||||||
|
xcb_grab_key(x11_connection.get(), false, root_window, XCB_GRAB_ANY,
|
||||||
|
*escape_keycode, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
|
||||||
|
}
|
||||||
|
|
||||||
xcb_flush(x11_connection.get());
|
xcb_flush(x11_connection.get());
|
||||||
|
|
||||||
// We will transfer the files in `text/uri-list` format, so a string of URIs
|
// We will transfer the files in `text/uri-list` format, so a string of URIs
|
||||||
@@ -236,6 +260,10 @@ void WineXdndProxy::begin_xdnd(const boost::container::small_vector_base<
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WineXdndProxy::end_xdnd() {
|
void WineXdndProxy::end_xdnd() {
|
||||||
|
if (escape_keycode) {
|
||||||
|
xcb_ungrab_key(x11_connection.get(), *escape_keycode, root_window,
|
||||||
|
XCB_GRAB_ANY);
|
||||||
|
}
|
||||||
xcb_set_selection_owner(x11_connection.get(), XCB_NONE, xcb_xdnd_selection,
|
xcb_set_selection_owner(x11_connection.get(), XCB_NONE, xcb_xdnd_selection,
|
||||||
XCB_CURRENT_TIME);
|
XCB_CURRENT_TIME);
|
||||||
xcb_flush(x11_connection.get());
|
xcb_flush(x11_connection.get());
|
||||||
@@ -255,9 +283,6 @@ void WineXdndProxy::end_xdnd() {
|
|||||||
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
|
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
|
||||||
|
|
||||||
void WineXdndProxy::run_xdnd_loop() {
|
void WineXdndProxy::run_xdnd_loop() {
|
||||||
const xcb_window_t root_window =
|
|
||||||
xcb_setup_roots_iterator(xcb_get_setup(x11_connection.get()))
|
|
||||||
.data->root;
|
|
||||||
const HWND windows_desktop_window = GetDesktopWindow();
|
const HWND windows_desktop_window = GetDesktopWindow();
|
||||||
|
|
||||||
std::optional<xcb_window_t> last_xdnd_window;
|
std::optional<xcb_window_t> last_xdnd_window;
|
||||||
@@ -324,9 +349,10 @@ void WineXdndProxy::run_xdnd_loop() {
|
|||||||
// the mouse cursor position, and we will end the drag once the left mouse
|
// the mouse cursor position, and we will end the drag once the left mouse
|
||||||
// button gets released.
|
// button gets released.
|
||||||
bool left_mouse_button_held = true;
|
bool left_mouse_button_held = true;
|
||||||
|
bool escape_pressed = false;
|
||||||
std::optional<uint16_t> last_pointer_x;
|
std::optional<uint16_t> last_pointer_x;
|
||||||
std::optional<uint16_t> last_pointer_y;
|
std::optional<uint16_t> last_pointer_y;
|
||||||
while (left_mouse_button_held) {
|
while (left_mouse_button_held && !escape_pressed) {
|
||||||
usleep(1000);
|
usleep(1000);
|
||||||
|
|
||||||
std::unique_ptr<xcb_generic_event_t> generic_event;
|
std::unique_ptr<xcb_generic_event_t> generic_event;
|
||||||
@@ -335,6 +361,16 @@ void WineXdndProxy::run_xdnd_loop() {
|
|||||||
const uint8_t event_type =
|
const uint8_t event_type =
|
||||||
generic_event->response_type & xcb_event_type_mask;
|
generic_event->response_type & xcb_event_type_mask;
|
||||||
switch (event_type) {
|
switch (event_type) {
|
||||||
|
// As with the regular Windows drag-and-drop, we should allow
|
||||||
|
// cancelling the operation when the escape key is pressed
|
||||||
|
case XCB_KEY_PRESS: {
|
||||||
|
const auto event = reinterpret_cast<xcb_key_press_event_t*>(
|
||||||
|
generic_event.get());
|
||||||
|
|
||||||
|
if (escape_keycode && event->detail == *escape_keycode) {
|
||||||
|
escape_pressed = true;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
case XCB_SELECTION_REQUEST: {
|
case XCB_SELECTION_REQUEST: {
|
||||||
handle_convert_selection(
|
handle_convert_selection(
|
||||||
*reinterpret_cast<xcb_selection_request_event_t*>(
|
*reinterpret_cast<xcb_selection_request_event_t*>(
|
||||||
@@ -459,9 +495,8 @@ void WineXdndProxy::run_xdnd_loop() {
|
|||||||
// 1) Finish the drop, if `last_xdnd_window` is a valid XDND window
|
// 1) Finish the drop, if `last_xdnd_window` is a valid XDND window
|
||||||
// 2) Cancel the drop, if the escape key is being held, or
|
// 2) Cancel the drop, if the escape key is being held, or
|
||||||
// 3) Don't do antyhing, if `last_xdnd_window` is a nullopt
|
// 3) Don't do antyhing, if `last_xdnd_window` is a nullopt
|
||||||
// TODO: Check if the escape key is pressed to allow cancelling the drop. In
|
if (!last_xdnd_window || escape_pressed) {
|
||||||
// that case we should call `maybe_leave_last_window()`
|
maybe_leave_last_window();
|
||||||
if (!last_xdnd_window) {
|
|
||||||
end_xdnd();
|
end_xdnd();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -855,3 +890,38 @@ void CALLBACK dnd_winevent_callback(HWINEVENTHOOK /*hWinEventHook*/,
|
|||||||
std::cerr << error.what() << std::endl;
|
std::cerr << error.what() << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<xcb_keycode_t> find_escape_keycode(
|
||||||
|
xcb_connection_t& x11_connection) {
|
||||||
|
const xcb_setup_t* x11_setup = xcb_get_setup(&x11_connection);
|
||||||
|
|
||||||
|
xcb_generic_error_t* error = nullptr;
|
||||||
|
const xcb_get_keyboard_mapping_cookie_t get_keyboard_cookie =
|
||||||
|
xcb_get_keyboard_mapping(
|
||||||
|
&x11_connection, x11_setup->min_keycode,
|
||||||
|
x11_setup->max_keycode - x11_setup->min_keycode + 1);
|
||||||
|
std::unique_ptr<xcb_get_keyboard_mapping_reply_t> get_keyboard_reply(
|
||||||
|
xcb_get_keyboard_mapping_reply(&x11_connection, get_keyboard_cookie,
|
||||||
|
&error));
|
||||||
|
if (error) {
|
||||||
|
free(error);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const xcb_keysym_t* keysyms =
|
||||||
|
xcb_get_keyboard_mapping_keysyms(get_keyboard_reply.get());
|
||||||
|
const size_t num_keysyms =
|
||||||
|
xcb_get_keyboard_mapping_keysyms_length(get_keyboard_reply.get());
|
||||||
|
for (size_t i = 0; i < num_keysyms; i++) {
|
||||||
|
const size_t keycode = x11_setup->min_keycode +
|
||||||
|
(i / get_keyboard_reply->keysyms_per_keycode);
|
||||||
|
|
||||||
|
// https://www.x.org/releases/X11R7.7/doc/xproto/x11protocol.html#Function_KEYSYMs
|
||||||
|
constexpr xcb_keysym_t escape_keysym = 0xFF1B;
|
||||||
|
if (keysyms[i] == escape_keysym) {
|
||||||
|
return keycode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|||||||
@@ -262,6 +262,17 @@ class WineXdndProxy {
|
|||||||
*/
|
*/
|
||||||
Win32Thread xdnd_handler;
|
Win32Thread xdnd_handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The X11 root window.
|
||||||
|
*/
|
||||||
|
xcb_window_t root_window;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The X11 keycode for the escape key. We need to figure this out once when
|
||||||
|
* the first drag-and-drop operation happens.
|
||||||
|
*/
|
||||||
|
std::optional<xcb_keycode_t> escape_keycode;
|
||||||
|
|
||||||
// These are the atoms used for the XDND protocol, as described by
|
// These are the atoms used for the XDND protocol, as described by
|
||||||
// https://www.freedesktop.org/wiki/Specifications/XDND/#atomsandproperties
|
// https://www.freedesktop.org/wiki/Specifications/XDND/#atomsandproperties
|
||||||
xcb_atom_t xcb_xdnd_selection;
|
xcb_atom_t xcb_xdnd_selection;
|
||||||
|
|||||||
Reference in New Issue
Block a user