Merge pull request #2638 from psychon/selection_watch

Add selection watcher objects
This commit is contained in:
mergify[bot] 2019-02-09 20:22:20 +00:00 committed by GitHub
commit a5e8cde34e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 380 additions and 1 deletions

View File

@ -28,6 +28,7 @@ read_globals = {
"mousegrabber", "mousegrabber",
"root", "root",
"selection", "selection",
"selection_watcher",
"tag", "tag",
"window", "window",
"table.unpack", "table.unpack",

View File

@ -37,7 +37,7 @@ install:
# Install build dependencies. # Install build dependencies.
# See also `apt-cache showsrc awesome | grep -E '^(Version|Build-Depends)'`. # See also `apt-cache showsrc awesome | grep -E '^(Version|Build-Depends)'`.
- sudo apt-get install -y libcairo2-dev gir1.2-gtk-3.0 libpango1.0-dev libxcb-xtest0-dev libxcb-icccm4-dev libxcb-randr0-dev libxcb-keysyms1-dev libxcb-xinerama0-dev libdbus-1-dev libxdg-basedir-dev libstartup-notification0-dev imagemagick libxcb1-dev libxcb-shape0-dev libxcb-util0-dev libx11-xcb-dev libxcb-cursor-dev libxcb-xkb-dev libxkbcommon-dev libxkbcommon-x11-dev - sudo apt-get install -y libcairo2-dev gir1.2-gtk-3.0 libpango1.0-dev libxcb-xtest0-dev libxcb-icccm4-dev libxcb-randr0-dev libxcb-keysyms1-dev libxcb-xinerama0-dev libdbus-1-dev libxdg-basedir-dev libstartup-notification0-dev imagemagick libxcb1-dev libxcb-shape0-dev libxcb-util0-dev libx11-xcb-dev libxcb-cursor-dev libxcb-xkb-dev libxcb-xfixes0-dev libxkbcommon-dev libxkbcommon-x11-dev
- sudo gem install asciidoctor - sudo gem install asciidoctor
# Deps for tests. # Deps for tests.

View File

@ -88,6 +88,7 @@ set(AWE_SRCS
${BUILD_DIR}/objects/drawin.c ${BUILD_DIR}/objects/drawin.c
${BUILD_DIR}/objects/key.c ${BUILD_DIR}/objects/key.c
${BUILD_DIR}/objects/screen.c ${BUILD_DIR}/objects/screen.c
${BUILD_DIR}/objects/selection_watcher.c
${BUILD_DIR}/objects/tag.c ${BUILD_DIR}/objects/tag.c
${BUILD_DIR}/objects/window.c) ${BUILD_DIR}/objects/window.c)

View File

@ -53,6 +53,7 @@
#include <xcb/xinerama.h> #include <xcb/xinerama.h>
#include <xcb/xtest.h> #include <xcb/xtest.h>
#include <xcb/shape.h> #include <xcb/shape.h>
#include <xcb/xfixes.h>
#include <glib-unix.h> #include <glib-unix.h>
@ -733,6 +734,7 @@ main(int argc, char **argv)
xcb_prefetch_extension_data(globalconf.connection, &xcb_randr_id); xcb_prefetch_extension_data(globalconf.connection, &xcb_randr_id);
xcb_prefetch_extension_data(globalconf.connection, &xcb_xinerama_id); xcb_prefetch_extension_data(globalconf.connection, &xcb_xinerama_id);
xcb_prefetch_extension_data(globalconf.connection, &xcb_shape_id); xcb_prefetch_extension_data(globalconf.connection, &xcb_shape_id);
xcb_prefetch_extension_data(globalconf.connection, &xcb_xfixes_id);
if (xcb_cursor_context_new(globalconf.connection, globalconf.screen, &globalconf.cursor_ctx) < 0) if (xcb_cursor_context_new(globalconf.connection, globalconf.screen, &globalconf.cursor_ctx) < 0)
fatal("Failed to initialize xcb-cursor"); fatal("Failed to initialize xcb-cursor");
@ -794,6 +796,13 @@ main(int argc, char **argv)
p_delete(&reply); p_delete(&reply);
} }
/* check for xfixes extension */
query = xcb_get_extension_data(globalconf.connection, &xcb_xfixes_id);
globalconf.have_xfixes = query && query->present;
if (globalconf.have_xfixes)
xcb_discard_reply(globalconf.connection,
xcb_xfixes_query_version(globalconf.connection, 1, 0).sequence);
event_init(); event_init();
/* Allocate the key symbols */ /* Allocate the key symbols */

View File

@ -141,6 +141,7 @@ set(AWESOME_DEPENDENCIES
xcb-keysyms>=0.3.4 xcb-keysyms>=0.3.4
xcb-icccm xcb-icccm
xcb-icccm>=0.3.8 xcb-icccm>=0.3.8
xcb-xfixes
# NOTE: it's not clear what version is required, but 1.10 works at least. # NOTE: it's not clear what version is required, but 1.10 works at least.
# See https://github.com/awesomeWM/awesome/pull/149#issuecomment-94208356. # See https://github.com/awesomeWM/awesome/pull/149#issuecomment-94208356.
xcb-xkb xcb-xkb

View File

@ -68,6 +68,7 @@ environment):
- [libxcb-util >= 0.3.8](https://xcb.freedesktop.org/) - [libxcb-util >= 0.3.8](https://xcb.freedesktop.org/)
- [libxcb-keysyms >= 0.3.4](https://xcb.freedesktop.org/) - [libxcb-keysyms >= 0.3.4](https://xcb.freedesktop.org/)
- [libxcb-icccm >= 0.3.8](https://xcb.freedesktop.org/) - [libxcb-icccm >= 0.3.8](https://xcb.freedesktop.org/)
- [libxcb-xfixes](https://xcb.freedesktop.org/)
- [xcb-util-xrm >= 1.0](https://github.com/Airblader/xcb-util-xrm) - [xcb-util-xrm >= 1.0](https://github.com/Airblader/xcb-util-xrm)
- [libxkbcommon](http://xkbcommon.org/) with X11 support enabled - [libxkbcommon](http://xkbcommon.org/) with X11 support enabled
- [libstartup-notification >= - [libstartup-notification >=

View File

@ -24,6 +24,7 @@
#include "property.h" #include "property.h"
#include "objects/tag.h" #include "objects/tag.h"
#include "objects/drawin.h" #include "objects/drawin.h"
#include "objects/selection_watcher.h"
#include "xwindow.h" #include "xwindow.h"
#include "ewmh.h" #include "ewmh.h"
#include "objects/client.h" #include "objects/client.h"
@ -43,6 +44,7 @@
#include <xcb/xcb_icccm.h> #include <xcb/xcb_icccm.h>
#include <xcb/xcb_event.h> #include <xcb/xcb_event.h>
#include <xcb/xkb.h> #include <xcb/xkb.h>
#include <xcb/xfixes.h>
#define DO_EVENT_HOOK_CALLBACK(type, xcbtype, xcbeventprefix, arraytype, match) \ #define DO_EVENT_HOOK_CALLBACK(type, xcbtype, xcbeventprefix, arraytype, match) \
static void \ static void \
@ -1116,6 +1118,7 @@ void event_handle(xcb_generic_event_t *event)
EXTENSION_EVENT(randr, XCB_RANDR_NOTIFY, event_handle_randr_output_change_notify); EXTENSION_EVENT(randr, XCB_RANDR_NOTIFY, event_handle_randr_output_change_notify);
EXTENSION_EVENT(shape, XCB_SHAPE_NOTIFY, event_handle_shape_notify); EXTENSION_EVENT(shape, XCB_SHAPE_NOTIFY, event_handle_shape_notify);
EXTENSION_EVENT(xkb, 0, event_handle_xkb_notify); EXTENSION_EVENT(xkb, 0, event_handle_xkb_notify);
EXTENSION_EVENT(xfixes, XCB_XFIXES_SELECTION_NOTIFY, event_handle_xfixes_selection_notify);
#undef EXTENSION_EVENT #undef EXTENSION_EVENT
} }
@ -1134,6 +1137,10 @@ void event_init(void)
reply = xcb_get_extension_data(globalconf.connection, &xcb_xkb_id); reply = xcb_get_extension_data(globalconf.connection, &xcb_xkb_id);
if (reply && reply->present) if (reply && reply->present)
globalconf.event_base_xkb = reply->first_event; globalconf.event_base_xkb = reply->first_event;
reply = xcb_get_extension_data(globalconf.connection, &xcb_xfixes_id);
if (reply && reply->present)
globalconf.event_base_xfixes = reply->first_event;
} }
// vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -110,9 +110,12 @@ typedef struct
bool have_input_shape; bool have_input_shape;
/** Check for XKB extension */ /** Check for XKB extension */
bool have_xkb; bool have_xkb;
/** Check for XFixes extension */
bool have_xfixes;
uint8_t event_base_shape; uint8_t event_base_shape;
uint8_t event_base_xkb; uint8_t event_base_xkb;
uint8_t event_base_randr; uint8_t event_base_randr;
uint8_t event_base_xfixes;
/** Clients list */ /** Clients list */
client_array_t clients; client_array_t clients;
/** Embedded windows */ /** Embedded windows */

4
luaa.c
View File

@ -49,6 +49,7 @@
#include "objects/drawable.h" #include "objects/drawable.h"
#include "objects/drawin.h" #include "objects/drawin.h"
#include "objects/screen.h" #include "objects/screen.h"
#include "objects/selection_watcher.h"
#include "objects/tag.h" #include "objects/tag.h"
#include "property.h" #include "property.h"
#include "selection.h" #include "selection.h"
@ -1035,6 +1036,9 @@ luaA_init(xdgHandle* xdg, string_array_t *searchpath)
/* Export keys */ /* Export keys */
key_class_setup(L); key_class_setup(L);
/* Export selection watcher */
selection_watcher_class_setup(L);
/* add Lua search paths */ /* add Lua search paths */
lua_getglobal(L, "package"); lua_getglobal(L, "package");
if (LUA_TTABLE != lua_type(L, 1)) if (LUA_TTABLE != lua_type(L, 1))

199
objects/selection_watcher.c Normal file
View File

@ -0,0 +1,199 @@
/*
* selection_watcher.h - selection change watcher
*
* Copyright © 2019 Uli Schlachter <psychon@znc.in>
*
* 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_watcher.h"
#include "common/luaobject.h"
#include "globalconf.h"
#include <xcb/xfixes.h>
#define REGISTRY_WATCHER_TABLE_INDEX "awesome_selection_watchers"
typedef struct selection_watcher_t
{
LUA_OBJECT_HEADER
/** Is this watcher currently active and watching? Used as reference with luaL_ref */
int active_ref;
/** Atom identifying the selection to watch */
xcb_atom_t selection;
/** Window used for watching */
xcb_window_t window;
} selection_watcher_t;
static lua_class_t selection_watcher_class;
LUA_OBJECT_FUNCS(selection_watcher_class, selection_watcher_t, selection_watcher)
void
event_handle_xfixes_selection_notify(xcb_generic_event_t *ev)
{
xcb_xfixes_selection_notify_event_t *e = (void *) ev;
lua_State *L = globalconf_get_lua_State();
/* Iterate over all active selection watchers */
lua_pushliteral(L, REGISTRY_WATCHER_TABLE_INDEX);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
if (lua_type(L, -1) == LUA_TUSERDATA) {
selection_watcher_t *selection = lua_touserdata(L, -1);
if (selection->selection == e->selection && selection->window == e->window) {
lua_pushboolean(L, e->owner != XCB_NONE);
luaA_object_emit_signal(L, -2, "selection_changed", 1);
}
}
/* Remove the watcher */
lua_pop(L, 1);
}
/* Remove watcher table */
lua_pop(L, 1);
}
/** Create a new selection watcher object.
* \param L The Lua VM state.
* \return The number of elements pushed on the stack.
*/
static int
luaA_selection_watcher_new(lua_State *L)
{
size_t name_length;
const char *name;
xcb_intern_atom_reply_t *reply;
selection_watcher_t *selection;
name = luaL_checklstring(L, 2, &name_length);
selection = (void *) selection_watcher_class.allocator(L);
selection->active_ref = LUA_NOREF;
selection->window = XCB_NONE;
/* Get the atom identifying the selection to watch */
reply = xcb_intern_atom_reply(globalconf.connection,
xcb_intern_atom_unchecked(globalconf.connection, false, name_length, name),
NULL);
if (reply) {
selection->selection = reply->atom;
p_delete(&reply);
}
return 1;
}
static int
luaA_selection_watcher_set_active(lua_State *L, selection_watcher_t *selection)
{
bool b = luaA_checkboolean(L, -1);
bool is_active = selection->active_ref != LUA_NOREF;
if(b != is_active)
{
if (b)
{
/* Selection becomes active */
/* Create a window for it */
if (selection->window == XCB_NONE)
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);
/* Start watching for selection changes */
if (globalconf.have_xfixes)
{
xcb_xfixes_select_selection_input(globalconf.connection, selection->window, selection->selection,
XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER |
XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY |
XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE);
} else {
luaA_warn(L, "X11 server does not support the XFixes extension; cannot watch selections");
}
/* Reference the selection watcher. For this, first get the tracking
* table out of the registry. */
lua_pushliteral(L, REGISTRY_WATCHER_TABLE_INDEX);
lua_rawget(L, LUA_REGISTRYINDEX);
/* Then actually get the reference */
lua_pushvalue(L, -3 - 1);
selection->active_ref = luaL_ref(L, -2);
/* And pop the tracking table again */
lua_pop(L, 1);
} else {
/* Stop watching and destroy the window */
if (globalconf.have_xfixes)
xcb_xfixes_select_selection_input(globalconf.connection, selection->window, selection->selection, 0);
xcb_destroy_window(globalconf.connection, selection->window);
/* Unreference the selection object */
lua_pushliteral(L, REGISTRY_WATCHER_TABLE_INDEX);
lua_rawget(L, LUA_REGISTRYINDEX);
luaL_unref(L, -1, selection->active_ref);
lua_pop(L, 1);
selection->active_ref = LUA_NOREF;
}
luaA_object_emit_signal(L, -3, "property::active", 0);
}
return 0;
}
static int
luaA_selection_watcher_get_active(lua_State *L, selection_watcher_t *selection)
{
lua_pushboolean(L, selection->active_ref != LUA_NOREF);
return 1;
}
void
selection_watcher_class_setup(lua_State *L)
{
static const struct luaL_Reg selection_watcher_methods[] =
{
LUA_CLASS_METHODS(selection_watcher)
{ "__call", luaA_selection_watcher_new },
{ NULL, NULL }
};
static const struct luaL_Reg selection_watcher_meta[] = {
LUA_OBJECT_META(selection_watcher)
LUA_CLASS_META
{ NULL, NULL }
};
/* Reference a table in the registry that tracks active watchers. This code
* does debug.getregistry()[REGISTRY_WATCHER_TABLE_INDEX] = {}
*/
lua_pushliteral(L, REGISTRY_WATCHER_TABLE_INDEX);
lua_newtable(L);
lua_rawset(L, LUA_REGISTRYINDEX);
luaA_class_setup(L, &selection_watcher_class, "selection_watcher", NULL,
(lua_class_allocator_t) selection_watcher_new, NULL, NULL,
luaA_class_index_miss_property, luaA_class_newindex_miss_property,
selection_watcher_methods, selection_watcher_meta);
luaA_class_add_property(&selection_watcher_class, "active",
(lua_class_propfunc_t) luaA_selection_watcher_set_active,
(lua_class_propfunc_t) luaA_selection_watcher_get_active,
(lua_class_propfunc_t) luaA_selection_watcher_set_active);
}
// vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -0,0 +1,33 @@
/*
* selection_watcher.h - selection change watcher header
*
* Copyright © 2019 Uli Schlachter <psychon@znc.in>
*
* 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_WATCHER_H
#define AWESOME_OBJECTS_SELECTION_WATCHER_H
#include <lua.h>
#include <xcb/xcb.h>
void selection_watcher_class_setup(lua_State*);
void event_handle_xfixes_selection_notify(xcb_generic_event_t*);
#endif
// vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -0,0 +1,120 @@
-- Test the selection watcher API
local runner = require("_runner")
local spawn = require("awful.spawn")
local header = [[
local lgi = require("lgi")
local Gdk = lgi.Gdk
local Gtk = lgi.Gtk
local GLib = lgi.GLib
local clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
clipboard:set_text("This is an experiment", -1)
]]
local acquire_and_clear_clipboard = header .. [[
GLib.idle_add(GLib.PRIORITY_DEFAULT, Gtk.main_quit)
Gtk.main()
]]
local acquire_clipboard = header .. [[
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 60, Gtk.main_quit)
GLib.idle_add(GLib.PRIORITY_DEFAULT, function()
print("initialisation done")
io.stdout:flush()
end)
Gtk.main()
]]
local had_error = false
local owned_clipboard_changes, unowned_clipboard_changes = 0, 0
local clipboard_watcher = selection_watcher("CLIPBOARD")
local clipboard_watcher_inactive = selection_watcher("CLIPBOARD")
local primary_watcher = selection_watcher("PRIMARY")
clipboard_watcher:connect_signal("selection_changed", function(_, owned)
if owned then
owned_clipboard_changes = owned_clipboard_changes + 1
else
unowned_clipboard_changes = unowned_clipboard_changes + 1
end
end)
clipboard_watcher_inactive:connect_signal("selection_changed", function()
had_error = true
error("Unexpected signal on inactive CLIPBOARD watcher")
end)
primary_watcher:connect_signal("selection_changed", function()
had_error = true
error("Unexpected signal on PRIMARY watcher")
end)
local function check_state(owned, unowned)
assert(not had_error, "there was an error")
assert(owned_clipboard_changes == owned,
string.format("expected %d owned changes, but got %d", owned, owned_clipboard_changes))
assert(unowned_clipboard_changes == unowned,
string.format("expected %d unowned changes, but got %d", unowned, unowned_clipboard_changes))
end
local continue = false
runner.run_steps{
-- Clear the clipboard to get to a known state
function()
check_state(0, 0)
spawn.with_line_callback({ "lua", "-e", acquire_and_clear_clipboard },
{ exit = function() continue = true end })
return true
end,
function()
-- Wait for the clipboard to be cleared
check_state(0, 0)
if not continue then
return
end
-- Activate the watchers
clipboard_watcher.active = true
primary_watcher.active = true
awesome.sync()
-- Set the clipboard
continue = false
spawn.with_line_callback({ "lua", "-e", acquire_clipboard },
{ stdout = function(line)
assert(line == "initialisation done",
"Unexpected line: " .. line)
continue = true
end })
return true
end,
function()
-- Wait for the clipboard to be set
if not continue then
return
end
check_state(1, 0)
-- Now clear the clipboard again
continue = false
spawn.with_line_callback({ "lua", "-e", acquire_and_clear_clipboard },
{ exit = function() continue = true end })
return true
end,
function()
-- Wait for the clipboard to be set
if not continue then
return
end
check_state(2, 1)
return true
end
}
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80