Wayland: Implement ridiculous workaround for broken dnd
Wayland is *the worst* window system I have ever had the displeasure of trying to support.
This commit is contained in:
parent
322a51da98
commit
9f2a4ad2dd
11 changed files with 50 additions and 26 deletions
2
glfw/glfw3.h
vendored
2
glfw/glfw3.h
vendored
|
|
@ -4984,7 +4984,7 @@ GLFWAPI GLFWdragsourcefun glfwSetDragSourceCallback(GLFWwindow* window, GLFWdrag
|
|||
// Start a drag. If called with operations == -1 indicates that previously
|
||||
// requested data via GLFW_DRAG_DATA_REQUEST is ready. operations == -2 means
|
||||
// that the drag image is changed.
|
||||
GLFWAPI int glfwStartDrag(GLFWwindow* window, const GLFWDragSourceItem *items, size_t mime_count, const GLFWimage* thumbnail, int operations);
|
||||
GLFWAPI int glfwStartDrag(GLFWwindow* window, const GLFWDragSourceItem *items, size_t mime_count, const GLFWimage* thumbnail, int operations, bool needs_toplevel_on_wayland);
|
||||
|
||||
/*! @brief Returns whether the specified joystick is present.
|
||||
*
|
||||
|
|
|
|||
3
glfw/input.c
vendored
3
glfw/input.c
vendored
|
|
@ -1161,7 +1161,7 @@ _glfwFreeDragSourceData(void) {
|
|||
}
|
||||
|
||||
GLFWAPI int
|
||||
glfwStartDrag(GLFWwindow* handle, const GLFWDragSourceItem *items, size_t item_count, const GLFWimage* thumbnail, int operations) {
|
||||
glfwStartDrag(GLFWwindow* handle, const GLFWDragSourceItem *items, size_t item_count, const GLFWimage* thumbnail, int operations, bool needs_toplevel_on_wayland) {
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
assert(window != NULL);
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(EINVAL);
|
||||
|
|
@ -1188,6 +1188,7 @@ glfwStartDrag(GLFWwindow* handle, const GLFWDragSourceItem *items, size_t item_c
|
|||
}
|
||||
_glfw.drag.window_id = window->id;
|
||||
_glfw.drag.operations = operations;
|
||||
_glfw.drag.needs_toplevel_on_wayland = needs_toplevel_on_wayland;
|
||||
int ans = _glfwPlatformStartDrag(window, thumbnail);
|
||||
if (ans != 0) _glfwFreeDragSourceData();
|
||||
return ans;
|
||||
|
|
|
|||
1
glfw/internal.h
vendored
1
glfw/internal.h
vendored
|
|
@ -668,6 +668,7 @@ struct _GLFWlibrary
|
|||
struct {
|
||||
GLFWDragSourceItem *items; size_t item_count;
|
||||
GLFWid window_id, instance_id; int operations;
|
||||
bool needs_toplevel_on_wayland;
|
||||
} drag;
|
||||
};
|
||||
|
||||
|
|
|
|||
2
glfw/wl_init.c
vendored
2
glfw/wl_init.c
vendored
|
|
@ -789,7 +789,7 @@ get_compositor_missing_capabilities(void) {
|
|||
C(single_pixel_buffer, wp_single_pixel_buffer_manager_v1); C(preferred_scale, has_preferred_buffer_scale);
|
||||
C(idle_inhibit, idle_inhibit_manager); C(icon, xdg_toplevel_icon_manager_v1); C(bell, xdg_system_bell_v1);
|
||||
C(window-tag, xdg_toplevel_tag_manager_v1); C(keyboard_shortcuts_inhibit, keyboard_shortcuts_inhibit_manager);
|
||||
C(key-repeat, has_key_repeat_events);
|
||||
C(key-repeat, has_key_repeat_events); C(top_level_drag, xdg_toplevel_drag_manager_v1);
|
||||
#define P(x) p += snprintf(p, sizeof(buf) - (p - buf), "%s ", x);
|
||||
if (_glfw.wl.xdg_wm_base_version < 6) P("window-state-suspended");
|
||||
if (_glfw.wl.xdg_wm_base_version < 5) P("window-capabilities");
|
||||
|
|
|
|||
6
glfw/wl_window.c
vendored
6
glfw/wl_window.c
vendored
|
|
@ -3325,8 +3325,8 @@ drag_source_cancelled(void *data UNUSED, struct wl_data_source *source UNUSED) {
|
|||
// Uber competent Wayland people contravene their own spec and make it
|
||||
// impossible to distinguish between drag cancelled and dropped but not
|
||||
// accepted. https://gitlab.freedesktop.org/wayland/wayland/-/issues/140
|
||||
// so we assume this is a drop. Sigh.
|
||||
cancel_drag(GLFW_DRAG_DROPPED);
|
||||
// so we assume this is a drop unless we are in top-level mode. Sigh.
|
||||
cancel_drag(_glfw.wl.drag.toplevel_xdg_toplevel ? GLFW_DRAG_CANCELLED : GLFW_DRAG_DROPPED);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -3411,7 +3411,7 @@ _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail) {
|
|||
wl_surface_set_buffer_scale(_glfw.wl.drag.drag_icon, scale);
|
||||
}
|
||||
|
||||
if (_glfw.wl.xdg_toplevel_drag_manager_v1) {
|
||||
if (_glfw.drag.needs_toplevel_on_wayland && _glfw.wl.xdg_toplevel_drag_manager_v1) {
|
||||
_glfw.wl.drag.toplevel_drag = xdg_toplevel_drag_manager_v1_get_xdg_toplevel_drag(
|
||||
_glfw.wl.xdg_toplevel_drag_manager_v1, _glfw.wl.drag.source);
|
||||
if (!_glfw.wl.drag.toplevel_drag) return ENOMEM;
|
||||
|
|
|
|||
|
|
@ -1945,14 +1945,20 @@ def on_drop(self, os_window_id: int, drop: dict[str, bytes] | int, from_self: bo
|
|||
w.on_drop(drop)
|
||||
|
||||
def on_drag_source_finished(
|
||||
self, was_dropped: bool, was_canceled: bool, accepted_mime_type: str, action: int, data: dict[str, bytes] | None
|
||||
self, was_dropped: bool, was_canceled: bool, accepted_mime_type: str, action: int, data: dict[str, bytes] | None,
|
||||
needs_toplevel_on_wayland: bool
|
||||
) -> None:
|
||||
if (tab_id := int((data or {}).get(f'application/net.kovidgoyal.kitty-tab-{os.getpid()}', b'0').decode())
|
||||
) and get_tab_being_dragged()[0] == tab_id:
|
||||
) and get_tab_being_dragged()[0] == tab_id and (tab := self.tab_for_id(tab_id)):
|
||||
if needs_toplevel_on_wayland:
|
||||
for tm in self.all_tab_managers:
|
||||
if tm.tab_being_dropped:
|
||||
tm.on_tab_drop(0, 0, bypass_move=True)
|
||||
return
|
||||
set_tab_being_dragged()
|
||||
for tm in self.all_tab_managers:
|
||||
tm.on_tab_drop_move()
|
||||
if was_dropped and (tab := self.tab_for_id(tab_id)): # detach tab into new OS Window
|
||||
if was_dropped: # detach tab into new OS Window
|
||||
self._move_tab_to(tab)
|
||||
|
||||
@ac('win', '''
|
||||
|
|
|
|||
2
kitty/glfw-wrapper.h
generated
2
kitty/glfw-wrapper.h
generated
|
|
@ -2302,7 +2302,7 @@ typedef GLFWdragsourcefun (*glfwSetDragSourceCallback_func)(GLFWwindow*, GLFWdra
|
|||
GFW_EXTERN glfwSetDragSourceCallback_func glfwSetDragSourceCallback_impl;
|
||||
#define glfwSetDragSourceCallback glfwSetDragSourceCallback_impl
|
||||
|
||||
typedef int (*glfwStartDrag_func)(GLFWwindow*, const GLFWDragSourceItem*, size_t, const GLFWimage*, int);
|
||||
typedef int (*glfwStartDrag_func)(GLFWwindow*, const GLFWDragSourceItem*, size_t, const GLFWimage*, int, bool);
|
||||
GFW_EXTERN glfwStartDrag_func glfwStartDrag_impl;
|
||||
#define glfwStartDrag glfwStartDrag_impl
|
||||
|
||||
|
|
|
|||
20
kitty/glfw.c
20
kitty/glfw.c
|
|
@ -645,11 +645,13 @@ window_focus_callback(GLFWwindow *w, int focused) {
|
|||
#undef osw
|
||||
}
|
||||
|
||||
#define TAB_DRAG_MIME_NUMBER 400
|
||||
|
||||
static int
|
||||
is_droppable_mime(const char *mime) {
|
||||
static char tab_mime[64] = {0};
|
||||
if (!tab_mime[0]) snprintf(tab_mime, sizeof(tab_mime), "application/net.kovidgoyal.kitty-tab-%d", getpid());
|
||||
if (strcmp(mime, tab_mime) == 0) return 4;
|
||||
if (strcmp(mime, tab_mime) == 0) return TAB_DRAG_MIME_NUMBER;
|
||||
if (strcmp(mime, "text/uri-list") == 0) return 3;
|
||||
if (strcmp(mime, "text/plain;charset=utf-8") == 0) return 2;
|
||||
if (strcmp(mime, "text/plain") == 0) return 1;
|
||||
|
|
@ -794,10 +796,10 @@ free_drag_source(void) {
|
|||
static void
|
||||
drag_source_callback(GLFWwindow *window UNUSED, GLFWDragEvent *ev) {
|
||||
#define finish \
|
||||
call_boss(on_drag_source_finished, "OOsiO", \
|
||||
call_boss(on_drag_source_finished, "OOsiOO", \
|
||||
ds.was_dropped ? Py_True : Py_False, ds.was_canceled ? Py_True: Py_False, \
|
||||
ds.accepted_mime_type ? ds.accepted_mime_type : "", \
|
||||
ds.action, ds.drag_data ? ds.drag_data : Py_None); \
|
||||
ds.action, ds.drag_data ? ds.drag_data : Py_None, ds.needs_toplevel_on_wayland ? Py_True : Py_False); \
|
||||
free_drag_source();
|
||||
|
||||
switch (ev->type) {
|
||||
|
|
@ -816,7 +818,9 @@ drag_source_callback(GLFWwindow *window UNUSED, GLFWDragEvent *ev) {
|
|||
ds.action = ev->action; break;
|
||||
case GLFW_DRAG_DROPPED:
|
||||
ds.was_dropped = true;
|
||||
if (ev->action == GLFW_DRAG_OPERATION_NONE) { finish }
|
||||
if (ev->action == GLFW_DRAG_OPERATION_NONE) {
|
||||
finish
|
||||
}
|
||||
break;
|
||||
case GLFW_DRAG_CANCELLED:
|
||||
ds.was_canceled = true;
|
||||
|
|
@ -2812,7 +2816,7 @@ change_drag_thumbnail(PyObject *self UNUSED, PyObject *args) {
|
|||
if (!get_thumbnail(global_state.drag_source.thumbnails, &thumbnail, idx)) return NULL;
|
||||
global_state.drag_source.thumbnail_idx = idx;
|
||||
} else global_state.drag_source.thumbnail_idx = -1;
|
||||
errno = glfwStartDrag(w->handle, NULL, 0, thumbnail.pixels ? &thumbnail : NULL, -2);
|
||||
errno = glfwStartDrag(w->handle, NULL, 0, thumbnail.pixels ? &thumbnail : NULL, -2, false);
|
||||
if (errno != 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return NULL;
|
||||
|
|
@ -2836,19 +2840,23 @@ start_drag_with_data(PyObject *self UNUSED, PyObject *args, PyObject *kw) {
|
|||
RAII_ALLOC(GLFWDragSourceItem, items, calloc(PyDict_Size(data_map), sizeof(GLFWDragSourceItem)));
|
||||
if (!items) { PyErr_NoMemory(); return NULL; }
|
||||
PyObject *key, *value; Py_ssize_t pos = 0; size_t num = 0;
|
||||
bool needs_toplevel_on_wayland = false;
|
||||
while (PyDict_Next(data_map, &pos, &key, &value)) {
|
||||
if (!PyUnicode_Check(key)) { PyErr_SetString(PyExc_TypeError, "data_map must have string keys"); return NULL; }
|
||||
if (!PyBytes_Check(value)) { PyErr_SetString(PyExc_TypeError, "data_map must have bytes values"); return NULL; }
|
||||
GLFWDragSourceItem *item = items + num++;
|
||||
item->mime_type = PyUnicode_AsUTF8(key);
|
||||
item->optional_data = PyBytes_AS_STRING(value); item->data_size = PyBytes_GET_SIZE(value);
|
||||
if (global_state.is_wayland && is_droppable_mime(item->mime_type) == TAB_DRAG_MIME_NUMBER)
|
||||
needs_toplevel_on_wayland = true;
|
||||
}
|
||||
free_drag_source();
|
||||
global_state.drag_source.is_active = true;
|
||||
global_state.drag_source.needs_toplevel_on_wayland = needs_toplevel_on_wayland;
|
||||
global_state.drag_source.drag_data = Py_NewRef(data_map);
|
||||
if (thumbnails) global_state.drag_source.thumbnails = Py_NewRef(thumbnails);
|
||||
global_state.drag_source.thumbnail_idx = thumbnail.pixels ? 0 : -1;
|
||||
errno = glfwStartDrag(w->handle, items, num, thumbnail.pixels ? &thumbnail : NULL, operations);
|
||||
errno = glfwStartDrag(w->handle, items, num, thumbnail.pixels ? &thumbnail : NULL, operations, needs_toplevel_on_wayland);
|
||||
if (errno != 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return NULL;
|
||||
|
|
|
|||
|
|
@ -1698,17 +1698,23 @@
|
|||
color is chosen to match the background color of the neighboring tab.
|
||||
''')
|
||||
|
||||
opt('tab_bar_drag_threshold', '5', option_type='positive_int',
|
||||
long_text='''
|
||||
opt(
|
||||
'tab_bar_drag_threshold',
|
||||
'5',
|
||||
option_type='positive_int',
|
||||
long_text="""
|
||||
Control when dragging of tabs to re-order them happens.
|
||||
The value is the drag threshold in pixels, the distance the mouse must move
|
||||
before a drag begins. A value of zero disables tab dragging entirely.
|
||||
Dragging a tab to another kitty window moves it there, while dragging
|
||||
outside any kitty window detaches it into a new OS window. Note that on
|
||||
Wayland, :link:`because of poor design
|
||||
<https://gitlab.freedesktop.org/wayland/wayland/-/issues/140>` drag and cancel
|
||||
will still detach tabs.
|
||||
''')
|
||||
outside any kitty window detaches it into a new OS window.
|
||||
|
||||
Note that on Wayland, :link:`because of poor design
|
||||
<https://gitlab.freedesktop.org/wayland/wayland/-/issues/140>` cancelling
|
||||
a drag will detach the tab. This is worked around for compositors that support
|
||||
:link:`xdg-toplevel-drag <https://wayland.app/protocols/xdg-toplevel-drag-v1>`.
|
||||
""",
|
||||
)
|
||||
egr() # }}}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -384,7 +384,7 @@ typedef struct GlobalState {
|
|||
} drop_dest;
|
||||
|
||||
struct {
|
||||
bool is_active, was_dropped, was_canceled;
|
||||
bool is_active, was_dropped, was_canceled, needs_toplevel_on_wayland;
|
||||
char *accepted_mime_type;
|
||||
int action, thumbnail_idx;
|
||||
PyObject *drag_data, *thumbnails;
|
||||
|
|
|
|||
|
|
@ -1608,14 +1608,16 @@ def on_tab_drop_move(self, tab_id: int = 0, is_dest: bool = False, x: int = 0, y
|
|||
self.layout_tab_bar()
|
||||
|
||||
@update_tab_bar_visibility
|
||||
def on_tab_drop(self, x: int, y: int) -> None:
|
||||
def on_tab_drop(self, x: int, y: int, bypass_move: bool = False) -> None:
|
||||
if (td := self.tab_being_dropped) is None:
|
||||
return
|
||||
if (tab := get_boss().tab_for_id(td.data.tab_id)) is None:
|
||||
return
|
||||
self.on_tab_drop_move(td.data.tab_id, True, x, y)
|
||||
if not bypass_move:
|
||||
self.on_tab_drop_move(td.data.tab_id, True, x, y)
|
||||
if (td := self.tab_being_dropped) is None:
|
||||
return
|
||||
print(3333333333)
|
||||
self.tab_being_dropped = None
|
||||
atid = self.active_tab.id if self.active_tab else 0
|
||||
set_tab_being_dragged()
|
||||
|
|
|
|||
Loading…
Reference in a new issue