From 94d413345a6a72d630d538db635fc273a0f98748 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 10 Feb 2019 15:49:42 +0100 Subject: [PATCH 01/17] Prepare a selection acquiring interface No run-time behaviour is changed so far. Signed-off-by: Uli Schlachter --- CMakeLists.txt | 1 + luaa.c | 4 ++++ objects/selection_acquire.c | 29 +++++++++++++++++++++++++++++ objects/selection_acquire.h | 31 +++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 objects/selection_acquire.c create mode 100644 objects/selection_acquire.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fc933c01..eed7ab66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,7 @@ set(AWE_SRCS ${BUILD_DIR}/objects/drawin.c ${BUILD_DIR}/objects/key.c ${BUILD_DIR}/objects/screen.c + ${BUILD_DIR}/objects/selection_acquire.c ${BUILD_DIR}/objects/selection_watcher.c ${BUILD_DIR}/objects/tag.c ${BUILD_DIR}/objects/selection_getter.c diff --git a/luaa.c b/luaa.c index 2473a678..07be4606 100644 --- a/luaa.c +++ b/luaa.c @@ -50,6 +50,7 @@ #include "objects/drawin.h" #include "objects/selection_getter.h" #include "objects/screen.h" +#include "objects/selection_acquire.h" #include "objects/selection_watcher.h" #include "objects/tag.h" #include "property.h" @@ -1040,6 +1041,9 @@ luaA_init(xdgHandle* xdg, string_array_t *searchpath) /* Export keys */ key_class_setup(L); + /* Export selection acquire */ + selection_acquire_class_setup(L); + /* Export selection watcher */ selection_watcher_class_setup(L); diff --git a/objects/selection_acquire.c b/objects/selection_acquire.c new file mode 100644 index 00000000..c51ab658 --- /dev/null +++ b/objects/selection_acquire.c @@ -0,0 +1,29 @@ +/* + * selection_acquire.c - objects for selection ownership + * + * Copyright © 2019 Uli Schlachter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "objects/selection_acquire.h" + +void +selection_acquire_class_setup(lua_State *L) +{ +} + +// vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/objects/selection_acquire.h b/objects/selection_acquire.h new file mode 100644 index 00000000..aed23f6e --- /dev/null +++ b/objects/selection_acquire.h @@ -0,0 +1,31 @@ +/* + * selection_acquire.c - objects for selection ownership header + * + * Copyright © 2019 Uli Schlachter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef AWESOME_OBJECTS_SELECTION_ACQUIRE_H +#define AWESOME_OBJECTS_SELECTION_ACQUIRE_H + +#include + +void selection_acquire_class_setup(lua_State*); + +#endif + +// vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 From 967c9f7a0141f1dccf29a8377829f1e2a5165bf8 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 10 Feb 2019 16:04:36 +0100 Subject: [PATCH 02/17] Add selection_acquire objects It is now possible to construct such objects and they already own a selection. Signed-off-by: Uli Schlachter --- objects/selection_acquire.c | 99 +++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/objects/selection_acquire.c b/objects/selection_acquire.c index c51ab658..378928ac 100644 --- a/objects/selection_acquire.c +++ b/objects/selection_acquire.c @@ -20,10 +20,109 @@ */ #include "objects/selection_acquire.h" +#include "common/luaobject.h" +#include "globalconf.h" + +#define REGISTRY_ACQUIRE_TABLE_INDEX "awesome_selection_acquires" + +typedef struct selection_acquire_t +{ + LUA_OBJECT_HEADER + /** Window used for owning the selection. */ + xcb_window_t window; + /** Timestamp used for acquiring the selection. */ + xcb_timestamp_t timestamp; + /** Reference in the special table to this object. */ + int ref; +} selection_acquire_t; + +static lua_class_t selection_acquire_class; +LUA_OBJECT_FUNCS(selection_acquire_class, selection_acquire_t, selection_acquire) + +static void +selection_acquire_wipe(selection_acquire_t *selection) +{ + if (selection->window != XCB_NONE) + xcb_destroy_window(globalconf.connection, selection->window); +} + +static int +luaA_selection_acquire_new(lua_State *L) +{ + size_t name_length; + const char *name; + xcb_intern_atom_reply_t *reply; + xcb_get_selection_owner_reply_t *selection_reply; + xcb_atom_t name_atom; + selection_acquire_t *selection; + + name = luaL_checklstring(L, 2, &name_length); + + /* Create a selection object */ + selection = (void *) selection_acquire_class.allocator(L); + selection->window = xcb_generate_id(globalconf.connection); + xcb_create_window(globalconf.connection, globalconf.screen->root_depth, + selection->window, globalconf.screen->root, -1, -1, 1, 1, 0, + XCB_COPY_FROM_PARENT, globalconf.screen->root_visual, 0, NULL); + + /* Get the atom identifying the selection */ + reply = xcb_intern_atom_reply(globalconf.connection, + xcb_intern_atom_unchecked(globalconf.connection, false, name_length, name), + NULL); + name_atom = reply ? reply->atom : XCB_NONE; + p_delete(&reply); + + /* Try to acquire the selection */ + selection->timestamp = globalconf.timestamp; + xcb_set_selection_owner(globalconf.connection, selection->window, name_atom, selection->timestamp); + selection_reply = xcb_get_selection_owner_reply(globalconf.connection, + xcb_get_selection_owner(globalconf.connection, name_atom), + NULL); + if (selection_reply == NULL || selection_reply->owner != selection->window) { + /* Acquiring the selection failed, return nothing */ + p_delete(&selection_reply); + + xcb_destroy_window(globalconf.connection, selection->window); + selection->window = XCB_NONE; + return 0; + } + + /* Everything worked, register the object in table */ + lua_pushliteral(L, REGISTRY_ACQUIRE_TABLE_INDEX); + lua_rawget(L, LUA_REGISTRYINDEX); + lua_pushvalue(L, -2); + selection->ref = luaL_ref(L, -2); + lua_pop(L, 1); + + return 1; +} void selection_acquire_class_setup(lua_State *L) { + static const struct luaL_Reg selection_acquire_methods[] = + { + { "__call", luaA_selection_acquire_new }, + { NULL, NULL } + }; + + static const struct luaL_Reg selection_acquire_meta[] = + { + LUA_OBJECT_META(selection_acquire) + LUA_CLASS_META + { NULL, NULL } + }; + + /* Store a table in the registry that tracks active selection_acquire_t. */ + lua_pushliteral(L, REGISTRY_ACQUIRE_TABLE_INDEX); + lua_newtable(L); + lua_rawset(L, LUA_REGISTRYINDEX); + + luaA_class_setup(L, &selection_acquire_class, "selection_acquire", NULL, + (lua_class_allocator_t) selection_acquire_new, + (lua_class_collector_t) selection_acquire_wipe, NULL, + luaA_class_index_miss_property, luaA_class_newindex_miss_property, + selection_acquire_methods, selection_acquire_meta); } // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 From e4199dd6b50c8a7aaed53c97bfdbfb391b401f95 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 10 Feb 2019 16:12:02 +0100 Subject: [PATCH 03/17] Add a :release() method to selection acquire objects This allows to voluntarily give up ownership of a selection. Because selection acquire objects cannot be garbage-collected before they are released, this also gets rid of the function destroying the window when its selection acquire object is destroyed. Instead, the window is immediately destroyed when no longer needed. Signed-off-by: Uli Schlachter --- objects/selection_acquire.c | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/objects/selection_acquire.c b/objects/selection_acquire.c index 378928ac..2bc1483d 100644 --- a/objects/selection_acquire.c +++ b/objects/selection_acquire.c @@ -39,13 +39,6 @@ typedef struct selection_acquire_t static lua_class_t selection_acquire_class; LUA_OBJECT_FUNCS(selection_acquire_class, selection_acquire_t, selection_acquire) -static void -selection_acquire_wipe(selection_acquire_t *selection) -{ - if (selection->window != XCB_NONE) - xcb_destroy_window(globalconf.connection, selection->window); -} - static int luaA_selection_acquire_new(lua_State *L) { @@ -97,6 +90,31 @@ luaA_selection_acquire_new(lua_State *L) return 1; } +static int +luaA_selection_acquire_release(lua_State *L) +{ + selection_acquire_t *selection = luaA_checkudata(L, 1, &selection_acquire_class); + + xcb_destroy_window(globalconf.connection, selection->window); + selection->window = XCB_NONE; + + /* Unreference the object, it's now dead */ + lua_pushliteral(L, REGISTRY_ACQUIRE_TABLE_INDEX); + lua_rawget(L, LUA_REGISTRYINDEX); + luaL_unref(L, -1, selection->ref); + lua_pop(L, 1); + + selection->ref = LUA_NOREF; + + return 0; +} + +static bool +selection_acquire_checker(selection_acquire_t *selection) +{ + return selection->window != XCB_NONE; +} + void selection_acquire_class_setup(lua_State *L) { @@ -110,6 +128,7 @@ selection_acquire_class_setup(lua_State *L) { LUA_OBJECT_META(selection_acquire) LUA_CLASS_META + { "release", luaA_selection_acquire_release }, { NULL, NULL } }; @@ -119,8 +138,8 @@ selection_acquire_class_setup(lua_State *L) lua_rawset(L, LUA_REGISTRYINDEX); luaA_class_setup(L, &selection_acquire_class, "selection_acquire", NULL, - (lua_class_allocator_t) selection_acquire_new, - (lua_class_collector_t) selection_acquire_wipe, NULL, + (lua_class_allocator_t) selection_acquire_new, NULL, + (lua_class_checker_t) selection_acquire_checker, luaA_class_index_miss_property, luaA_class_newindex_miss_property, selection_acquire_methods, selection_acquire_meta); } From 869b1b0dfffc955e4c6e7682091e08c59fa6ef4a Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 10 Feb 2019 17:03:00 +0100 Subject: [PATCH 04/17] selection_acquire: Also emit release when X11 selection is lost Signed-off-by: Uli Schlachter --- event.c | 4 +- objects/selection_acquire.c | 125 ++++++++++++++++++++++++++++-------- objects/selection_acquire.h | 2 + 3 files changed, 103 insertions(+), 28 deletions(-) diff --git a/event.c b/event.c index 3325e668..2c5ef746 100644 --- a/event.c +++ b/event.c @@ -25,6 +25,7 @@ #include "objects/tag.h" #include "objects/selection_getter.h" #include "objects/drawin.h" +#include "objects/selection_acquire.h" #include "objects/selection_watcher.h" #include "xwindow.h" #include "ewmh.h" @@ -1013,7 +1014,8 @@ event_handle_selectionclear(xcb_selection_clear_event_t *ev) { warn("Lost WM_Sn selection, exiting..."); g_main_loop_quit(globalconf.loop); - } + } else + selection_handle_selectionclear(ev); } /** \brief awesome xerror function. diff --git a/objects/selection_acquire.c b/objects/selection_acquire.c index 2bc1483d..3d619cc5 100644 --- a/objects/selection_acquire.c +++ b/objects/selection_acquire.c @@ -28,17 +28,85 @@ typedef struct selection_acquire_t { LUA_OBJECT_HEADER + /** The selection that is being owned. */ + xcb_atom_t selection; /** Window used for owning the selection. */ xcb_window_t window; /** Timestamp used for acquiring the selection. */ xcb_timestamp_t timestamp; - /** Reference in the special table to this object. */ - int ref; } selection_acquire_t; static lua_class_t selection_acquire_class; LUA_OBJECT_FUNCS(selection_acquire_class, selection_acquire_t, selection_acquire) +static void +luaA_pushatom(lua_State *L, xcb_atom_t atom) +{ + lua_pushnumber(L, atom); +} + +static int +selection_acquire_find_by_window(lua_State *L, xcb_window_t window) +{ + /* Iterate over all active selection acquire objects */ + lua_pushliteral(L, REGISTRY_ACQUIRE_TABLE_INDEX); + lua_rawget(L, LUA_REGISTRYINDEX); + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + if (lua_type(L, -1) == LUA_TUSERDATA) { + selection_acquire_t *selection = lua_touserdata(L, -1); + if (selection->window == window) + { + /* Remove table and key */ + lua_remove(L, -2); + lua_remove(L, -2); + return 1; + } + } + /* Remove the value, leaving only the key */ + lua_pop(L, 1); + } + /* Remove the table */ + lua_pop(L, 1); + + return 0; +} + +static void +selection_release(lua_State *L, int ud) +{ + selection_acquire_t *selection = luaA_checkudata(L, ud, &selection_acquire_class); + + luaA_object_emit_signal(L, ud, "release", 0); + + /* Destroy the window, this also releases the selection in X11 */ + xcb_destroy_window(globalconf.connection, selection->window); + selection->window = XCB_NONE; + + /* Unreference the object, it's now dead */ + lua_pushliteral(L, REGISTRY_ACQUIRE_TABLE_INDEX); + lua_rawget(L, LUA_REGISTRYINDEX); + luaA_pushatom(L, selection->selection); + lua_pushnil(L); + lua_rawset(L, -3); + + selection->selection = XCB_NONE; + + lua_pop(L, 1); +} + +void +selection_handle_selectionclear(xcb_selection_clear_event_t *ev) +{ + lua_State *L = globalconf_get_lua_State(); + + if (selection_acquire_find_by_window(L, ev->owner) == 0) + return; + + selection_release(L, -1); + lua_pop(L, 1); +} + static int luaA_selection_acquire_new(lua_State *L) { @@ -51,13 +119,6 @@ luaA_selection_acquire_new(lua_State *L) name = luaL_checklstring(L, 2, &name_length); - /* Create a selection object */ - selection = (void *) selection_acquire_class.allocator(L); - selection->window = xcb_generate_id(globalconf.connection); - xcb_create_window(globalconf.connection, globalconf.screen->root_depth, - selection->window, globalconf.screen->root, -1, -1, 1, 1, 0, - XCB_COPY_FROM_PARENT, globalconf.screen->root_visual, 0, NULL); - /* Get the atom identifying the selection */ reply = xcb_intern_atom_reply(globalconf.connection, xcb_intern_atom_unchecked(globalconf.connection, false, name_length, name), @@ -65,8 +126,16 @@ luaA_selection_acquire_new(lua_State *L) name_atom = reply ? reply->atom : XCB_NONE; p_delete(&reply); - /* Try to acquire the selection */ + /* Create a selection object */ + selection = (void *) selection_acquire_class.allocator(L); + selection->selection = name_atom; selection->timestamp = globalconf.timestamp; + selection->window = xcb_generate_id(globalconf.connection); + xcb_create_window(globalconf.connection, globalconf.screen->root_depth, + selection->window, globalconf.screen->root, -1, -1, 1, 1, 0, + XCB_COPY_FROM_PARENT, globalconf.screen->root_visual, 0, NULL); + + /* Try to acquire the selection */ xcb_set_selection_owner(globalconf.connection, selection->window, name_atom, selection->timestamp); selection_reply = xcb_get_selection_owner_reply(globalconf.connection, xcb_get_selection_owner(globalconf.connection, name_atom), @@ -80,12 +149,24 @@ luaA_selection_acquire_new(lua_State *L) return 0; } - /* Everything worked, register the object in table */ + /* Everything worked, register the object in the table */ lua_pushliteral(L, REGISTRY_ACQUIRE_TABLE_INDEX); lua_rawget(L, LUA_REGISTRYINDEX); - lua_pushvalue(L, -2); - selection->ref = luaL_ref(L, -2); - lua_pop(L, 1); + + luaA_pushatom(L, name_atom); + lua_rawget(L, -2); + if (!lua_isnil(L, -1)) { + /* There is already another selection_acquire object for this selection, + * release it now. X11 does not send us SelectionClear events for our + * own changes to the selection. + */ + selection_release(L, -1); + } + + luaA_pushatom(L, name_atom); + lua_pushvalue(L, -4); + lua_rawset(L, -4); + lua_pop(L, 2); return 1; } @@ -93,18 +174,8 @@ luaA_selection_acquire_new(lua_State *L) static int luaA_selection_acquire_release(lua_State *L) { - selection_acquire_t *selection = luaA_checkudata(L, 1, &selection_acquire_class); - - xcb_destroy_window(globalconf.connection, selection->window); - selection->window = XCB_NONE; - - /* Unreference the object, it's now dead */ - lua_pushliteral(L, REGISTRY_ACQUIRE_TABLE_INDEX); - lua_rawget(L, LUA_REGISTRYINDEX); - luaL_unref(L, -1, selection->ref); - lua_pop(L, 1); - - selection->ref = LUA_NOREF; + luaA_checkudata(L, 1, &selection_acquire_class); + selection_release(L, 1); return 0; } @@ -112,7 +183,7 @@ luaA_selection_acquire_release(lua_State *L) static bool selection_acquire_checker(selection_acquire_t *selection) { - return selection->window != XCB_NONE; + return selection->selection != XCB_NONE; } void diff --git a/objects/selection_acquire.h b/objects/selection_acquire.h index aed23f6e..1f13bd3b 100644 --- a/objects/selection_acquire.h +++ b/objects/selection_acquire.h @@ -23,8 +23,10 @@ #define AWESOME_OBJECTS_SELECTION_ACQUIRE_H #include +#include void selection_acquire_class_setup(lua_State*); +void selection_handle_selectionclear(xcb_selection_clear_event_t*); #endif From 0f948d0dd946024bcb390c2b201ab84a11f5c4c2 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 10 Feb 2019 17:42:34 +0100 Subject: [PATCH 05/17] Begin implementing selection transfers This commit adds an implementation that always answers "Test\n" when asked for the selection contents. Signed-off-by: Uli Schlachter --- event.c | 1 + objects/selection_acquire.c | 56 +++++++++++++++++++++++++++++++++++++ objects/selection_acquire.h | 1 + 3 files changed, 58 insertions(+) diff --git a/event.c b/event.c index 2c5ef746..5e4ef912 100644 --- a/event.c +++ b/event.c @@ -1111,6 +1111,7 @@ void event_handle(xcb_generic_event_t *event) EVENT(XCB_UNMAP_NOTIFY, event_handle_unmapnotify); EVENT(XCB_SELECTION_CLEAR, event_handle_selectionclear); EVENT(XCB_SELECTION_NOTIFY, event_handle_selectionnotify); + EVENT(XCB_SELECTION_REQUEST, selection_handle_selectionrequest); #undef EVENT } diff --git a/objects/selection_acquire.c b/objects/selection_acquire.c index 3d619cc5..4bd18248 100644 --- a/objects/selection_acquire.c +++ b/objects/selection_acquire.c @@ -107,6 +107,62 @@ selection_handle_selectionclear(xcb_selection_clear_event_t *ev) lua_pop(L, 1); } +static void +selection_transfer_notify(xcb_window_t requestor, xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t time) +{ + xcb_selection_notify_event_t ev; + + p_clear(&ev, 1); + ev.response_type = XCB_SELECTION_NOTIFY; + ev.requestor = requestor; + ev.selection = selection; + ev.target = target; + ev.property = property; + ev.time = time; + + xcb_send_event(globalconf.connection, false, requestor, XCB_EVENT_MASK_NO_EVENT, (char *) &ev); +} + +static void +selection_transfer_reject(xcb_window_t requestor, xcb_atom_t selection, + xcb_atom_t target, xcb_timestamp_t time) +{ + selection_transfer_notify(requestor, selection, target, XCB_NONE, time); +} + +#include "common/atoms.h" /* TODO remove */ +static void +selection_transfer_begin(lua_State *L, int ud, xcb_window_t requestor, + xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, + xcb_timestamp_t time) +{ + /* TODO implement this */ + xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, + requestor, property, UTF8_STRING, 8, 5, "Test\n"); + selection_transfer_notify(requestor, selection, target, property, time); +} + +void +selection_handle_selectionrequest(xcb_selection_request_event_t *ev) +{ + lua_State *L = globalconf_get_lua_State(); + + if (ev->property == XCB_NONE) + /* Obsolete client */ + ev->property = ev->target; + + if (selection_acquire_find_by_window(L, ev->owner) == 0) + { + selection_transfer_reject(ev->requestor, ev->selection, ev->target, ev->time); + return; + } + + selection_transfer_begin(L, -1, ev->requestor, ev->selection, ev->target, + ev->property, ev->time); + + lua_pop(L, 1); +} + static int luaA_selection_acquire_new(lua_State *L) { diff --git a/objects/selection_acquire.h b/objects/selection_acquire.h index 1f13bd3b..8036a9f4 100644 --- a/objects/selection_acquire.h +++ b/objects/selection_acquire.h @@ -27,6 +27,7 @@ void selection_acquire_class_setup(lua_State*); void selection_handle_selectionclear(xcb_selection_clear_event_t*); +void selection_handle_selectionrequest(xcb_selection_request_event_t*); #endif From b979fb724aea1547f477d5963ed8b2aeb4944633 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 10 Feb 2019 17:51:53 +0100 Subject: [PATCH 06/17] Start a selection transfer object Signed-off-by: Uli Schlachter --- CMakeLists.txt | 1 + luaa.c | 4 ++ objects/selection_acquire.c | 36 +----------- objects/selection_transfer.c | 106 +++++++++++++++++++++++++++++++++++ objects/selection_transfer.h | 35 ++++++++++++ 5 files changed, 147 insertions(+), 35 deletions(-) create mode 100644 objects/selection_transfer.c create mode 100644 objects/selection_transfer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index eed7ab66..26d5ebad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,7 @@ set(AWE_SRCS ${BUILD_DIR}/objects/key.c ${BUILD_DIR}/objects/screen.c ${BUILD_DIR}/objects/selection_acquire.c + ${BUILD_DIR}/objects/selection_transfer.c ${BUILD_DIR}/objects/selection_watcher.c ${BUILD_DIR}/objects/tag.c ${BUILD_DIR}/objects/selection_getter.c diff --git a/luaa.c b/luaa.c index 07be4606..902fa2e6 100644 --- a/luaa.c +++ b/luaa.c @@ -51,6 +51,7 @@ #include "objects/selection_getter.h" #include "objects/screen.h" #include "objects/selection_acquire.h" +#include "objects/selection_transfer.h" #include "objects/selection_watcher.h" #include "objects/tag.h" #include "property.h" @@ -1044,6 +1045,9 @@ luaA_init(xdgHandle* xdg, string_array_t *searchpath) /* Export selection acquire */ selection_acquire_class_setup(L); + /* Export selection transfer */ + selection_transfer_class_setup(L); + /* Export selection watcher */ selection_watcher_class_setup(L); diff --git a/objects/selection_acquire.c b/objects/selection_acquire.c index 4bd18248..11395813 100644 --- a/objects/selection_acquire.c +++ b/objects/selection_acquire.c @@ -20,6 +20,7 @@ */ #include "objects/selection_acquire.h" +#include "objects/selection_transfer.h" #include "common/luaobject.h" #include "globalconf.h" @@ -107,41 +108,6 @@ selection_handle_selectionclear(xcb_selection_clear_event_t *ev) lua_pop(L, 1); } -static void -selection_transfer_notify(xcb_window_t requestor, xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t time) -{ - xcb_selection_notify_event_t ev; - - p_clear(&ev, 1); - ev.response_type = XCB_SELECTION_NOTIFY; - ev.requestor = requestor; - ev.selection = selection; - ev.target = target; - ev.property = property; - ev.time = time; - - xcb_send_event(globalconf.connection, false, requestor, XCB_EVENT_MASK_NO_EVENT, (char *) &ev); -} - -static void -selection_transfer_reject(xcb_window_t requestor, xcb_atom_t selection, - xcb_atom_t target, xcb_timestamp_t time) -{ - selection_transfer_notify(requestor, selection, target, XCB_NONE, time); -} - -#include "common/atoms.h" /* TODO remove */ -static void -selection_transfer_begin(lua_State *L, int ud, xcb_window_t requestor, - xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, - xcb_timestamp_t time) -{ - /* TODO implement this */ - xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, - requestor, property, UTF8_STRING, 8, 5, "Test\n"); - selection_transfer_notify(requestor, selection, target, property, time); -} - void selection_handle_selectionrequest(xcb_selection_request_event_t *ev) { diff --git a/objects/selection_transfer.c b/objects/selection_transfer.c new file mode 100644 index 00000000..040aea61 --- /dev/null +++ b/objects/selection_transfer.c @@ -0,0 +1,106 @@ +/* + * selection_transfer.c - objects for selection transfer header + * + * Copyright © 2019 Uli Schlachter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "objects/selection_transfer.h" +#include "common/luaobject.h" +#include "globalconf.h" + +#define REGISTRY_TRANSFER_TABLE_INDEX "awesome_selection_transfers" + +typedef struct selection_transfer_t +{ + LUA_OBJECT_HEADER +} selection_transfer_t; + +static lua_class_t selection_transfer_class; +LUA_OBJECT_FUNCS(selection_transfer_class, selection_transfer_t, selection_transfer) + +static void +selection_transfer_notify(xcb_window_t requestor, xcb_atom_t selection, + xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t time) +{ + xcb_selection_notify_event_t ev; + + p_clear(&ev, 1); + ev.response_type = XCB_SELECTION_NOTIFY; + ev.requestor = requestor; + ev.selection = selection; + ev.target = target; + ev.property = property; + ev.time = time; + + xcb_send_event(globalconf.connection, false, requestor, + XCB_EVENT_MASK_NO_EVENT, (char *) &ev); +} + +void +selection_transfer_reject(xcb_window_t requestor, xcb_atom_t selection, + xcb_atom_t target, xcb_timestamp_t time) +{ + selection_transfer_notify(requestor, selection, target, XCB_NONE, time); +} + +#include "common/atoms.h" /* TODO remove */ +void +selection_transfer_begin(lua_State *L, int ud, xcb_window_t requestor, + xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, + xcb_timestamp_t time) +{ + /* TODO implement this */ + xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, + requestor, property, UTF8_STRING, 8, 5, "Test\n"); + selection_transfer_notify(requestor, selection, target, property, time); +} + +static bool +selection_transfer_checker(selection_transfer_t *selection) +{ + return true; +} + +void +selection_transfer_class_setup(lua_State *L) +{ + static const struct luaL_Reg selection_transfer_methods[] = + { + { NULL, NULL } + }; + + static const struct luaL_Reg selection_transfer_meta[] = + { + LUA_OBJECT_META(selection_transfer) + LUA_CLASS_META + { NULL, NULL } + }; + + /* Store a table in the registry that tracks active selection_transfer_t. */ + lua_pushliteral(L, REGISTRY_TRANSFER_TABLE_INDEX); + lua_newtable(L); + lua_rawset(L, LUA_REGISTRYINDEX); + + luaA_class_setup(L, &selection_transfer_class, "selection_transfer", NULL, + (lua_class_allocator_t) selection_transfer_new, NULL, + (lua_class_checker_t) selection_transfer_checker, + luaA_class_index_miss_property, luaA_class_newindex_miss_property, + selection_transfer_methods, selection_transfer_meta); +} + +// vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/objects/selection_transfer.h b/objects/selection_transfer.h new file mode 100644 index 00000000..31ada219 --- /dev/null +++ b/objects/selection_transfer.h @@ -0,0 +1,35 @@ +/* + * selection_transfer.c - objects for selection transfer header + * + * Copyright © 2019 Uli Schlachter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef AWESOME_OBJECTS_SELECTION_TRANSFER_H +#define AWESOME_OBJECTS_SELECTION_TRANSFER_H + +#include +#include + +void selection_transfer_class_setup(lua_State*); +void selection_transfer_reject(xcb_window_t, xcb_atom_t, xcb_atom_t, xcb_timestamp_t); +void selection_transfer_begin(lua_State*, int, xcb_window_t, xcb_atom_t, + xcb_atom_t, xcb_atom_t, xcb_timestamp_t); + +#endif + +// vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 From 53db43c2b2a19a26b11d3b83a9090beab9236de5 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 10 Feb 2019 18:22:31 +0100 Subject: [PATCH 07/17] Emit a "request" signal on selection acquire objects This now creates a selection transfer object and requests Lua to reply to the request via this object. However, so far no answer is possible. Signed-off-by: Uli Schlachter --- objects/selection_transfer.c | 77 ++++++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/objects/selection_transfer.c b/objects/selection_transfer.c index 040aea61..a5574c7a 100644 --- a/objects/selection_transfer.c +++ b/objects/selection_transfer.c @@ -21,13 +21,29 @@ #include "objects/selection_transfer.h" #include "common/luaobject.h" +#include "common/atoms.h" #include "globalconf.h" #define REGISTRY_TRANSFER_TABLE_INDEX "awesome_selection_transfers" +enum transfer_state { + TRANSFER_WAIT_FOR_DATA, + TRANSFER_DONE +}; + typedef struct selection_transfer_t { LUA_OBJECT_HEADER + /** Reference in the special table to this object */ + int ref; + /* Information from the xcb_selection_request_event_t */ + xcb_window_t requestor; + xcb_atom_t selection; + xcb_atom_t target; + xcb_atom_t property; + xcb_timestamp_t time; + /* Current state of the transfer */ + enum transfer_state state; } selection_transfer_t; static lua_class_t selection_transfer_class; @@ -58,22 +74,69 @@ selection_transfer_reject(xcb_window_t requestor, xcb_atom_t selection, selection_transfer_notify(requestor, selection, target, XCB_NONE, time); } -#include "common/atoms.h" /* TODO remove */ +static void +transfer_done(lua_State *L, selection_transfer_t *transfer) +{ + transfer->state = TRANSFER_DONE; + + lua_pushliteral(L, REGISTRY_TRANSFER_TABLE_INDEX); + lua_rawget(L, LUA_REGISTRYINDEX); + luaL_unref(L, -1, transfer->ref); + transfer->ref = LUA_NOREF; + lua_pop(L, 1); +} + void selection_transfer_begin(lua_State *L, int ud, xcb_window_t requestor, xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t time) { - /* TODO implement this */ - xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, - requestor, property, UTF8_STRING, 8, 5, "Test\n"); - selection_transfer_notify(requestor, selection, target, property, time); + ud = luaA_absindex(L, ud); + + /* Allocate a transfer object */ + selection_transfer_t *transfer = (void *) selection_transfer_class.allocator(L); + transfer->requestor = requestor; + transfer->selection = selection; + transfer->target = target; + transfer->property = property; + transfer->time = time; + transfer->state = TRANSFER_WAIT_FOR_DATA; + + /* Save the object in the registry */ + lua_pushliteral(L, REGISTRY_TRANSFER_TABLE_INDEX); + lua_rawget(L, LUA_REGISTRYINDEX); + lua_pushvalue(L, -2); + transfer->ref = luaL_ref(L, -2); + lua_pop(L, 1); + + /* Get the atom name */ + xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply(globalconf.connection, + xcb_get_atom_name_unchecked(globalconf.connection, target), NULL); + if (reply) { + lua_pushlstring(L, xcb_get_atom_name_name(reply), + xcb_get_atom_name_name_length(reply)); + p_delete(&reply); + } else + lua_pushnil(L); + + /* Emit the request signal with target and transfer object */ + lua_pushvalue(L, -2); + luaA_object_emit_signal(L, ud, "request", 2); + + /* Reject the transfer if Lua did not do anything */ + if (transfer->state == TRANSFER_WAIT_FOR_DATA) { + selection_transfer_reject(requestor, selection, target, time); + transfer_done(L, transfer); + } + + /* Remove the transfer object from the stack */ + lua_pop(L, 1); } static bool -selection_transfer_checker(selection_transfer_t *selection) +selection_transfer_checker(selection_transfer_t *transfer) { - return true; + return transfer->state != TRANSFER_DONE; } void From a4bac0683ee19b1dbae562574396c8deab9f0cfd Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 10 Feb 2019 18:27:34 +0100 Subject: [PATCH 08/17] Add a :send method to selection transfers This makes the following work on a selection acquire object: o:connect_signal("request", function(_, _, t) t:send{ data = "Hello World!\n" } end) Signed-off-by: Uli Schlachter --- objects/selection_transfer.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/objects/selection_transfer.c b/objects/selection_transfer.c index a5574c7a..95f946b0 100644 --- a/objects/selection_transfer.c +++ b/objects/selection_transfer.c @@ -133,6 +133,33 @@ selection_transfer_begin(lua_State *L, int ud, xcb_window_t requestor, lua_pop(L, 1); } +static int +luaA_selection_transfer_send(lua_State *L) +{ + size_t data_length; + const char *data; + uint8_t format = 8; + xcb_atom_t type = UTF8_STRING; + + selection_transfer_t *transfer = luaA_checkudata(L, 1, &selection_transfer_class); + if (transfer->state != TRANSFER_WAIT_FOR_DATA) + luaL_error(L, "Transfer object is not ready for more data to be sent"); + + luaA_checktable(L, 2); + lua_pushliteral(L, "data"); + lua_rawget(L, 2); + + data = luaL_checklstring(L, -1, &data_length); + + xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, + transfer->requestor, transfer->property, type, format, data_length, data); + selection_transfer_notify(transfer->requestor, transfer->selection, + transfer->target, transfer->property, transfer->time); + transfer_done(L, transfer); + + return 0; +} + static bool selection_transfer_checker(selection_transfer_t *transfer) { @@ -151,6 +178,7 @@ selection_transfer_class_setup(lua_State *L) { LUA_OBJECT_META(selection_transfer) LUA_CLASS_META + { "send", luaA_selection_transfer_send }, { NULL, NULL } }; From fe62ad5faad2ee1d593b23afb239d57c968c9239 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 10 Feb 2019 18:45:46 +0100 Subject: [PATCH 09/17] selection transfer: Support atom format This commit makes :send{ data={"TARGETS", "UTF8_STRING"}, format="atom"} work correctly. This was tested with xclip -o -target TARGETS. With this commit, it becomes possible to implement the TARGETS target. Signed-off-by: Uli Schlachter --- objects/selection_transfer.c | 49 ++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/objects/selection_transfer.c b/objects/selection_transfer.c index 95f946b0..12b2eae2 100644 --- a/objects/selection_transfer.c +++ b/objects/selection_transfer.c @@ -138,21 +138,60 @@ luaA_selection_transfer_send(lua_State *L) { size_t data_length; const char *data; - uint8_t format = 8; - xcb_atom_t type = UTF8_STRING; selection_transfer_t *transfer = luaA_checkudata(L, 1, &selection_transfer_class); if (transfer->state != TRANSFER_WAIT_FOR_DATA) luaL_error(L, "Transfer object is not ready for more data to be sent"); + /* Get format and data from the table */ luaA_checktable(L, 2); + lua_pushliteral(L, "format"); + lua_rawget(L, 2); lua_pushliteral(L, "data"); lua_rawget(L, 2); - data = luaL_checklstring(L, -1, &data_length); + if (lua_isstring(L, -2)) { + const char *format_string = luaL_checkstring(L, -2); + if (A_STRNEQ(format_string, "atom")) + luaL_error(L, "Unknown format '%s'", format_string); + + /* 'data' is a table with strings */ + size_t len = luaA_rawlen(L, -1); + + /* Get an array with atoms */ + size_t atom_lengths[len]; + const char *atom_strings[len]; + for (size_t i = 0; i < len; i++) { + lua_rawgeti(L, -1, i+1); + atom_strings[i] = luaL_checklstring(L, -1, &atom_lengths[i]); + lua_pop(L, 1); + } + + xcb_intern_atom_cookie_t cookies[len]; + xcb_atom_t atoms[len]; + for (size_t i = 0; i < len; i++) { + cookies[i] = xcb_intern_atom_unchecked(globalconf.connection, false, + atom_lengths[i], atom_strings[i]); + } + for (size_t i = 0; i < len; i++) { + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(globalconf.connection, + cookies[i], NULL); + atoms[i] = reply ? reply->atom : XCB_NONE; + p_delete(&reply); + } + + xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, + transfer->requestor, transfer->property, XCB_ATOM_ATOM, 32, + len, &atoms[0]); + } else { + /* 'data' is a string with the data to transfer */ + data = luaL_checklstring(L, -1, &data_length); + + xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, + transfer->requestor, transfer->property, UTF8_STRING, 8, + data_length, data); + } - xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, - transfer->requestor, transfer->property, type, format, data_length, data); selection_transfer_notify(transfer->requestor, transfer->selection, transfer->target, transfer->property, transfer->time); transfer_done(L, transfer); From 434f97e4b15b8b9fdfa222796d69ab4f23b56d0e Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Mon, 11 Feb 2019 06:26:34 +0100 Subject: [PATCH 10/17] Selection transfer: Support INCR transfers Large amounts of data have to be send in chunks via the INCR protocol. This commit adds support for that protocol to selection transfers. Signed-off-by: Uli Schlachter --- objects/selection_transfer.c | 125 +++++++++++++++++++++++++++++++++-- objects/selection_transfer.h | 1 + property.c | 2 + 3 files changed, 124 insertions(+), 4 deletions(-) diff --git a/objects/selection_transfer.c b/objects/selection_transfer.c index 12b2eae2..092745d9 100644 --- a/objects/selection_transfer.c +++ b/objects/selection_transfer.c @@ -25,9 +25,11 @@ #include "globalconf.h" #define REGISTRY_TRANSFER_TABLE_INDEX "awesome_selection_transfers" +#define TRANSFER_DATA_INDEX "data_for_next_chunk" enum transfer_state { TRANSFER_WAIT_FOR_DATA, + TRANSFER_INCREMENTAL, TRANSFER_DONE }; @@ -44,11 +46,20 @@ typedef struct selection_transfer_t xcb_timestamp_t time; /* Current state of the transfer */ enum transfer_state state; + /* Offset into TRANSFER_DATA_INDEX for the next chunk of data */ + size_t offset; } selection_transfer_t; static lua_class_t selection_transfer_class; LUA_OBJECT_FUNCS(selection_transfer_class, selection_transfer_t, selection_transfer) +static size_t max_property_length(void) +{ + uint32_t max_request_length = xcb_get_maximum_request_length(globalconf.connection); + max_request_length = MIN(max_request_length, (1<<16) - 1); + return max_request_length * 4 - sizeof(xcb_change_property_request_t); +} + static void selection_transfer_notify(xcb_window_t requestor, xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t time) @@ -138,6 +149,8 @@ luaA_selection_transfer_send(lua_State *L) { size_t data_length; const char *data; + bool incr = false; + size_t incr_size = 0; selection_transfer_t *transfer = luaA_checkudata(L, 1, &selection_transfer_class); if (transfer->state != TRANSFER_WAIT_FOR_DATA) @@ -150,10 +163,19 @@ luaA_selection_transfer_send(lua_State *L) lua_pushliteral(L, "data"); lua_rawget(L, 2); + lua_pushliteral(L, "continue"); + lua_rawget(L, 2); + incr = lua_toboolean(L, -1); + if (incr && lua_isnumber(L, -1)) + incr_size = lua_tonumber(L, -1); + lua_pop(L, 1); + if (lua_isstring(L, -2)) { const char *format_string = luaL_checkstring(L, -2); if (A_STRNEQ(format_string, "atom")) luaL_error(L, "Unknown format '%s'", format_string); + if (incr) + luaL_error(L, "Cannot transfer atoms in pieces"); /* 'data' is a table with strings */ size_t len = luaA_rawlen(L, -1); @@ -187,18 +209,113 @@ luaA_selection_transfer_send(lua_State *L) /* 'data' is a string with the data to transfer */ data = luaL_checklstring(L, -1, &data_length); - xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, - transfer->requestor, transfer->property, UTF8_STRING, 8, - data_length, data); + if (!incr) + incr_size = data_length; + + if (data_length >= max_property_length()) + incr = true; + + if (incr) { + xcb_change_window_attributes(globalconf.connection, + transfer->requestor, XCB_CW_EVENT_MASK, + (uint32_t[]) { XCB_EVENT_MASK_PROPERTY_CHANGE }); + + xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, + transfer->requestor, transfer->property, INCR, 32, 1, + (const uint32_t[]) { incr_size }); + + /* Save the data on the transfer object */ + luaA_getuservalue(L, 1); + lua_pushliteral(L, TRANSFER_DATA_INDEX); + lua_pushvalue(L, -3); + lua_rawset(L, -3); + lua_pop(L, 1); + + transfer->state = TRANSFER_INCREMENTAL; + transfer->offset = 0; + } else { + xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, + transfer->requestor, transfer->property, UTF8_STRING, 8, + data_length, data); + } } selection_transfer_notify(transfer->requestor, transfer->selection, transfer->target, transfer->property, transfer->time); - transfer_done(L, transfer); + if (!incr) + transfer_done(L, transfer); return 0; } +static void +transfer_continue_incremental(lua_State *L, int ud) +{ + const char *data; + size_t data_length; + selection_transfer_t *transfer = luaA_checkudata(L, ud, &selection_transfer_class); + + ud = luaA_absindex(L, ud); + + /* Get the data that is to be sent next */ + luaA_getuservalue(L, ud); + lua_pushliteral(L, TRANSFER_DATA_INDEX); + lua_rawget(L, -2); + lua_remove(L, -2); + + data = luaL_checklstring(L, -1, &data_length); + if (transfer->offset == data_length) { + /* End of transfer */ + xcb_change_window_attributes(globalconf.connection, + transfer->requestor, XCB_CW_EVENT_MASK, + (uint32_t[]) { 0 }); + xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, + transfer->requestor, transfer->property, UTF8_STRING, 8, + 0, NULL); + transfer_done(L, transfer); + } else { + /* Send next piece of data */ + assert(transfer->offset < data_length); + size_t next_length = MIN(data_length - transfer->offset, max_property_length()); + xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, + transfer->requestor, transfer->property, UTF8_STRING, 8, + next_length, &data[transfer->offset]); + transfer->offset += next_length; + } + lua_pop(L, 1); +} + +void +selection_transfer_handle_propertynotify(xcb_property_notify_event_t *ev) +{ + lua_State *L = globalconf_get_lua_State(); + + if (ev->state != XCB_PROPERTY_DELETE) + return; + + /* Iterate over all active selection acquire objects */ + lua_pushliteral(L, REGISTRY_TRANSFER_TABLE_INDEX); + lua_rawget(L, LUA_REGISTRYINDEX); + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + if (lua_type(L, -1) == LUA_TUSERDATA) { + selection_transfer_t *transfer = lua_touserdata(L, -1); + if (transfer->state == TRANSFER_INCREMENTAL + && transfer->requestor == ev->window + && transfer->property == ev->atom) { + transfer_continue_incremental(L, -1); + /* Remove table, key and transfer object */ + lua_pop(L, 3); + return; + } + } + /* Remove the value, leaving only the key */ + lua_pop(L, 1); + } + /* Remove the table */ + lua_pop(L, 1); +} + static bool selection_transfer_checker(selection_transfer_t *transfer) { diff --git a/objects/selection_transfer.h b/objects/selection_transfer.h index 31ada219..25d197b4 100644 --- a/objects/selection_transfer.h +++ b/objects/selection_transfer.h @@ -29,6 +29,7 @@ void selection_transfer_class_setup(lua_State*); void selection_transfer_reject(xcb_window_t, xcb_atom_t, xcb_atom_t, xcb_timestamp_t); void selection_transfer_begin(lua_State*, int, xcb_window_t, xcb_atom_t, xcb_atom_t, xcb_atom_t, xcb_timestamp_t); +void selection_transfer_handle_propertynotify(xcb_property_notify_event_t*); #endif diff --git a/property.c b/property.c index f84a6b52..cea98fb8 100644 --- a/property.c +++ b/property.c @@ -26,6 +26,7 @@ #include "objects/client.h" #include "objects/drawin.h" #include "objects/selection_getter.h" +#include "objects/selection_transfer.h" #include "xwindow.h" #include @@ -487,6 +488,7 @@ property_handle_propertynotify(xcb_property_notify_event_t *ev) globalconf.timestamp = ev->time; property_handle_propertynotify_xproperty(ev); + selection_transfer_handle_propertynotify(ev); /* Find the correct event handler */ #define HANDLE(atom_, cb) \ From 18dd22811e3ce3cabb1db61dd3ef683fc738a328 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Mon, 11 Feb 2019 06:54:21 +0100 Subject: [PATCH 11/17] Transfer objects: Accept data in chunks This commit adds a "continue" signal on transfer objects. This signal is used to request the next chunk of data from Lua after the last one was sent. Signed-off-by: Uli Schlachter --- objects/selection_transfer.c | 132 ++++++++++++++++++++++------------- 1 file changed, 83 insertions(+), 49 deletions(-) diff --git a/objects/selection_transfer.c b/objects/selection_transfer.c index 092745d9..ae1e1b43 100644 --- a/objects/selection_transfer.c +++ b/objects/selection_transfer.c @@ -29,7 +29,8 @@ enum transfer_state { TRANSFER_WAIT_FOR_DATA, - TRANSFER_INCREMENTAL, + TRANSFER_INCREMENTAL_SENDING, + TRANSFER_INCREMENTAL_DONE, TRANSFER_DONE }; @@ -48,6 +49,8 @@ typedef struct selection_transfer_t enum transfer_state state; /* Offset into TRANSFER_DATA_INDEX for the next chunk of data */ size_t offset; + /* Can there be more data coming from Lua? */ + bool more_data; } selection_transfer_t; static lua_class_t selection_transfer_class; @@ -97,6 +100,53 @@ transfer_done(lua_State *L, selection_transfer_t *transfer) lua_pop(L, 1); } +static void +transfer_continue_incremental(lua_State *L, int ud) +{ + const char *data; + size_t data_length; + selection_transfer_t *transfer = luaA_checkudata(L, ud, &selection_transfer_class); + + ud = luaA_absindex(L, ud); + + /* Get the data that is to be sent next */ + luaA_getuservalue(L, ud); + lua_pushliteral(L, TRANSFER_DATA_INDEX); + lua_rawget(L, -2); + lua_remove(L, -2); + + data = luaL_checklstring(L, -1, &data_length); + if (transfer->offset == data_length) { + if (transfer->more_data) { + /* Request the next piece of data from Lua */ + transfer->state = TRANSFER_INCREMENTAL_DONE; + luaA_object_emit_signal(L, ud, "continue", 0); + if (transfer->state != TRANSFER_INCREMENTAL_DONE) { + /* Lua gave us more data to send. */ + lua_pop(L, 1); + return; + } + } + /* End of transfer */ + xcb_change_window_attributes(globalconf.connection, + transfer->requestor, XCB_CW_EVENT_MASK, + (uint32_t[]) { 0 }); + xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, + transfer->requestor, transfer->property, UTF8_STRING, 8, + 0, NULL); + transfer_done(L, transfer); + } else { + /* Send next piece of data */ + assert(transfer->offset < data_length); + size_t next_length = MIN(data_length - transfer->offset, max_property_length()); + xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, + transfer->requestor, transfer->property, UTF8_STRING, 8, + next_length, &data[transfer->offset]); + transfer->offset += next_length; + } + lua_pop(L, 1); +} + void selection_transfer_begin(lua_State *L, int ud, xcb_window_t requestor, xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, @@ -153,23 +203,44 @@ luaA_selection_transfer_send(lua_State *L) size_t incr_size = 0; selection_transfer_t *transfer = luaA_checkudata(L, 1, &selection_transfer_class); - if (transfer->state != TRANSFER_WAIT_FOR_DATA) + if (transfer->state != TRANSFER_WAIT_FOR_DATA && transfer->state != TRANSFER_INCREMENTAL_DONE) luaL_error(L, "Transfer object is not ready for more data to be sent"); - /* Get format and data from the table */ luaA_checktable(L, 2); + + lua_pushliteral(L, "continue"); + lua_rawget(L, 2); + transfer->more_data = incr = lua_toboolean(L, -1); + if (incr && lua_isnumber(L, -1)) + incr_size = lua_tonumber(L, -1); + lua_pop(L, 1); + + if (transfer->state == TRANSFER_INCREMENTAL_DONE) { + /* Save the data on the transfer object */ + lua_pushliteral(L, "data"); + lua_rawget(L, 2); + + luaA_getuservalue(L, 1); + lua_pushliteral(L, TRANSFER_DATA_INDEX); + lua_pushvalue(L, -3); + lua_rawset(L, -3); + lua_pop(L, 1); + + /* Continue the incremental transfer */ + transfer->state = TRANSFER_INCREMENTAL_SENDING; + transfer->offset = 0; + + transfer_continue_incremental(L, 1); + + return 0; + } + + /* Get format and data from the table */ lua_pushliteral(L, "format"); lua_rawget(L, 2); lua_pushliteral(L, "data"); lua_rawget(L, 2); - lua_pushliteral(L, "continue"); - lua_rawget(L, 2); - incr = lua_toboolean(L, -1); - if (incr && lua_isnumber(L, -1)) - incr_size = lua_tonumber(L, -1); - lua_pop(L, 1); - if (lua_isstring(L, -2)) { const char *format_string = luaL_checkstring(L, -2); if (A_STRNEQ(format_string, "atom")) @@ -231,7 +302,7 @@ luaA_selection_transfer_send(lua_State *L) lua_rawset(L, -3); lua_pop(L, 1); - transfer->state = TRANSFER_INCREMENTAL; + transfer->state = TRANSFER_INCREMENTAL_SENDING; transfer->offset = 0; } else { xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, @@ -248,43 +319,6 @@ luaA_selection_transfer_send(lua_State *L) return 0; } -static void -transfer_continue_incremental(lua_State *L, int ud) -{ - const char *data; - size_t data_length; - selection_transfer_t *transfer = luaA_checkudata(L, ud, &selection_transfer_class); - - ud = luaA_absindex(L, ud); - - /* Get the data that is to be sent next */ - luaA_getuservalue(L, ud); - lua_pushliteral(L, TRANSFER_DATA_INDEX); - lua_rawget(L, -2); - lua_remove(L, -2); - - data = luaL_checklstring(L, -1, &data_length); - if (transfer->offset == data_length) { - /* End of transfer */ - xcb_change_window_attributes(globalconf.connection, - transfer->requestor, XCB_CW_EVENT_MASK, - (uint32_t[]) { 0 }); - xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, - transfer->requestor, transfer->property, UTF8_STRING, 8, - 0, NULL); - transfer_done(L, transfer); - } else { - /* Send next piece of data */ - assert(transfer->offset < data_length); - size_t next_length = MIN(data_length - transfer->offset, max_property_length()); - xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, - transfer->requestor, transfer->property, UTF8_STRING, 8, - next_length, &data[transfer->offset]); - transfer->offset += next_length; - } - lua_pop(L, 1); -} - void selection_transfer_handle_propertynotify(xcb_property_notify_event_t *ev) { @@ -300,7 +334,7 @@ selection_transfer_handle_propertynotify(xcb_property_notify_event_t *ev) while (lua_next(L, -2) != 0) { if (lua_type(L, -1) == LUA_TUSERDATA) { selection_transfer_t *transfer = lua_touserdata(L, -1); - if (transfer->state == TRANSFER_INCREMENTAL + if (transfer->state == TRANSFER_INCREMENTAL_SENDING && transfer->requestor == ev->window && transfer->property == ev->atom) { transfer_continue_incremental(L, -1); From 6d7014ca68afb08cef0850d9c3270f56cbfcce99 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Mon, 11 Feb 2019 07:45:48 +0100 Subject: [PATCH 12/17] Selection transfer: Change event mask after append When a selection transfer is done, we are no longer interested in events from the selection window. However, this happens after we set the property to indicate end of transfer, not before. The (untested) theory here is that this should make selection transfers from AwesomeWM to AwesomeWM, i.e. internal transfers, work. Signed-off-by: Uli Schlachter --- objects/selection_transfer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/objects/selection_transfer.c b/objects/selection_transfer.c index ae1e1b43..d721757e 100644 --- a/objects/selection_transfer.c +++ b/objects/selection_transfer.c @@ -128,12 +128,12 @@ transfer_continue_incremental(lua_State *L, int ud) } } /* End of transfer */ - xcb_change_window_attributes(globalconf.connection, - transfer->requestor, XCB_CW_EVENT_MASK, - (uint32_t[]) { 0 }); xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, transfer->requestor, transfer->property, UTF8_STRING, 8, 0, NULL); + xcb_change_window_attributes(globalconf.connection, + transfer->requestor, XCB_CW_EVENT_MASK, + (uint32_t[]) { 0 }); transfer_done(L, transfer); } else { /* Send next piece of data */ From fa39de17b38506b6f7f7b447775b21975a415a72 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Mon, 11 Feb 2019 09:15:34 +0100 Subject: [PATCH 13/17] Add a test for selection ownership and transfer Signed-off-by: Uli Schlachter --- tests/test-selection-transfer.lua | 235 ++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 tests/test-selection-transfer.lua diff --git a/tests/test-selection-transfer.lua b/tests/test-selection-transfer.lua new file mode 100644 index 00000000..8a877720 --- /dev/null +++ b/tests/test-selection-transfer.lua @@ -0,0 +1,235 @@ +-- Test the selection ownership and transfer API + +local runner = require("_runner") +local spawn = require("awful.spawn") + +-- Assemble data for the large transfer that will be done later +local large_transfer_piece = "a" +for _ = 1, 25 do + large_transfer_piece = large_transfer_piece .. large_transfer_piece +end +large_transfer_piece = large_transfer_piece .. large_transfer_piece .. large_transfer_piece + +local large_transfer_piece_count = 3 +local large_transfer_size = #large_transfer_piece * large_transfer_piece_count + +local header = [[ +local lgi = require("lgi") +local Gdk = lgi.Gdk +local Gtk = lgi.Gtk +local GLib = lgi.GLib +local function assert_equal(a, b) + assert(a == b, + string.format("%s == %s", a or "nil/false", b or "nil/false")) +end +local clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) +]] + +local acquire_and_clear_clipboard = header .. [[ +clipboard:set_text("This is an experiment", -1) +GLib.idle_add(GLib.PRIORITY_DEFAULT, Gtk.main_quit) +Gtk.main() +]] + +local done_footer = [[ +io.stdout:write("done") +io.stdout:flush() +]] + +local check_targets_and_text = header .. [[ +local targets = clipboard:wait_for_targets() +assert_equal(targets[1]:name(), "TARGETS") +assert_equal(targets[2]:name(), "UTF8_STRING") +assert_equal(#targets, 2) +assert_equal(clipboard:wait_for_text(), "Hello World!") +]] .. done_footer + +local check_large_transfer = header + .. string.format("\nassert_equal(#clipboard:wait_for_text(), %d)\n", large_transfer_size) + .. done_footer + +local check_empty_selection = header .. [[ +assert_equal(clipboard:wait_for_targets(), nil) +assert_equal(clipboard:wait_for_text(), nil) +]] .. done_footer + +local selection +local selection_released +local continue + +local function wait_a_bit(count) + if continue or count == 5 then + return true + end +end + +runner.run_steps{ + function() + -- Get the selection + local s = assert(selection_acquire("CLIPBOARD"), + "Failed to acquire the clipboard selection") + + -- Steal selection ownership from ourselves and test that it works + local s_released + s:connect_signal("release", function() s_released = true end) + + selection = assert(selection_acquire("CLIPBOARD"), + "Failed to acquire the clipboard selection") + + assert(s_released) + + -- Now test selection transfers + selection = assert(selection_acquire("CLIPBOARD"), + "Failed to acquire the clipboard selection") + selection:connect_signal("request", function(_, target, transfer) + if target == "TARGETS" then + transfer:send{ + format = "atom", + data = { "TARGETS", "UTF8_STRING" }, + } + elseif target == "UTF8_STRING" then + transfer:send{ data = "Hello World!" } + end + end) + awesome.sync() + spawn.with_line_callback({ "lua", "-e", check_targets_and_text }, + { stdout = function(line) + assert(line == "done", "Unexpected line: " .. line) + continue = true + end }) + return true + end, + + function() + -- Wait for the test to succeed + if not continue then + return + end + continue = false + + -- Now test piece-wise selection transfers + selection = assert(selection_acquire("CLIPBOARD"), + "Failed to acquire the clipboard selection") + selection:connect_signal("request", function(_, target, transfer) + if target == "TARGETS" then + transfer:send{ + format = "atom", + data = { "TARGETS", "UTF8_STRING" }, + } + elseif target == "UTF8_STRING" then + local offset = 1 + local data = "Hello World!" + local function send_piece() + local piece = data:sub(offset, offset) + transfer:send{ + data = piece, + continue = piece ~= "" + } + offset = offset + 1 + end + transfer:connect_signal("continue", send_piece) + send_piece() + end + end) + awesome.sync() + spawn.with_line_callback({ "lua", "-e", check_targets_and_text }, + { stdout = function(line) + assert(line == "done", "Unexpected line: " .. line) + continue = true + end }) + return true + end, + + function() + -- Wait for the test to succeed + if not continue then + return + end + continue = false + + -- Now test a huge transfer + selection = assert(selection_acquire("CLIPBOARD"), + "Failed to acquire the clipboard selection") + selection:connect_signal("request", function(_, target, transfer) + if target == "TARGETS" then + transfer:send{ + format = "atom", + data = { "TARGETS", "UTF8_STRING" }, + } + elseif target == "UTF8_STRING" then + local count = 0 + local function send_piece() + count = count + 1 + local done = count == large_transfer_piece_count + transfer:send{ + data = large_transfer_piece, + continue = not done, + } + end + transfer:connect_signal("continue", send_piece) + send_piece() + end + end) + awesome.sync() + spawn.with_line_callback({ "lua", "-e", check_large_transfer }, + { stdout = function(line) + assert(line == "done", "Unexpected line: " .. line) + continue = true + end }) + return true + end, + + wait_a_bit, + wait_a_bit, + wait_a_bit, + wait_a_bit, + wait_a_bit, + + function() + -- Wait for the test to succeed + if not continue then + return + end + continue = false + + -- Now test that :release() works + selection:release() + awesome.sync() + spawn.with_line_callback({ "lua", "-e", check_empty_selection }, + { stdout = function(line) + assert(line == "done", "Unexpected line: " .. line) + continue = true + end }) + + return true + end, + + function() + -- Wait for the test to succeed + if not continue then + return + end + continue = false + + -- Test for "release" signal when we lose selection + selection = assert(selection_acquire("CLIPBOARD"), + "Failed to acquire the clipboard selection") + selection:connect_signal("release", function() selection_released = true end) + awesome.sync() + spawn.with_line_callback({ "lua", "-e", acquire_and_clear_clipboard }, + { exit = function() continue = true end }) + return true + end, + + function() + -- Wait for the previous test to succeed + if not continue then + return + end + continue = false + assert(selection_released) + return true + end, +} + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 From c3338c5a740034b65a02d3041b366fff67bc1279 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Mon, 11 Feb 2019 09:15:57 +0100 Subject: [PATCH 14/17] Luacheck: Allow selection_acquire global Signed-off-by: Uli Schlachter --- .luacheckrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.luacheckrc b/.luacheckrc index 0261cdb0..d5a3ccad 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -27,6 +27,7 @@ read_globals = { "keygrabber", "mousegrabber", "root", + "selection_acquire", "selection_getter", "selection", "selection_watcher", From 01bd2d18965ade682b2c28e96ff49d8f9e10fc16 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 17 Feb 2019 08:00:57 +0100 Subject: [PATCH 15/17] selection_acquire: Change the API to a table As requested in the review, instead of just having a single string argument, selection_acquire() now has a table as its argument. It searches the string under the "selection" key here. Signed-off-by: Uli Schlachter --- objects/selection_acquire.c | 5 ++++- tests/test-selection-transfer.lua | 12 ++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/objects/selection_acquire.c b/objects/selection_acquire.c index 11395813..46868b11 100644 --- a/objects/selection_acquire.c +++ b/objects/selection_acquire.c @@ -139,7 +139,10 @@ luaA_selection_acquire_new(lua_State *L) xcb_atom_t name_atom; selection_acquire_t *selection; - name = luaL_checklstring(L, 2, &name_length); + luaA_checktable(L, 2); + lua_pushliteral(L, "selection"); + lua_gettable(L, 2); + name = luaL_checklstring(L, -1, &name_length); /* Get the atom identifying the selection */ reply = xcb_intern_atom_reply(globalconf.connection, diff --git a/tests/test-selection-transfer.lua b/tests/test-selection-transfer.lua index 8a877720..018caa8d 100644 --- a/tests/test-selection-transfer.lua +++ b/tests/test-selection-transfer.lua @@ -66,20 +66,20 @@ end runner.run_steps{ function() -- Get the selection - local s = assert(selection_acquire("CLIPBOARD"), + local s = assert(selection_acquire{ selection = "CLIPBOARD" }, "Failed to acquire the clipboard selection") -- Steal selection ownership from ourselves and test that it works local s_released s:connect_signal("release", function() s_released = true end) - selection = assert(selection_acquire("CLIPBOARD"), + selection = assert(selection_acquire{ selection = "CLIPBOARD" }, "Failed to acquire the clipboard selection") assert(s_released) -- Now test selection transfers - selection = assert(selection_acquire("CLIPBOARD"), + selection = assert(selection_acquire{ selection = "CLIPBOARD" }, "Failed to acquire the clipboard selection") selection:connect_signal("request", function(_, target, transfer) if target == "TARGETS" then @@ -108,7 +108,7 @@ runner.run_steps{ continue = false -- Now test piece-wise selection transfers - selection = assert(selection_acquire("CLIPBOARD"), + selection = assert(selection_acquire{ selection = "CLIPBOARD" }, "Failed to acquire the clipboard selection") selection:connect_signal("request", function(_, target, transfer) if target == "TARGETS" then @@ -148,7 +148,7 @@ runner.run_steps{ continue = false -- Now test a huge transfer - selection = assert(selection_acquire("CLIPBOARD"), + selection = assert(selection_acquire{ selection = "CLIPBOARD" }, "Failed to acquire the clipboard selection") selection:connect_signal("request", function(_, target, transfer) if target == "TARGETS" then @@ -212,7 +212,7 @@ runner.run_steps{ continue = false -- Test for "release" signal when we lose selection - selection = assert(selection_acquire("CLIPBOARD"), + selection = assert(selection_acquire{ selection = "CLIPBOARD" }, "Failed to acquire the clipboard selection") selection:connect_signal("release", function() selection_released = true end) awesome.sync() From b73fa1cc28550666ff4c77c9b3f130f72f4c97a6 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 17 Feb 2019 18:43:02 +0100 Subject: [PATCH 16/17] Reduce scope of data variable This fixes a warning from Codacy. Signed-off-by: Uli Schlachter --- objects/selection_transfer.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/objects/selection_transfer.c b/objects/selection_transfer.c index d721757e..e1553014 100644 --- a/objects/selection_transfer.c +++ b/objects/selection_transfer.c @@ -198,7 +198,6 @@ static int luaA_selection_transfer_send(lua_State *L) { size_t data_length; - const char *data; bool incr = false; size_t incr_size = 0; @@ -278,7 +277,7 @@ luaA_selection_transfer_send(lua_State *L) len, &atoms[0]); } else { /* 'data' is a string with the data to transfer */ - data = luaL_checklstring(L, -1, &data_length); + const char *data = luaL_checklstring(L, -1, &data_length); if (!incr) incr_size = data_length; From 0ce668857ede1b33e86fc4d71d94f1b9708c8f69 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Mon, 18 Feb 2019 07:16:16 +0100 Subject: [PATCH 17/17] test-selection-transfer: Explain need for wait_a_bit Signed-off-by: Uli Schlachter --- tests/test-selection-transfer.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test-selection-transfer.lua b/tests/test-selection-transfer.lua index 018caa8d..1f851bb8 100644 --- a/tests/test-selection-transfer.lua +++ b/tests/test-selection-transfer.lua @@ -179,6 +179,8 @@ runner.run_steps{ return true end, + -- The large data transfer above transfers 3 * 2^25 bytes of data. That's + -- 96 MiB and takes a while. wait_a_bit, wait_a_bit, wait_a_bit,