When dropping, paste into window under mouse cursor rather than active window

This commit is contained in:
Kovid Goyal 2026-02-04 21:21:25 +05:30
parent 7004911271
commit d51dec8187
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
6 changed files with 59 additions and 31 deletions

View file

@ -1384,6 +1384,10 @@ static void freeFilteredDragMimes(_GLFWwindow* window, int old_count, int new_co
const NSPoint pos = [sender draggingLocation];
double xpos = pos.x;
double ypos = contentRect.size.height - pos.y;
float xscale = 1, yscale = 1;
_glfwPlatformGetWindowContentScale(window, &xscale, &yscale);
xpos *= xscale; ypos *= yscale;
// Get MIME types from the dragging pasteboard
NSPasteboard* pasteboard = [sender draggingPasteboard];
@ -1462,6 +1466,9 @@ static void freeFilteredDragMimes(_GLFWwindow* window, int old_count, int new_co
const NSPoint pos = [sender draggingLocation];
double xpos = pos.x;
double ypos = contentRect.size.height - pos.y;
float xscale = 1, yscale = 1;
_glfwPlatformGetWindowContentScale(window, &xscale, &yscale);
xpos *= xscale; ypos *= yscale;
// Call drag move callback with cached MIME types
int old_count = window->ns.dragMimeCount;

15
glfw/wl_window.c vendored
View file

@ -2473,7 +2473,8 @@ static void update_drag_state(_GLFWWaylandDataOffer *d, _GLFWwindow* window UNUS
}
}
static void drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) {
static void
drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) {
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
_GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i;
if (d->id == id) {
@ -2489,8 +2490,9 @@ static void drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device
// Call drag enter callback with writable MIME types array
double xpos = wl_fixed_to_double(x);
double ypos = wl_fixed_to_double(y);
double scale = _glfwWaylandWindowScale(window);
int mime_count = (int)d->mimes_count;
int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, xpos, ypos, d->mimes, &mime_count);
int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, scale * xpos, scale * ypos, d->mimes, &mime_count);
// Update drag state based on callback results
update_drag_state(d, window, accepted, mime_count);
@ -2505,7 +2507,8 @@ static void drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device
prune_unclaimed_data_offers();
}
static void drag_leave(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED) {
static void
drag_leave(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED) {
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
// Find the window for this offer and call the leave callback
@ -2562,7 +2565,8 @@ drop(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED) {
}
}
static void motion(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint32_t time UNUSED, wl_fixed_t x, wl_fixed_t y) {
static void
motion(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint32_t time UNUSED, wl_fixed_t x, wl_fixed_t y) {
// Find the current drag offer and send motion events
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
_GLFWWaylandDataOffer *d = &_glfw.wl.dataOffers[i];
@ -2572,9 +2576,10 @@ static void motion(void *data UNUSED, struct wl_data_device *wl_data_device UNUS
if (window->wl.surface == d->surface) {
double xpos = wl_fixed_to_double(x);
double ypos = wl_fixed_to_double(y);
double scale = _glfwWaylandWindowScale(window);
// Pass the MIME types array for move events
int mime_count = (int)d->mimes_count;
int accepted = _glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos, d->mimes, &mime_count);
int accepted = _glfwInputDragEvent(window, GLFW_DRAG_MOVE, scale * xpos, scale * ypos, d->mimes, &mime_count);
// Update drag state based on callback results
update_drag_state(d, window, accepted, mime_count);

View file

@ -114,6 +114,7 @@ from .fast_data_types import (
toggle_maximized,
toggle_os_window_visibility,
toggle_secure_input,
viewport_for_window,
wrapped_kitten_names,
)
from .key_encoding import get_name_to_functional_number_map
@ -151,7 +152,6 @@ from .utils import (
open_url,
parse_address_spec,
parse_os_window_state,
parse_uri_list,
platform_window_id,
safe_print,
sanitize_url_for_display_to_user,
@ -1883,28 +1883,24 @@ class Boss:
if tm is not None:
tm.update_tab_bar_data()
def on_drop(self, os_window_id: int, drop: dict[str, bytes] | Exception) -> None:
def on_drop(self, os_window_id: int, drop: dict[str, bytes] | Exception, x: int, y: int) -> None:
if isinstance(drop, Exception):
self.show_error(_('Drop failed'), str(drop))
return
tm = self.os_window_map.get(os_window_id)
if tm is not None:
w = tm.active_window
if w is not None:
text = ''
if uri_list := drop.pop('text/uri-list', b''):
urls = parse_uri_list(uri_list.decode('utf-8', 'replace'))
if w.at_prompt:
import shlex
text = ' '.join(map(shlex.quote, urls))
else:
text = '\n'.join(urls)
elif tp := drop.pop('text/plain', b''):
text = tp.decode('utf-8', 'replace')
elif tp := drop.pop('text/plain;charset=utf-8', b''):
text = tp.decode('utf-8', 'replace')
if text:
w.paste_text(text)
central, tab_bar = viewport_for_window(os_window_id)[:2]
if central.left <= x < central.right and central.top <= y < central.bottom:
if (tm := self.os_window_map.get(os_window_id)) is None or (tab := tm.active_tab) is None:
return
for window in tab:
g = window.geometry
if g.left <= x - central.left < g.right and g.top <= y - central.top < g.bottom:
window.on_drop(drop)
break
elif tab_bar.left <= x < tab_bar.right and tab_bar.top <= y < central.bottom:
if (tm := self.os_window_map.get(os_window_id)) is None:
return
if (tab_id := tm.tab_bar.tab_id_at(x)) and (tab := self.tab_for_id(tab_id)) and (w := tab.active_window):
w.on_drop(drop)
@ac('win', '''
Focus the nth OS window if positive or the previously active OS windows if negative. When the number is larger

View file

@ -657,6 +657,9 @@ drag_callback(GLFWwindow *w, GLFWDragEventType event, double xpos, double ypos,
switch (event) {
case GLFW_DRAG_ENTER:
case GLFW_DRAG_MOVE:
global_state.callback_os_window->last_drag_event.x = (int)xpos;
global_state.callback_os_window->last_drag_event.y = (int)ypos;
/* fallthrough */
case GLFW_DRAG_STATUS_UPDATE:
if (mime_types && mime_count && *mime_count > 0) {
// Sort MIME types by priority (descending) and keep only accepted ones
@ -693,6 +696,8 @@ drag_callback(GLFWwindow *w, GLFWDragEventType event, double xpos, double ypos,
}
break;
case GLFW_DRAG_LEAVE:
global_state.callback_os_window->last_drag_event.x = (int)xpos;
global_state.callback_os_window->last_drag_event.y = (int)ypos;
break;
}
end:
@ -745,8 +750,8 @@ drop_callback(GLFWwindow *w, GLFWDropData *drop) {
RAII_PyObject(exc, PyErr_GetRaisedException());
glfwFinishDrop(drop, GLFW_DRAG_OPERATION_COPY, true);
if (!set_callback_window(w)) return;
if (exc != NULL) { WINDOW_CALLBACK(on_drop, "O", exc); }
else if (PyDict_Size(ans)) WINDOW_CALLBACK(on_drop, "O", ans);
if (exc != NULL) { WINDOW_CALLBACK(on_drop, "Oii", exc, global_state.callback_os_window->last_drag_event.x, global_state.callback_os_window->last_drag_event.y); }
else if (PyDict_Size(ans)) WINDOW_CALLBACK(on_drop, "Oii", ans, global_state.callback_os_window->last_drag_event.x, global_state.callback_os_window->last_drag_event.y);
request_tick_callback();
global_state.callback_os_window = NULL;
}

View file

@ -342,6 +342,7 @@ typedef struct OSWindow {
id_type last_focused_counter;
CloseRequest close_request;
bool is_layer_shell, hide_on_focus_loss;
struct { int x, y; } last_drag_event;
} OSWindow;
static inline float

View file

@ -106,6 +106,7 @@ from .utils import (
log_error,
open_cmd,
open_url,
parse_uri_list,
path_from_osc7_url,
resolve_custom_file,
resolved_shell,
@ -981,10 +982,6 @@ class Window:
if update_ime_position:
update_ime_position_for_window(self.id, True)
def contains(self, x: int, y: int) -> bool:
g = self.geometry
return g.left <= x <= g.right and g.top <= y <= g.bottom
def close(self) -> None:
get_boss().mark_window_for_close(self)
@ -1942,6 +1939,23 @@ class Window:
' Return the last position at which a mouse event was received by this window '
return get_mouse_data_for_window(self.os_window_id, self.tab_id, self.id)
def on_drop(self, drop: dict[str, bytes]) -> None:
text = ''
if uri_list := drop.pop('text/uri-list', b''):
urls = parse_uri_list(uri_list.decode('utf-8', 'replace'))
if self.at_prompt:
import shlex
text = ' '.join(map(shlex.quote, urls))
else:
text = '\n'.join(urls)
elif tp := drop.pop('text/plain', b''):
text = tp.decode('utf-8', 'replace')
elif tp := drop.pop('text/plain;charset=utf-8', b''):
text = tp.decode('utf-8', 'replace')
if text:
self.paste_text(text)
# Serialization {{{
def as_dict(
self, is_focused: bool = False, is_self: bool = False, is_active: bool = False,