diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index b79c561a0..03641529d 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -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; diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 948d0a66f..39e1ff812 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -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); diff --git a/kitty/boss.py b/kitty/boss.py index 821d3a31d..46da32126 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -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 diff --git a/kitty/glfw.c b/kitty/glfw.c index 690335da4..54ecaf04d 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -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; } diff --git a/kitty/state.h b/kitty/state.h index 8a8216c51..3f8aa9295 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -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 diff --git a/kitty/window.py b/kitty/window.py index 7dd134e15..14e242534 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -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,