From dabd471bffe1f772c3cf4770dffbce240761917d Mon Sep 17 00:00:00 2001 From: Eamon Walsh Date: Thu, 9 Jul 2009 22:12:55 -0400 Subject: Initial commit. --- Makefile.am | 1 + README | 1 + autogen.sh | 12 ++ configure.ac | 37 +++++ src/Makefile.am | 9 ++ src/app.h | 107 +++++++++++++ src/atom.c | 162 +++++++++++++++++++ src/config.h.in | 25 +++ src/conn.c | 104 ++++++++++++ src/inputmon.c | 466 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.c | 360 ++++++++++++++++++++++++++++++++++++++++++ src/propmon.c | 466 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/selmgr.c | 481 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/selmon.c | 397 ++++++++++++++++++++++++++++++++++++++++++++++ src/util.c | 67 ++++++++ 15 files changed, 2695 insertions(+) create mode 100644 Makefile.am create mode 100644 README create mode 100755 autogen.sh create mode 100644 configure.ac create mode 100644 src/Makefile.am create mode 100644 src/app.h create mode 100644 src/atom.c create mode 100644 src/config.h.in create mode 100644 src/conn.c create mode 100644 src/inputmon.c create mode 100644 src/main.c create mode 100644 src/propmon.c create mode 100644 src/selmgr.c create mode 100644 src/selmon.c create mode 100644 src/util.c diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..1bfdcf4 --- /dev/null +++ b/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=src diff --git a/README b/README new file mode 100644 index 0000000..4d4e9e6 --- /dev/null +++ b/README @@ -0,0 +1 @@ +SELinux Enhanced Selection Manager diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..61289aa --- /dev/null +++ b/autogen.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +srcdir=`dirname $0` +test -z "$srcdir" && srcdir=. + +ORIGDIR=`pwd` +cd $srcdir + +autoreconf -v --install || exit 1 +cd $ORIGDIR || exit $? + +$srcdir/configure "$@" diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..04ec8d1 --- /dev/null +++ b/configure.ac @@ -0,0 +1,37 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.57) +AC_INIT([sedpymgr], 0.9, [ewalsh@tycho.nsa.gov]) +AC_CONFIG_SRCDIR([README]) +AM_INIT_AUTOMAKE([foreign dist-bzip2]) +AC_CONFIG_HEADERS([src/config.h]) + +AC_PROG_CC +AC_HEADER_STDC + +CFLAGS="-g3 -O0 -Wall" +AC_SUBST(CFLAGS) + +# Checks for pkg-config packages +PKG_CHECK_MODULES(XCB, xcb) +PKG_CHECK_MODULES(XFIXES, xcb-xfixes) +PKG_CHECK_MODULES(XSELINUX, xcb-xselinux) +PKG_CHECK_MODULES(GTK, gtk+-2.0) + +AC_CONFIG_FILES([Makefile src/Makefile]) +AC_OUTPUT + +dnl Configuration output + +echo "" +echo " Package: ${PACKAGE_NAME} ${PACKAGE_VERSION}" +echo "" +echo " Used CFLAGS:" +echo " CPPFLAGS............: ${CPPFLAGS}" +echo " CFLAGS..............: ${CFLAGS}" +echo " Warning CFLAGS......: ${CWARNFLAGS}" +echo "" +echo " Installation:" +echo " Prefix..............: ${prefix}" +echo "" diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..3009a4f --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,9 @@ +pkgpythondir = $(pythondir)/xcb + +bin_PROGRAMS = sedpymgr + +sedpymgr_SOURCES = main.c selmon.c selmgr.c propmon.c atom.c conn.c util.c +sedpymgr_CFLAGS = $(GTK_CFLAGS) +sedpymgr_LDADD = $(GTK_LIBS) $(XFIXES_LIBS) $(XSELINUX_LIBS) -lselinux + +noinst_HEADERS = app.h diff --git a/src/app.h b/src/app.h new file mode 100644 index 0000000..1a1c61c --- /dev/null +++ b/src/app.h @@ -0,0 +1,107 @@ +#ifndef _SESELMGR_H +#define _SESELMGR_H + +#include +#include +#include +#include +#include +#include +#include + +enum { + COL_NAME, + COL_ATOM, + COL_WIN, + COL_TYPE, + COL_DATA, + COL_OCTX, + COL_OFG, + COL_OBG, + COL_DCTX, + COL_DFG, + COL_DBG, + COL_ICON, + N_COLS +}; + +struct thread_data { + const gchar *display; + GtkTreeModel *store; + GtkWidget *appwin; + GtkWidget *expwin; +}; + + +/* selmon.c */ +int +sel_monitor_init(struct thread_data *data); + +/* selman.c */ +int +sel_manager_init(struct thread_data *data); + +void +expose(xcb_atom_t atom, xcb_window_t owner, security_context_t level, + xcb_timestamp_t time); + +void +expose_stop(xcb_timestamp_t time); + +/* propmon.c */ +int +prop_monitor_init(struct thread_data *data); + +/* inputmon.c */ + +int +input_monitor_init(struct thread_data *data); + +/* atom.c */ +extern xcb_atom_t xa_targets; +extern xcb_atom_t xa_prop; +extern xcb_atom_t xa_incr; +extern xcb_atom_t xa_atom; +extern xcb_atom_t xa_text; +extern xcb_atom_t wm_name; + +void +atom_init(xcb_connection_t *conn); + +gboolean +atom_is_string_type(xcb_atom_t atom); + +const char * +atom_lookup(xcb_connection_t *conn, xcb_atom_t atom); + +const char * +selection_lookup(xcb_connection_t *conn, xcb_window_t window, xcb_atom_t atom); + +void +selection_register(xcb_connection_t *conn, xcb_window_t window); + +/* conn.c */ +xcb_connection_t * +make_connection(const gchar *display); + +int +fetch_property_data(xcb_connection_t *conn, + xcb_window_t window, xcb_atom_t property, int delete, + xcb_atom_t *type_rtn, unsigned *format_rtn, + void **data_rtn, size_t *len_rtn); + +/* util.c */ +#define APP_ERR(error, msg...) app_err(error, __func__, __LINE__, msg); + +void +app_err(xcb_generic_error_t *error, + const char *func, const int line, const char *fmt, ...) + __attribute__ ((noreturn)); + +char * +get_colors(security_context_t octx, security_context_t dctx); + +char * +scrub_text(void *input); + +#endif diff --git a/src/atom.c b/src/atom.c new file mode 100644 index 0000000..7893db7 --- /dev/null +++ b/src/atom.c @@ -0,0 +1,162 @@ +#include +#include +#include +#include + +#include "app.h" + + +/* + * Changing this to 1 will cause the application to detect and manage + * other selections besides the initial ones + * (PRIMARY, SECONDARY, and CLIPBOARD). + */ +#define TRACK_ALL_SELECTIONS 1 + +struct sel_atom { + const char *name; + xcb_atom_t *ptr; +}; + +static xcb_atom_t clips[3]; +xcb_atom_t xa_targets; +xcb_atom_t xa_prop; +xcb_atom_t xa_incr; +xcb_atom_t xa_atom; +xcb_atom_t xa_text; +static xcb_atom_t xa_string; +static xcb_atom_t xa_utf8; +xcb_atom_t wm_name; + +static struct sel_atom atoms[] = { + { "PRIMARY", clips }, + { "SECONDARY", clips + 1 }, + { "CLIPBOARD", clips + 2 }, + { "TARGETS", &xa_targets }, + { "ATOM", &xa_atom }, + { "INCR", &xa_incr }, + { "TEXT", &xa_text }, + { "STRING", &xa_string }, + { "UTF8_STRING", &xa_utf8 }, + { "_SESELMGR_PROP", &xa_prop }, + { "WM_NAME", &wm_name }, +}; + +static GHashTable *ahash; +static GHashTable *shash; +static int initialized; + +#define NCLIP 3 +#define NATOM (sizeof(atoms) / sizeof(*atoms)) +#define SEL_MASK (XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | \ + XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | \ + XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE) + + +gboolean +atom_is_string_type(xcb_atom_t atom) +{ + return (atom == xa_string) || (atom == xa_utf8); +} + +static const char * +atom_add(xcb_connection_t *conn, xcb_atom_t atom) +{ + char *name = NULL; + xcb_get_atom_name_cookie_t cookie; + xcb_get_atom_name_reply_t *reply; + xcb_generic_error_t *error; + int len; + + cookie = xcb_get_atom_name(conn, atom); + reply = xcb_get_atom_name_reply(conn, cookie, &error); + if (error) + APP_ERR(error, "An error was received during GetAtomName.\n"); + + len = xcb_get_atom_name_name_length(reply); + name = malloc(len + 1); + if (name) { + strncpy(name, (const char *)xcb_get_atom_name_name(reply), len); + name[len] = '\0'; + } + + g_hash_table_insert(ahash, (void *)atom, name); + + free(reply); + return name; +} + +const char * +atom_lookup(xcb_connection_t *conn, xcb_atom_t atom) +{ + const char *name = g_hash_table_lookup(ahash, (void *)atom); + + if (!name) + name = atom_add(conn, atom); + + return name; +} + +static const char * +selection_add(xcb_connection_t *conn, xcb_window_t window, xcb_atom_t atom) +{ + const char *name = atom_lookup(conn, atom); + + xcb_xfixes_select_selection_input(conn, window, atom, SEL_MASK); + g_hash_table_insert(shash, (void *)atom, strdup(name)); + + return name; +} + +const char * +selection_lookup(xcb_connection_t *conn, xcb_window_t window, xcb_atom_t atom) +{ + const char *name = g_hash_table_lookup(shash, (void *)atom); + + if (TRACK_ALL_SELECTIONS && !name) + name = selection_add(conn, window, atom); + + return name; +} + +void +selection_register(xcb_connection_t *conn, xcb_window_t window) +{ + unsigned i; + + for (i = 0; i < NCLIP; i++) + xcb_xfixes_select_selection_input(conn, window, *atoms[i].ptr, SEL_MASK); +} + +void +atom_init(xcb_connection_t *conn) +{ + xcb_intern_atom_cookie_t cookie[NATOM]; + xcb_intern_atom_reply_t *reply; + xcb_generic_error_t *error; + unsigned i; + + if (initialized) + return; + + ahash = g_hash_table_new_full(NULL, NULL, NULL, free); + shash = g_hash_table_new_full(NULL, NULL, NULL, free); + + for (i = 0; i < NATOM; i++) + cookie[i] = xcb_intern_atom(conn, 0, strlen(atoms[i].name), atoms[i].name); + + for (i = 0; i < NATOM; i++) { + reply = xcb_intern_atom_reply(conn, cookie[i], &error); + if (error) + APP_ERR(error, "An error was received during InternAtom.\n"); + *atoms[i].ptr = reply->atom; + g_hash_table_insert(ahash, (void *)*atoms[i].ptr, strdup(atoms[i].name)); + free(reply); + } + + for (i = 0; i < NCLIP; i++) + g_hash_table_insert(shash, (void *)*atoms[i].ptr, strdup(atoms[i].name)); + + initialized = 1; +} + diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..ba1563c --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,25 @@ +/* src/config.h.in. Generated from configure.ac by autoheader. */ + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Version number of package */ +#undef VERSION diff --git a/src/conn.c b/src/conn.c new file mode 100644 index 0000000..7176f61 --- /dev/null +++ b/src/conn.c @@ -0,0 +1,104 @@ +#include +#include +#include +#include + +#include "app.h" + + +static void +check_xfixes(xcb_connection_t *conn) +{ + const xcb_query_extension_reply_t *ext; + xcb_xfixes_query_version_cookie_t cookie; + xcb_xfixes_query_version_reply_t *version; + xcb_generic_error_t *error; + + ext = xcb_get_extension_data(conn, &xcb_xfixes_id); + if (!ext->present) { + APP_ERR(NULL, "X display does not support XFixes extension\n"); + exit(1); + } + + cookie = xcb_xfixes_query_version(conn, 4, 0); + version = xcb_xfixes_query_version_reply(conn, cookie, &error); + + if (!ext->present || error || version->major_version < 4) { + APP_ERR(NULL, "X display does not support XFixes extension 4.0\n"); + exit(1); + } + + free(version); +} + +static void +check_xselinux(xcb_connection_t *conn) +{ + const xcb_query_extension_reply_t *ext; + xcb_selinux_query_version_cookie_t cookie; + xcb_selinux_query_version_reply_t *version; + xcb_generic_error_t *error; + + ext = xcb_get_extension_data(conn, &xcb_selinux_id); + if (!ext->present) + APP_ERR(NULL, "X display does not support SELinux extension\n"); + + cookie = xcb_selinux_query_version(conn, 1, 1); + version = xcb_selinux_query_version_reply(conn, cookie, &error); + + if (!ext->present || error || version->server_major < 1) + APP_ERR(NULL, "X display does not support SELinux extension 1.0\n"); + + free(version); +} + +xcb_connection_t * +make_connection(const gchar *display) +{ + xcb_connection_t *conn = xcb_connect(display, NULL); + if (xcb_connection_has_error(conn)) + APP_ERR(NULL, "could not connect to X display\n"); + + xcb_prefetch_extension_data(conn, &xcb_xfixes_id); + xcb_prefetch_extension_data(conn, &xcb_selinux_id); + check_xfixes(conn); + check_xselinux(conn); + return conn; +} + +int +fetch_property_data(xcb_connection_t *conn, + xcb_window_t window, xcb_atom_t property, int delete, + xcb_atom_t *type_rtn, unsigned *format_rtn, + void **data_rtn, size_t *len_rtn) +{ + xcb_get_property_cookie_t cookie; + xcb_get_property_reply_t *reply; + xcb_generic_error_t *error; + int bytes = 4096; + size_t size; + char *data; + + cookie = xcb_get_property(conn, delete, window, property, + XCB_GET_PROPERTY_TYPE_ANY, 0, bytes); + reply = xcb_get_property_reply(conn, cookie, &error); + if (error) + APP_ERR(error, "Error while retrieving property!\n"); + + size = (reply->format >> 3) * xcb_get_property_value_length(reply); + + data = malloc(size + 1); + if (!data) + APP_ERR(NULL, "Out of memory"); + + memcpy(data, xcb_get_property_value(reply), size); + data[size] = '\0'; + + *type_rtn = reply->type; + *format_rtn = reply->format; + *data_rtn = data; + *len_rtn = size; + + free(reply); + return 0; +} diff --git a/src/inputmon.c b/src/inputmon.c new file mode 100644 index 0000000..d54add6 --- /dev/null +++ b/src/inputmon.c @@ -0,0 +1,466 @@ +#include +#include +#include + +#include "app.h" + + +static xcb_connection_t *conn; +static GtkTreeStore *store; +xcb_window_t root; + +static void +setup_x(const gchar *display) +{ + const xcb_setup_t *setup; + xcb_screen_iterator_t iter; + uint32_t mask, list; + + conn = make_connection(display); + + setup = xcb_get_setup(conn); + iter = xcb_setup_roots_iterator(setup); + root = iter.data->root; + + atom_init(conn); + + /* Select for root window events */ + mask = XCB_CW_EVENT_MASK; + list = XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE; + xcb_change_window_attributes(conn, root, mask, &list); + + xcb_flush(conn); +} + +static char * +get_prop_text(xcb_get_property_reply_t *reply) +{ + size_t size; + char *data; + + if (!atom_is_string_type(reply->type)) + return NULL; + + size = (reply->format >> 3) * xcb_get_property_value_length(reply); + + data = malloc(size + 1); + if (!data) + APP_ERR(NULL, "Out of memory"); + + memcpy(data, xcb_get_property_value(reply), size); + data[size] = '\0'; + + return scrub_text(data); +} + +static gboolean +find_window(xcb_window_t window, GtkTreeIter *iter) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + xcb_window_t cur; + + if (gtk_tree_model_get_iter_first(tree, iter)) { + do { + gtk_tree_model_get(tree, iter, COL_WIN, &cur, -1); + if (cur == window) + return TRUE; + + } while (gtk_tree_model_iter_next(tree, iter)); + } + + return FALSE; +} + +static void +update_window_name(xcb_window_t window, const char *text) +{ + GtkTreeIter iter; + char buf[128]; + + if (find_window(window, &iter)) { + snprintf(buf, sizeof(buf), "%s (%08x)", text, window); + gtk_tree_store_set(store, &iter, COL_NAME, buf, -1); + } +} + +static void +add_item(GtkTreeIter *parent, GtkTreeIter *sibling, + xcb_window_t window, xcb_atom_t atom, + security_context_t octx, security_context_t dctx, + xcb_atom_t type, const char *data) +{ + GtkTreeIter iter; + char *color; + + color = get_colors(octx, dctx); + + gtk_tree_store_insert_before(store, &iter, parent, sibling); + gtk_tree_store_set(store, &iter, + COL_ICON, GTK_STOCK_FILE, + COL_ATOM, atom, + COL_WIN, window, + COL_DATA, data, + COL_NAME, atom_lookup(conn, atom), + COL_TYPE, atom_lookup(conn, type), + COL_OCTX, octx, + COL_DCTX, dctx, + COL_OFG, color, + COL_OBG, color + 8, + COL_DFG, color + 16, + COL_DBG, color + 24, + -1); + + free(color); +} + +static void +remove_items(xcb_window_t window, xcb_atom_t atom) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + GtkTreeIter iter, child; + xcb_atom_t iatom; + int more; + + if (!find_window(window, &iter)) + return; + + if (gtk_tree_model_iter_children(tree, &child, &iter)) { + do { + gtk_tree_model_get(tree, &child, COL_ATOM, &iatom, -1); + if (atom == iatom) + more = gtk_tree_store_remove(store, &child); + else + more = gtk_tree_model_iter_next(tree, &child); + } while (more); + } +} + +static int +update_item_matches(GtkTreeIter *iter, xcb_atom_t atom, security_context_t ctx) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + xcb_atom_t iatom; + security_context_t ictx; + int result; + + gtk_tree_model_get(tree, iter, COL_ATOM, &iatom, COL_OCTX, &ictx, -1); + result = (atom == iatom) && !strcmp(ctx, ictx); + g_free(ictx); + return result; +} + +static gboolean +update_item_next(xcb_atom_t atom, GtkTreeIter *iter, GtkTreeIter **sibling) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + xcb_atom_t iatom; + const char *name, *iname; + + gtk_tree_model_get(tree, iter, COL_ATOM, &iatom, -1); + name = atom_lookup(conn, atom); + iname = atom_lookup(conn, iatom); + + if (strcasecmp(name, iname) < 0) { + *sibling = iter; + return FALSE; + } + + *sibling = NULL; + return gtk_tree_model_iter_next(tree, iter); +} + +static void +update_item(xcb_window_t window, xcb_atom_t atom, + security_context_t octx, security_context_t dctx, + xcb_atom_t type, const char *data) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + GtkTreeIter iter, child, *sibling = NULL; + char *color; + + if (!find_window(window, &iter)) + return; + + if (gtk_tree_model_iter_children(tree, &child, &iter)) { + do { + if (update_item_matches(&child, atom, octx)) { + color = get_colors(NULL, dctx); + gtk_tree_store_set(store, &child, + COL_DATA, data, + COL_TYPE, atom_lookup(conn, type), + COL_DCTX, dctx, + COL_DFG, color + 16, + COL_DBG, color + 24, + -1); + free(color); + return; + } + } while (update_item_next(atom, &child, &sibling)); + } + + add_item(&iter, sibling, window, atom, octx, dctx, type, data); +} + +static gboolean +update_is_managed(xcb_atom_t atom, xcb_atom_t wildcard) +{ + return (wildcard == XCB_NONE || wildcard == atom); +} + +static void +update(xcb_window_t window, xcb_atom_t atom) +{ + xcb_selinux_list_properties_cookie_t cookie; + xcb_selinux_list_properties_reply_t *reply; + xcb_get_property_cookie_t *cookies; + xcb_get_property_reply_t *propinfo; + xcb_generic_error_t *error; + xcb_selinux_list_item_iterator_t iter; + security_context_t octx, dctx; + char *text; + int i; + + /* remove existing items if updating */ + if (atom != XCB_NONE) + remove_items(window, atom); + + /* list all property instances on the window */ + cookie = xcb_selinux_list_properties(conn, window); + reply = xcb_selinux_list_properties_reply(conn, cookie, &error); + if (error) { + /* window might have gone away */ + if (error->error_code == XCB_WINDOW) { + free(error); + return; + } + APP_ERR(error, "An error was received during ListProperties.\n"); + } + + iter = xcb_selinux_list_properties_properties_iterator(reply); + cookies = malloc(iter.rem * sizeof(*cookies)); + if (!cookies) + APP_ERR(NULL, "Out of memory\n"); + + /* get the property type and text content */ + i = 0; + while (iter.rem) { + if (update_is_managed(iter.data->name, atom)) { + octx = xcb_selinux_list_item_object_context(iter.data); + xcb_selinux_set_property_use_context(conn, strlen(octx) + 1, octx); + cookies[i] = xcb_get_property(conn, FALSE, window, iter.data->name, + XCB_GET_PROPERTY_TYPE_ANY, 0, 1024); + } + xcb_selinux_list_item_next(&iter); + ++i; + } + + /* process the replies */ + i = 0; + iter = xcb_selinux_list_properties_properties_iterator(reply); + while (iter.rem) { + if (update_is_managed(iter.data->name, atom)) { + octx = xcb_selinux_list_item_object_context(iter.data); + dctx = xcb_selinux_list_item_data_context(iter.data); + propinfo = xcb_get_property_reply(conn, cookies[i], &error); + if (error) + APP_ERR(error, "An error was received during GetProperty.\n"); + + text = get_prop_text(propinfo); + update_item(window, iter.data->name, octx, dctx, propinfo->type, text); + + /* special window updates */ + if (iter.data->name == wm_name) + update_window_name(window, text); + + free(text); + free(propinfo); + } + xcb_selinux_list_item_next(&iter); + ++i; + } + + free(reply); + free(cookies); + xcb_flush(conn); +} + +static void +add_window(xcb_window_t window) +{ + xcb_selinux_get_window_context_cookie_t cookie; + xcb_selinux_get_window_context_reply_t *reply; + xcb_generic_error_t *error; + security_context_t octx; + uint32_t mask, list; + GtkTreeIter iter; + char *color, buf[24]; + + /* Get window context */ + cookie = xcb_selinux_get_window_context(conn, window); + reply = xcb_selinux_get_window_context_reply(conn, cookie, &error); + if (error) + APP_ERR(error, "An error was received during GetWindowContext.\n"); + + octx = xcb_selinux_get_window_context_context(reply); + + /* Add window to list */ + color = get_colors(octx, NULL); + if (window == root) + snprintf(buf, sizeof(buf), "root (%08x)", window); + else + snprintf(buf, sizeof(buf), "unnamed (%08x)", window); + + gtk_tree_store_append(store, &iter, NULL); + gtk_tree_store_set(store, &iter, + COL_ICON, GTK_STOCK_DIRECTORY, + COL_WIN, window, + COL_NAME, buf, + COL_OCTX, octx, + COL_OFG, color, + COL_OBG, color + 8, + -1); + + /* Select for window events */ + if (window != root) { + mask = XCB_CW_EVENT_MASK; + list = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE; + xcb_change_window_attributes(conn, window, mask, &list); + } + + update(window, XCB_NONE); + free(color); + free(reply); +} + +static void +add_windows(void) +{ + xcb_query_tree_cookie_t cookie; + xcb_query_tree_reply_t *reply; + xcb_generic_error_t *error; + xcb_window_t *children; + int i; + + cookie = xcb_query_tree(conn, root); + reply = xcb_query_tree_reply(conn, cookie, &error); + if (error) + APP_ERR(error, "An error was received during QueryTree.\n"); + + children = xcb_query_tree_children(reply); + + add_window(root); + for (i = 0; i < reply->children_len; i++) + add_window(children[i]); + + free(reply); +} + +static void +del_window_helper(xcb_window_t window, GtkTreeIter *iter) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + GtkTreeIter child; + xcb_window_t cur; + int more; + + do { + gtk_tree_model_get(tree, iter, COL_WIN, &cur, -1); + if (cur == window) { + if (gtk_tree_model_iter_children(tree, &child, iter)) + del_window_helper(window, &child); + more = gtk_tree_store_remove(store, iter); + } else + more = gtk_tree_model_iter_next(tree, iter); + } while (more); +} + +static void +del_window(xcb_window_t window) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + GtkTreeIter iter; + + if (gtk_tree_model_get_iter_first(tree, &iter)) + del_window_helper(window, &iter); +} + +static void +handle_property(xcb_property_notify_event_t *event) +{ + update(event->window, event->atom); +} + +static void +handle_create(xcb_create_notify_event_t *event) +{ + add_window(event->window); +} + +static void +handle_destroy(xcb_destroy_notify_event_t *event) +{ + del_window(event->window); +} + +static gboolean +handle_event(GIOChannel *source, GIOCondition condition, gpointer data) +{ + xcb_generic_event_t *event; + + while ((event = xcb_poll_for_event(conn))) { + switch (event->response_type) { + case XCB_PROPERTY_NOTIFY: + handle_property((xcb_property_notify_event_t *)event); + break; + + case XCB_CREATE_NOTIFY: + handle_create((xcb_create_notify_event_t *)event); + break; + + case XCB_DESTROY_NOTIFY: + handle_destroy((xcb_destroy_notify_event_t *)event); + break; + + case XCB_MAP_NOTIFY: + case XCB_UNMAP_NOTIFY: + case XCB_UNMAP_NOTIFY | 0x80: + case XCB_CONFIGURE_NOTIFY: + case XCB_CONFIGURE_NOTIFY | 0x80: + case XCB_REPARENT_NOTIFY: + case XCB_MAPPING_NOTIFY: + case XCB_CLIENT_MESSAGE: + case XCB_CLIENT_MESSAGE | 0x80: + /* do nothing */ + break; + + default: + fprintf(stderr, "Monitor: %d event\n", event->response_type); + break; + } + free(event); + xcb_flush(conn); + } + + return TRUE; +} + +int +input_monitor_init(struct thread_data *data) +{ + int fd; + GIOChannel *channel; + + store = GTK_TREE_STORE(data->store); + + setup_x(data->display); + + fd = xcb_get_file_descriptor(conn); + channel = g_io_channel_unix_new(fd); + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_add_watch(channel, G_IO_IN|G_IO_ERR|G_IO_HUP, handle_event, NULL); + + add_windows(); + handle_event(channel, 0, NULL); + + return 0; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..73bf1e2 --- /dev/null +++ b/src/main.c @@ -0,0 +1,360 @@ +#include + +#include "app.h" + +/* + * Main routine for the selection manager. + * Creates the GUI components and launches the monitor and manager. + */ + +static GtkWidget *appwin; +static GtkWidget *expwin; +static GtkListStore *sel_store; +static GtkTreeStore *prop_store; +static unsigned timestamp; + +static gboolean +delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) +{ + return FALSE; +} + +static void +destroy(GtkWidget *widget, gpointer data) +{ + gtk_main_quit(); +} + +static gboolean +event(GtkWidget *widget, GdkEvent *event, gpointer user_data) +{ + timestamp = event->button.time; + return FALSE; +} + +static void +event_register(GObject *obj) +{ + g_signal_connect(obj, "key-press-event", G_CALLBACK(event), NULL); + g_signal_connect(obj, "key-release-event", G_CALLBACK(event), NULL); + g_signal_connect(obj, "button-press-event", G_CALLBACK(event), NULL); + g_signal_connect(obj, "button-release-event", G_CALLBACK(event), NULL); +} + +static void +start(GtkWidget *widget, GtkTreeView *tree) +{ + GtkTreeSelection *sel; + GtkTreeIter iter; + GtkWidget *dialog; + xcb_atom_t atom; + xcb_window_t owner; + gchar *name, *octx; + + sel = gtk_tree_view_get_selection(tree); + + if (gtk_tree_selection_get_selected(sel, NULL, &iter)) { + gtk_tree_model_get(GTK_TREE_MODEL(sel_store), &iter, + COL_ATOM, &atom, COL_WIN, &owner, + COL_NAME, &name, COL_OCTX, &octx, + -1); + + dialog = gtk_message_dialog_new(GTK_WINDOW(appwin), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + "Really make clipboard %s,\n" + "at level %s\n" + "available to all levels?", + name, octx); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES) + expose(atom, owner, octx, timestamp); + + gtk_widget_destroy(dialog); + g_free(name); + g_free(octx); + } + else { + dialog = gtk_message_dialog_new(GTK_WINDOW(appwin), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "No clipboard is currently selected."); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + } +} + +static void +stop(GtkDialog *dialog, gint response_id, gpointer user_data) +{ + gtk_widget_hide_all(expwin); + expose_stop(timestamp); +} + +static GtkWidget * +create_sel_widget(void) +{ + GtkListStore *store; + GtkTreeView *tree; + GtkTreeSelection *sel; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + store = gtk_list_store_new(N_COLS, + G_TYPE_STRING, /* SEL */ + G_TYPE_ULONG, /* ATOM */ + G_TYPE_ULONG, /* WIN */ + G_TYPE_STRING, /* TYPE */ + G_TYPE_STRING, /* DATA */ + G_TYPE_STRING, /* OCTX */ + G_TYPE_STRING, /* OFG */ + G_TYPE_STRING, /* OBG */ + G_TYPE_STRING, /* DCTX */ + G_TYPE_STRING, /* DFG */ + G_TYPE_STRING, /* DBG */ + G_TYPE_STRING /* ICON */ + ); + + tree = GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(store))); + sel = gtk_tree_view_get_selection(tree); + gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE); + + renderer = gtk_cell_renderer_pixbuf_new(); + column = gtk_tree_view_column_new_with_attributes( + "", renderer, + "stock-id", COL_ICON, + NULL); + gtk_tree_view_append_column(tree, column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes( + "Selection", renderer, + "text", COL_NAME, + NULL); + gtk_tree_view_append_column(tree, column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes( + "Level", renderer, + "text", COL_DCTX, + "foreground", COL_DFG, + "background", COL_DBG, + NULL); + gtk_tree_view_append_column(tree, column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes( + "Content", renderer, + "text", COL_DATA, + NULL); + gtk_tree_view_append_column(tree, column); + + sel_store = store; + return GTK_WIDGET(tree); +} + +static GtkWidget * +create_prop_widget(void) +{ + GtkTreeStore *store; + GtkTreeView *tree; + GtkTreeSelection *sel; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + store = gtk_tree_store_new(N_COLS, + G_TYPE_STRING, /* SEL */ + G_TYPE_ULONG, /* ATOM */ + G_TYPE_ULONG, /* WIN */ + G_TYPE_STRING, /* TYPE */ + G_TYPE_STRING, /* DATA */ + G_TYPE_STRING, /* OCTX */ + G_TYPE_STRING, /* OFG */ + G_TYPE_STRING, /* OBG */ + G_TYPE_STRING, /* DCTX */ + G_TYPE_STRING, /* DFG */ + G_TYPE_STRING, /* DBG */ + G_TYPE_STRING /* ICON */ + ); + + tree = GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(store))); + sel = gtk_tree_view_get_selection(tree); + gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE); + + renderer = gtk_cell_renderer_pixbuf_new(); + column = gtk_tree_view_column_new_with_attributes( + "", renderer, + "stock-id", COL_ICON, + NULL); + gtk_tree_view_append_column(tree, column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes( + "Object", renderer, + "text", COL_NAME, + NULL); + gtk_tree_view_append_column(tree, column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes( + "Level (Object)", renderer, + "text", COL_OCTX, + "foreground", COL_OFG, + "background", COL_OBG, + NULL); + gtk_tree_view_append_column(tree, column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes( + "Level (Data)", renderer, + "text", COL_OCTX, + "foreground", COL_OFG, + "background", COL_OBG, + NULL); + gtk_tree_view_append_column(tree, column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes( + "Type", renderer, + "text", COL_TYPE, + NULL); + gtk_tree_view_append_column(tree, column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes( + "Text Content", renderer, + "text", COL_DATA, + NULL); + gtk_tree_view_append_column(tree, column); + + prop_store = store; + return GTK_WIDGET(tree); +} + +static void +create_dialogs(void) +{ + GtkWidget *item, *view, *scroll, *box; + GtkTextBuffer *buffer; + + /* Expose window */ + expwin = gtk_dialog_new_with_buttons("Expose Operation", + GTK_WINDOW(appwin), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + NULL); + + item = gtk_dialog_add_button(GTK_DIALOG(expwin), GTK_STOCK_STOP, GTK_RESPONSE_REJECT); + event_register(G_OBJECT(item)); + + gtk_window_set_default_size(GTK_WINDOW(expwin), 800, 480); + + g_signal_connect(G_OBJECT(expwin), "delete_event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); + + view = gtk_text_view_new (); + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); + g_object_set_data(G_OBJECT(expwin), "textbuf", buffer); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_container_add(GTK_CONTAINER(scroll), view); + + box = gtk_dialog_get_content_area(GTK_DIALOG(expwin)); + gtk_box_pack_start(GTK_BOX(box), scroll, TRUE, TRUE, 0); + + g_signal_connect(G_OBJECT(expwin), "response", G_CALLBACK(stop), NULL); + +} + +static GtkWidget * +create_menu(void) +{ + GtkWidget *bar, *menu, *item; + + /* File menu */ + bar = gtk_menu_bar_new(); + + menu = gtk_menu_new(); + item = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL); + g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(destroy), NULL); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + + item = gtk_menu_item_new_with_mnemonic("_File"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu); + gtk_menu_shell_append(GTK_MENU_SHELL(bar), item); + + /* Action menu */ +/* + menu = gtk_menu_new(); + item = gtk_image_menu_item_new_from_stock(GTK_STOCK_COPY, NULL); + event_register(G_OBJECT(item)); + g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(start), selw); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + + item = gtk_menu_item_new_with_mnemonic("_Action"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu); + gtk_menu_shell_append(GTK_MENU_SHELL(bar), item); +*/ + + return bar; +} + +static void +create_gui(void) +{ + GtkWidget *selw = create_sel_widget(); + GtkWidget *propw = create_prop_widget(); + GtkWidget *menubar = create_menu(); + GtkWidget *scroll, *box, *tabs; + + /* App window */ + appwin = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size(GTK_WINDOW(appwin), 800, 480); + + g_signal_connect(G_OBJECT(appwin), "delete_event", G_CALLBACK(delete_event), NULL); + g_signal_connect(G_OBJECT(appwin), "destroy", G_CALLBACK(destroy), NULL); + + tabs = gtk_notebook_new(); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_container_add(GTK_CONTAINER(scroll), selw); + gtk_notebook_append_page(GTK_NOTEBOOK(tabs), scroll, gtk_label_new("Selections")); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_container_add(GTK_CONTAINER(scroll), propw); + gtk_notebook_append_page(GTK_NOTEBOOK(tabs), scroll, gtk_label_new("Properties")); + + box = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), menubar, FALSE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(box), tabs, TRUE, TRUE, 0); + gtk_container_add(GTK_CONTAINER(appwin), box); + + create_dialogs(); +} + +int +main(int argc, char **argv) +{ + struct thread_data data; + + gtk_init(&argc, &argv); + + create_gui(); + + data.display = gdk_display_get_name(gdk_display_get_default()); + data.store = GTK_TREE_MODEL(sel_store); + data.appwin = appwin; + data.expwin = expwin; + + sel_manager_init(&data); + sel_monitor_init(&data); + + data.store = GTK_TREE_MODEL(prop_store); + + prop_monitor_init(&data); + + gtk_widget_show_all(appwin); + gtk_main(); + + exit(0); +} diff --git a/src/propmon.c b/src/propmon.c new file mode 100644 index 0000000..96182db --- /dev/null +++ b/src/propmon.c @@ -0,0 +1,466 @@ +#include +#include +#include + +#include "app.h" + + +static xcb_connection_t *conn; +static GtkTreeStore *store; +xcb_window_t root; + +static void +setup_x(const gchar *display) +{ + const xcb_setup_t *setup; + xcb_screen_iterator_t iter; + uint32_t mask, list; + + conn = make_connection(display); + + setup = xcb_get_setup(conn); + iter = xcb_setup_roots_iterator(setup); + root = iter.data->root; + + atom_init(conn); + + /* Select for root window events */ + mask = XCB_CW_EVENT_MASK; + list = XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE; + xcb_change_window_attributes(conn, root, mask, &list); + + xcb_flush(conn); +} + +static char * +get_prop_text(xcb_get_property_reply_t *reply) +{ + size_t size; + char *data; + + if (!atom_is_string_type(reply->type)) + return NULL; + + size = (reply->format >> 3) * xcb_get_property_value_length(reply); + + data = malloc(size + 1); + if (!data) + APP_ERR(NULL, "Out of memory"); + + memcpy(data, xcb_get_property_value(reply), size); + data[size] = '\0'; + + return scrub_text(data); +} + +static gboolean +find_window(xcb_window_t window, GtkTreeIter *iter) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + xcb_window_t cur; + + if (gtk_tree_model_get_iter_first(tree, iter)) { + do { + gtk_tree_model_get(tree, iter, COL_WIN, &cur, -1); + if (cur == window) + return TRUE; + + } while (gtk_tree_model_iter_next(tree, iter)); + } + + return FALSE; +} + +static void +update_window_name(xcb_window_t window, const char *text) +{ + GtkTreeIter iter; + char buf[128]; + + if (find_window(window, &iter)) { + snprintf(buf, sizeof(buf), "%s (%08x)", text, window); + gtk_tree_store_set(store, &iter, COL_NAME, buf, -1); + } +} + +static void +add_item(GtkTreeIter *parent, GtkTreeIter *sibling, + xcb_window_t window, xcb_atom_t atom, + security_context_t octx, security_context_t dctx, + xcb_atom_t type, const char *data) +{ + GtkTreeIter iter; + char *color; + + color = get_colors(octx, dctx); + + gtk_tree_store_insert_before(store, &iter, parent, sibling); + gtk_tree_store_set(store, &iter, + COL_ICON, GTK_STOCK_FILE, + COL_ATOM, atom, + COL_WIN, window, + COL_DATA, data, + COL_NAME, atom_lookup(conn, atom), + COL_TYPE, atom_lookup(conn, type), + COL_OCTX, octx, + COL_DCTX, dctx, + COL_OFG, color, + COL_OBG, color + 8, + COL_DFG, color + 16, + COL_DBG, color + 24, + -1); + + free(color); +} + +static void +remove_items(xcb_window_t window, xcb_atom_t atom) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + GtkTreeIter iter, child; + xcb_atom_t iatom; + int more; + + if (!find_window(window, &iter)) + return; + + if (gtk_tree_model_iter_children(tree, &child, &iter)) { + do { + gtk_tree_model_get(tree, &child, COL_ATOM, &iatom, -1); + if (atom == iatom) + more = gtk_tree_store_remove(store, &child); + else + more = gtk_tree_model_iter_next(tree, &child); + } while (more); + } +} + +static int +update_item_matches(GtkTreeIter *iter, xcb_atom_t atom, security_context_t ctx) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + xcb_atom_t iatom; + security_context_t ictx; + int result; + + gtk_tree_model_get(tree, iter, COL_ATOM, &iatom, COL_OCTX, &ictx, -1); + result = (atom == iatom) && !strcmp(ctx, ictx); + g_free(ictx); + return result; +} + +static gboolean +update_item_next(xcb_atom_t atom, GtkTreeIter *iter, GtkTreeIter **sibling) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + xcb_atom_t iatom; + const char *name, *iname; + + gtk_tree_model_get(tree, iter, COL_ATOM, &iatom, -1); + name = atom_lookup(conn, atom); + iname = atom_lookup(conn, iatom); + + if (strcasecmp(name, iname) < 0) { + *sibling = iter; + return FALSE; + } + + *sibling = NULL; + return gtk_tree_model_iter_next(tree, iter); +} + +static void +update_item(xcb_window_t window, xcb_atom_t atom, + security_context_t octx, security_context_t dctx, + xcb_atom_t type, const char *data) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + GtkTreeIter iter, child, *sibling = NULL; + char *color; + + if (!find_window(window, &iter)) + return; + + if (gtk_tree_model_iter_children(tree, &child, &iter)) { + do { + if (update_item_matches(&child, atom, octx)) { + color = get_colors(NULL, dctx); + gtk_tree_store_set(store, &child, + COL_DATA, data, + COL_TYPE, atom_lookup(conn, type), + COL_DCTX, dctx, + COL_DFG, color + 16, + COL_DBG, color + 24, + -1); + free(color); + return; + } + } while (update_item_next(atom, &child, &sibling)); + } + + add_item(&iter, sibling, window, atom, octx, dctx, type, data); +} + +static gboolean +update_is_managed(xcb_atom_t atom, xcb_atom_t wildcard) +{ + return (wildcard == XCB_NONE || wildcard == atom); +} + +static void +update(xcb_window_t window, xcb_atom_t atom) +{ + xcb_selinux_list_properties_cookie_t cookie; + xcb_selinux_list_properties_reply_t *reply; + xcb_get_property_cookie_t *cookies; + xcb_get_property_reply_t *propinfo; + xcb_generic_error_t *error; + xcb_selinux_list_item_iterator_t iter; + security_context_t octx, dctx; + char *text; + int i; + + /* remove existing items if updating */ + if (atom != XCB_NONE) + remove_items(window, atom); + + /* list all property instances on the window */ + cookie = xcb_selinux_list_properties(conn, window); + reply = xcb_selinux_list_properties_reply(conn, cookie, &error); + if (error) { + /* window might have gone away */ + if (error->error_code == XCB_WINDOW) { + free(error); + return; + } + APP_ERR(error, "An error was received during ListProperties.\n"); + } + + iter = xcb_selinux_list_properties_properties_iterator(reply); + cookies = malloc(iter.rem * sizeof(*cookies)); + if (!cookies) + APP_ERR(NULL, "Out of memory\n"); + + /* get the property type and text content */ + i = 0; + while (iter.rem) { + if (update_is_managed(iter.data->name, atom)) { + octx = xcb_selinux_list_item_object_context(iter.data); + xcb_selinux_set_property_use_context(conn, strlen(octx) + 1, octx); + cookies[i] = xcb_get_property(conn, FALSE, window, iter.data->name, + XCB_GET_PROPERTY_TYPE_ANY, 0, 1024); + } + xcb_selinux_list_item_next(&iter); + ++i; + } + + /* process the replies */ + i = 0; + iter = xcb_selinux_list_properties_properties_iterator(reply); + while (iter.rem) { + if (update_is_managed(iter.data->name, atom)) { + octx = xcb_selinux_list_item_object_context(iter.data); + dctx = xcb_selinux_list_item_data_context(iter.data); + propinfo = xcb_get_property_reply(conn, cookies[i], &error); + if (error) + APP_ERR(error, "An error was received during GetProperty.\n"); + + text = get_prop_text(propinfo); + update_item(window, iter.data->name, octx, dctx, propinfo->type, text); + + /* special window updates */ + if (iter.data->name == wm_name) + update_window_name(window, text); + + free(text); + free(propinfo); + } + xcb_selinux_list_item_next(&iter); + ++i; + } + + free(reply); + free(cookies); + xcb_flush(conn); +} + +static void +add_window(xcb_window_t window) +{ + xcb_selinux_get_window_context_cookie_t cookie; + xcb_selinux_get_window_context_reply_t *reply; + xcb_generic_error_t *error; + security_context_t octx; + uint32_t mask, list; + GtkTreeIter iter; + char *color, buf[24]; + + /* Get window context */ + cookie = xcb_selinux_get_window_context(conn, window); + reply = xcb_selinux_get_window_context_reply(conn, cookie, &error); + if (error) + APP_ERR(error, "An error was received during GetWindowContext.\n"); + + octx = xcb_selinux_get_window_context_context(reply); + + /* Add window to list */ + color = get_colors(octx, NULL); + if (window == root) + snprintf(buf, sizeof(buf), "root (%08x)", window); + else + snprintf(buf, sizeof(buf), "unnamed (%08x)", window); + + gtk_tree_store_append(store, &iter, NULL); + gtk_tree_store_set(store, &iter, + COL_ICON, GTK_STOCK_DIRECTORY, + COL_WIN, window, + COL_NAME, buf, + COL_OCTX, octx, + COL_OFG, color, + COL_OBG, color + 8, + -1); + + /* Select for window events */ + if (window != root) { + mask = XCB_CW_EVENT_MASK; + list = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE; + xcb_change_window_attributes(conn, window, mask, &list); + } + + update(window, XCB_NONE); + free(color); + free(reply); +} + +static void +add_windows(void) +{ + xcb_query_tree_cookie_t cookie; + xcb_query_tree_reply_t *reply; + xcb_generic_error_t *error; + xcb_window_t *children; + int i; + + cookie = xcb_query_tree(conn, root); + reply = xcb_query_tree_reply(conn, cookie, &error); + if (error) + APP_ERR(error, "An error was received during QueryTree.\n"); + + children = xcb_query_tree_children(reply); + + add_window(root); + for (i = 0; i < reply->children_len; i++) + add_window(children[i]); + + free(reply); +} + +static void +del_window_helper(xcb_window_t window, GtkTreeIter *iter) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + GtkTreeIter child; + xcb_window_t cur; + int more; + + do { + gtk_tree_model_get(tree, iter, COL_WIN, &cur, -1); + if (cur == window) { + if (gtk_tree_model_iter_children(tree, &child, iter)) + del_window_helper(window, &child); + more = gtk_tree_store_remove(store, iter); + } else + more = gtk_tree_model_iter_next(tree, iter); + } while (more); +} + +static void +del_window(xcb_window_t window) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + GtkTreeIter iter; + + if (gtk_tree_model_get_iter_first(tree, &iter)) + del_window_helper(window, &iter); +} + +static void +handle_property(xcb_property_notify_event_t *event) +{ + update(event->window, event->atom); +} + +static void +handle_create(xcb_create_notify_event_t *event) +{ + add_window(event->window); +} + +static void +handle_destroy(xcb_destroy_notify_event_t *event) +{ + del_window(event->window); +} + +static gboolean +handle_event(GIOChannel *source, GIOCondition condition, gpointer data) +{ + xcb_generic_event_t *event; + + while ((event = xcb_poll_for_event(conn))) { + switch (event->response_type) { + case XCB_PROPERTY_NOTIFY: + handle_property((xcb_property_notify_event_t *)event); + break; + + case XCB_CREATE_NOTIFY: + handle_create((xcb_create_notify_event_t *)event); + break; + + case XCB_DESTROY_NOTIFY: + handle_destroy((xcb_destroy_notify_event_t *)event); + break; + + case XCB_MAP_NOTIFY: + case XCB_UNMAP_NOTIFY: + case XCB_UNMAP_NOTIFY | 0x80: + case XCB_CONFIGURE_NOTIFY: + case XCB_CONFIGURE_NOTIFY | 0x80: + case XCB_REPARENT_NOTIFY: + case XCB_MAPPING_NOTIFY: + case XCB_CLIENT_MESSAGE: + case XCB_CLIENT_MESSAGE | 0x80: + /* do nothing */ + break; + + default: + fprintf(stderr, "Monitor: %d event\n", event->response_type); + break; + } + free(event); + xcb_flush(conn); + } + + return TRUE; +} + +int +prop_monitor_init(struct thread_data *data) +{ + int fd; + GIOChannel *channel; + + store = GTK_TREE_STORE(data->store); + + setup_x(data->display); + + fd = xcb_get_file_descriptor(conn); + channel = g_io_channel_unix_new(fd); + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_add_watch(channel, G_IO_IN|G_IO_ERR|G_IO_HUP, handle_event, NULL); + + add_windows(); + handle_event(channel, 0, NULL); + + return 0; +} diff --git a/src/selmgr.c b/src/selmgr.c new file mode 100644 index 0000000..5908f5c --- /dev/null +++ b/src/selmgr.c @@ -0,0 +1,481 @@ +#include +#include +#include + +#include "app.h" + +/* + * The manager connection for the selection manager. + * Responds to requests by the user to expose selections across levels. + * Handles taking ownership of selections, passing data, and updating + * the "Exposure in Progress" dialog box. + */ + +static xcb_connection_t *conn; +static xcb_window_t window; +static uint8_t xfixes_type; +static GtkWidget *appwin; +static GtkWidget *expwin; +static GtkListStore *store; +static GtkTextBuffer *buffer; +static GHashTable *transfers; +static GSList *ownership; + +static char expose_in_progress; +static security_context_t cur_level; +static xcb_atom_t cur_name; +static const char *cur_string; +static xcb_window_t cur_owner; + +struct transfer { + xcb_atom_t name; + xcb_atom_t target; + xcb_atom_t property; + xcb_window_t requestor; + xcb_window_t owner; + security_context_t level; +}; + + +static void +free_transfer(void *p) +{ + struct transfer *ptr = p; + freecon(ptr->level); + free(ptr); +} + +static void +setup_x(const gchar *display) +{ + const xcb_setup_t *setup; + xcb_screen_iterator_t iter; + xcb_window_t root; + + conn = make_connection(display); + xfixes_type = xcb_get_extension_data(conn, &xcb_xfixes_id)->first_event; + + setup = xcb_get_setup(conn); + iter = xcb_setup_roots_iterator(setup); + root = iter.data->root; + + window = xcb_generate_id(conn); + xcb_create_window(conn, 0, window, root, 0, 0, 100, 100, 0, + XCB_WINDOW_CLASS_INPUT_ONLY, 0, 0, NULL); + + atom_init(conn); + selection_register(conn, window); + + xcb_flush(conn); +} + +void +expose_stop(xcb_timestamp_t time) +{ + security_context_t octx; + GSList *elt; + + if (expose_in_progress) { + free(cur_level); + cur_level = NULL; + expose_in_progress = FALSE; + + for (elt = ownership; elt; elt = g_slist_next(elt)) { + octx = elt->data; + xcb_selinux_set_selection_use_context(conn, strlen(octx) + 1, octx); + xcb_set_selection_owner(conn, XCB_NONE, cur_name, time); + } + } + + xcb_flush(conn); +} + +static void +expose_stop_msg(xcb_timestamp_t time, const char *msg) +{ + GtkWidget *dialog; + + expose_stop(time); + gtk_dialog_response(GTK_DIALOG(expwin), GTK_RESPONSE_REJECT); + + dialog = gtk_message_dialog_new(GTK_WINDOW(appwin), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + msg); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +} + +static int +expose_item_matches(GtkTreeIter *iter, xcb_atom_t name, security_context_t ctx) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + xcb_atom_t iname; + security_context_t ictx; + int result; + + gtk_tree_model_get(tree, iter, COL_ATOM, &iname, COL_OCTX, &ictx, -1); + result = (name == iname) && strcmp(ctx, ictx); + g_free(ictx); + return result; +} + +void +expose(xcb_atom_t name, xcb_window_t owner, security_context_t level, + xcb_timestamp_t time) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + GtkTreeIter iter; + GtkWidget *dialog; + GtkTextIter text; + security_context_t octx; + char msg[256]; + GSList *elt; + int i = 0; + + cur_string = selection_lookup(conn, window, name); + snprintf(msg, sizeof(msg), "Starting expose operation for %s at %s\n", cur_string, level); + gtk_text_buffer_set_text(buffer, msg, -1); + + for (elt = ownership; elt; elt = g_slist_next(elt)) + free(elt->data); + g_slist_free(ownership); + ownership = NULL; + + if (gtk_tree_model_get_iter_first(tree, &iter)) { + do { + if (expose_item_matches(&iter, name, level)) { + gtk_tree_model_get(tree, &iter, COL_OCTX, &octx, -1); + xcb_selinux_set_selection_use_context(conn, strlen(octx) + 1, octx); + xcb_set_selection_owner(conn, window, name, time); + ownership = g_slist_append(ownership, strdup(octx)); + + snprintf(msg, sizeof(msg), "Took control of %s at %s\n", cur_string, octx); + gtk_text_buffer_get_end_iter(buffer, &text); + gtk_text_buffer_insert(buffer, &text, msg, -1); + g_free(octx); + i++; + } + } while (gtk_tree_model_iter_next(tree, &iter)); + } + + if (i > 0) { + free(cur_level); + cur_level = strdup(level); + cur_name = name; + cur_owner = owner; + expose_in_progress = TRUE; + gtk_widget_show_all(expwin); + } else { + dialog = gtk_message_dialog_new(GTK_WINDOW(appwin), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "There are no other instances of %s.\n", + cur_string); + + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + } + + xcb_flush(conn); +} + +static xcb_window_t +make_window_at_level(security_context_t level) +{ + xcb_window_t result; + + xcb_selinux_set_window_create_context(conn, strlen(level) + 1, level); + + result = xcb_generate_id(conn); + xcb_create_window(conn, 0, result, window, 0, 0, 100, 100, 0, + XCB_WINDOW_CLASS_INPUT_ONLY, 0, 0, NULL); + + return result; +} + +static void +send_convert_request(xcb_atom_t name, xcb_atom_t target, xcb_timestamp_t time, + struct transfer *transfer) +{ + xcb_selinux_get_window_context_cookie_t cookie; + xcb_selinux_get_window_context_reply_t *reply; + xcb_generic_error_t *error; + xcb_window_t requestor; + security_context_t wctx; + + cookie = xcb_selinux_get_window_context(conn, cur_owner); + reply = xcb_selinux_get_window_context_reply(conn, cookie, &error); + if (error) + APP_ERR(error, "An error was received during GetWindowContext.\n"); + + wctx = xcb_selinux_get_window_context_context(reply); + requestor = make_window_at_level(wctx); + + g_hash_table_insert(transfers, (void *)requestor, transfer); + + xcb_selinux_set_selection_use_context(conn, strlen(cur_level) + 1, cur_level); + xcb_convert_selection(conn, requestor, cur_name, target, xa_prop, time); + + free(reply); +} + +static gboolean +update_check(xcb_atom_t name) +{ + xcb_get_selection_owner_cookie_t cookie; + xcb_get_selection_owner_reply_t *reply; + xcb_generic_error_t *error; + xcb_window_t owner; + + /* check if the current owner is still the selection owner, and that + the current level is still the current owner's context */ + xcb_selinux_set_selection_use_context(conn, strlen(cur_level) + 1, cur_level); + cookie = xcb_get_selection_owner(conn, name); + reply = xcb_get_selection_owner_reply(conn, cookie, &error); + if (error) + APP_ERR(error, "An error was received during GetSelectionOwner.\n"); + owner = reply->owner; + free(reply); + + return (owner == cur_owner); +} + +static gboolean +update(xcb_atom_t selection, xcb_timestamp_t time) +{ + security_context_t octx; + xcb_get_selection_owner_cookie_t *cookies; + xcb_get_selection_owner_reply_t *owner; + xcb_generic_error_t *error; + GSList *elt, *newlist = NULL; + guint i, len; + GtkTextIter text; + char msg[256]; + + if (!expose_in_progress || selection != cur_name) + return FALSE; + + if (!update_check(selection)) { + snprintf(msg, sizeof(msg), + "%s changed owners at the exposed level.\n" + "Canceling expose operation.", cur_string); + expose_stop_msg(time, msg); + return FALSE; + } + + len = g_slist_length(ownership); + cookies = malloc(len * sizeof(*cookies)); + if (!cookies) + APP_ERR(NULL, "Out of memory\n"); + + for (elt = ownership, i = 0; elt; elt = g_slist_next(elt), i++) { + octx = elt->data; + xcb_selinux_set_selection_use_context(conn, strlen(octx) + 1, octx); + cookies[i] = xcb_get_selection_owner(conn, cur_name); + } + + for (elt = ownership, i = 0; elt; elt = g_slist_next(elt), i++) { + octx = elt->data; + owner = xcb_get_selection_owner_reply(conn, cookies[i], &error); + if (error) + APP_ERR(error, "An error was received during GetSelectionOwner.\n"); + if (owner->owner != window) { + snprintf(msg, sizeof(msg), "Lost ownership of %s at %s\n", cur_string, octx); + gtk_text_buffer_get_end_iter(buffer, &text); + gtk_text_buffer_insert(buffer, &text, msg, -1); + free(octx); + } else { + newlist = g_slist_append(newlist, octx); + } + } + + g_slist_free(ownership); + ownership = newlist; + if (!ownership) { + snprintf(msg, sizeof(msg), + "Selection manager lost ownership of %s.\n" + "Canceling expose operation.", cur_string); + expose_stop_msg(time, msg); + return FALSE; + } + + return TRUE; +} + +static void +handle_request(xcb_selection_request_event_t *event) +{ + struct transfer *transfer; + xcb_selection_notify_event_t response; + + /* do a complete update to avoid race conditions */ + if (!update(event->selection, event->time)) { + response.response_type = XCB_SELECTION_NOTIFY; + response.time = event->time; + response.requestor = event->requestor; + response.selection = event->selection; + response.target = event->target; + response.property = XCB_NONE; + + xcb_send_event(conn, FALSE, event->requestor, 0, (const char *)&response); + return; + } + + /* check if the request is allowed by policy */ + if (event->target != xa_targets) { + ; /* security check here */ + } + + transfer = malloc(sizeof(struct transfer)); + if (!transfer) + APP_ERR(NULL, "Out of memory\n"); + + transfer->name = event->selection; + transfer->target = event->target; + transfer->property = event->property; + transfer->owner = event->owner; + transfer->requestor = event->requestor; + transfer->level = strdup(cur_level); + + send_convert_request(event->selection, event->target, event->time, transfer); +} + +static void +handle_notify(xcb_selection_notify_event_t *event) +{ + xcb_selinux_get_client_context_cookie_t cookie; + xcb_selinux_get_client_context_reply_t *reply; + xcb_generic_error_t *error; + security_context_t cctx; + xcb_atom_t type; + unsigned format; + void *data; + size_t len; + struct transfer *transfer; + xcb_window_t tmpwin = event->requestor; + GtkTextIter text; + char msg[256]; + + transfer = g_hash_table_lookup(transfers, (void *)event->requestor); + if (!transfer) + return; + + if (event->property != XCB_NONE) { + fetch_property_data(conn, event->requestor, event->property, FALSE, + &type, &format, &data, &len); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, transfer->requestor, + transfer->property, type, format, len, data); + free(data); + } + + event->response_type &= 0x7f; + event->requestor = transfer->requestor; + xcb_send_event(conn, FALSE, transfer->requestor, 0, (const char *)event); + + g_hash_table_remove(transfers, (void *)tmpwin); + xcb_destroy_window(conn, tmpwin); + + cookie = xcb_selinux_get_client_context(conn, transfer->requestor); + reply = xcb_selinux_get_client_context_reply(conn, cookie, &error); + if (error) + APP_ERR(error, "An error was received during GetClientContext.\n"); + + cctx = xcb_selinux_get_client_context_context(reply); + snprintf(msg, sizeof(msg), + "Transferred content to domain %s\n", cctx); + gtk_text_buffer_get_end_iter(buffer, &text); + gtk_text_buffer_insert(buffer, &text, msg, -1); + free(reply); +} + +static void +handle_clear(xcb_selection_clear_event_t *event) +{ + update(event->selection, event->time); +} + +static void +handle_xfixes(xcb_xfixes_selection_notify_event_t *event) +{ + update(event->selection, event->timestamp); +} + +static void +handle_client(xcb_client_message_event_t *event) +{ + fprintf(stderr, "Manager: got a %s ClientMessage\n", + atom_lookup(conn, event->type)); +} + +static gboolean +handle_event(GIOChannel *source, GIOCondition condition, gpointer data) +{ + xcb_generic_event_t *event; + + while ((event = xcb_poll_for_event(conn))) { + switch (event->response_type) { + case XCB_MAPPING_NOTIFY: + /* do nothing */ + break; + + case XCB_SELECTION_CLEAR: + handle_clear((xcb_selection_clear_event_t *)event); + break; + + case XCB_SELECTION_REQUEST: + handle_request((xcb_selection_request_event_t *)event); + break; + + case XCB_SELECTION_NOTIFY: + case XCB_SELECTION_NOTIFY | 0x80: + handle_notify((xcb_selection_notify_event_t *)event); + break; + + case XCB_CLIENT_MESSAGE: + case XCB_CLIENT_MESSAGE | 0x80: + handle_client((xcb_client_message_event_t *)event); + break; + + case 0: + APP_ERR((xcb_generic_error_t *)event, "Manager: Error in main loop!\n"); + + default: + if (event->response_type == xfixes_type) + handle_xfixes((xcb_xfixes_selection_notify_event_t *)event); + else + fprintf(stderr, "Manager: %d event\n", event->response_type); + break; + break; + } + free(event); + xcb_flush(conn); + } + + return TRUE; +} + +int +sel_manager_init(struct thread_data *data) +{ + int fd; + GIOChannel *channel; + + store = GTK_LIST_STORE(data->store); + appwin = data->appwin; + expwin = data->expwin; + buffer = g_object_get_data(G_OBJECT(expwin), "textbuf"); + + transfers = g_hash_table_new_full(NULL, NULL, NULL, free_transfer); + + setup_x(data->display); + + fd = xcb_get_file_descriptor(conn); + channel = g_io_channel_unix_new(fd); + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_add_watch(channel, G_IO_IN|G_IO_ERR|G_IO_HUP, handle_event, NULL); + + return 0; +} diff --git a/src/selmon.c b/src/selmon.c new file mode 100644 index 0000000..9f4b1b5 --- /dev/null +++ b/src/selmon.c @@ -0,0 +1,397 @@ +#include +#include +#include + +#include "app.h" + +/* + * The monitor connection for the selection manager. + * Keeps track of all current selections and their owners, + * updating the ListStore as appropriate. + */ + + +static xcb_connection_t *conn; +static xcb_window_t window; +static uint8_t xfixes_type; +static GtkListStore *store; + +static GHashTable *transfers; + +struct transfer { + xcb_window_t owner; + security_context_t octx; + security_context_t dctx; +}; + + +static void +free_transfer(void *p) +{ + struct transfer *ptr = p; + freecon(ptr->octx); + freecon(ptr->dctx); + free(ptr); +} + +static void +setup_x(const gchar *display) +{ + const xcb_setup_t *setup; + xcb_screen_iterator_t iter; + xcb_window_t root; + + conn = make_connection(display); + xfixes_type = xcb_get_extension_data(conn, &xcb_xfixes_id)->first_event; + + setup = xcb_get_setup(conn); + iter = xcb_setup_roots_iterator(setup); + root = iter.data->root; + + window = xcb_generate_id(conn); + xcb_create_window(conn, 0, window, root, 0, 0, 100, 100, 0, + XCB_WINDOW_CLASS_INPUT_ONLY, 0, 0, NULL); + + atom_init(conn); + selection_register(conn, window); + + xcb_flush(conn); +} + +static xcb_window_t +make_window_at_level(security_context_t level) +{ + xcb_window_t result; + + xcb_selinux_set_window_create_context(conn, strlen(level) + 1, level); + + result = xcb_generate_id(conn); + xcb_create_window(conn, 0, result, window, 0, 0, 100, 100, 0, + XCB_WINDOW_CLASS_INPUT_ONLY, 0, 0, NULL); + + return result; +} + +static void +send_convert_request(xcb_selinux_list_item_t *item, xcb_window_t owner, + xcb_atom_t target, xcb_timestamp_t time, struct transfer *transfer) +{ + xcb_selinux_get_window_context_cookie_t cookie; + xcb_selinux_get_window_context_reply_t *reply; + xcb_generic_error_t *error; + xcb_window_t requestor; + security_context_t wctx, octx, dctx; + + octx = xcb_selinux_list_item_object_context(item); + dctx = xcb_selinux_list_item_data_context(item); + + cookie = xcb_selinux_get_window_context(conn, owner); + reply = xcb_selinux_get_window_context_reply(conn, cookie, &error); + if (error) + APP_ERR(error, "An error was received during GetWindowContext.\n"); + + wctx = xcb_selinux_get_window_context_context(reply); + requestor = make_window_at_level(wctx); + + g_hash_table_insert(transfers, (void *)requestor, transfer); + + xcb_selinux_set_selection_use_context(conn, strlen(octx) + 1, octx); + xcb_convert_selection(conn, requestor, item->name, target, xa_prop, time); + + free(reply); +} + +static void +add_item(GtkTreeIter *sibling, + xcb_atom_t atom, xcb_window_t owner, + security_context_t octx, security_context_t dctx, + const char *data) +{ + GtkTreeIter iter; + const char *name; + char *color; + + name = selection_lookup(conn, window, atom); + if (!name) + return; + + color = get_colors(octx, dctx); + + gtk_list_store_insert_before(store, &iter, sibling); + gtk_list_store_set(store, &iter, + COL_ICON, GTK_STOCK_FILE, + COL_ATOM, atom, + COL_WIN, owner, + COL_DATA, data, + COL_NAME, name, + COL_OCTX, octx, + COL_DCTX, dctx, + COL_OFG, color, + COL_OBG, color + 8, + COL_DFG, color + 16, + COL_DBG, color + 24, + -1); + + free(color); +} + +static void +remove_items(xcb_atom_t atom) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + GtkTreeIter iter; + xcb_atom_t iatom; + int more; + + if (gtk_tree_model_get_iter_first(tree, &iter)) { + do { + gtk_tree_model_get(tree, &iter, COL_ATOM, &iatom, -1); + if (atom == iatom) + more = gtk_list_store_remove(store, &iter); + else + more = gtk_tree_model_iter_next(tree, &iter); + } while (more); + } +} + +static int +update_item_matches(GtkTreeIter *iter, xcb_atom_t name, security_context_t ctx) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + xcb_atom_t iname; + security_context_t ictx; + int result; + + gtk_tree_model_get(tree, iter, COL_ATOM, &iname, COL_OCTX, &ictx, -1); + result = (name == iname) && !strcmp(ctx, ictx); + g_free(ictx); + return result; +} + +static gboolean +update_item_next(xcb_atom_t atom, GtkTreeIter *iter, GtkTreeIter **sibling) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + xcb_atom_t iatom; + const char *name, *iname; + + gtk_tree_model_get(tree, iter, COL_ATOM, &iatom, -1); + name = atom_lookup(conn, atom); + iname = atom_lookup(conn, iatom); + + if (strcasecmp(name, iname) < 0) { + *sibling = iter; + return FALSE; + } + + *sibling = NULL; + return gtk_tree_model_iter_next(tree, iter); +} + +static void +update_item(xcb_atom_t atom, xcb_window_t owner, + security_context_t octx, security_context_t dctx, + const char *data) +{ + GtkTreeModel *tree = GTK_TREE_MODEL(store); + GtkTreeIter iter, *sibling = NULL; + + if (gtk_tree_model_get_iter_first(tree, &iter)) { + do { + if (update_item_matches(&iter, atom, octx)) { + gtk_list_store_set(store, &iter, COL_WIN, owner, COL_DATA, data, -1); + return; + } + } while (update_item_next(atom, &iter, &sibling)); + } + + add_item(sibling, atom, owner, octx, dctx, data); +} + +static gboolean +update_is_managed(xcb_atom_t selection, xcb_atom_t wildcard) +{ + return selection_lookup(conn, window, selection) && + (wildcard == XCB_NONE || wildcard == selection); +} + +static void +update(xcb_atom_t selection, xcb_timestamp_t time) +{ + xcb_selinux_list_selections_cookie_t cookie; + xcb_selinux_list_selections_reply_t *reply; + xcb_get_selection_owner_cookie_t *cookies; + xcb_get_selection_owner_reply_t *owner; + xcb_generic_error_t *error; + xcb_selinux_list_item_iterator_t iter; + security_context_t octx, dctx; + struct transfer *transfer; + int i; + + /* remove existing items if updating */ + if (selection != XCB_NONE) + remove_items(selection); + + /* list all selection instances in the server */ + cookie = xcb_selinux_list_selections(conn); + reply = xcb_selinux_list_selections_reply(conn, cookie, &error); + if (error) + APP_ERR(error, "An error was received during ListSelections.\n"); + + iter = xcb_selinux_list_selections_selections_iterator(reply); + cookies = malloc(iter.rem * sizeof(*cookies)); + if (!cookies) + APP_ERR(NULL, "Out of memory\n"); + + /* for selections we care about, get the owner window */ + i = 0; + while (iter.rem) { + if (update_is_managed(iter.data->name, selection)) { + octx = xcb_selinux_list_item_object_context(iter.data); + xcb_selinux_set_selection_use_context(conn, strlen(octx) + 1, octx); + cookies[i] = xcb_get_selection_owner(conn, iter.data->name); + } + xcb_selinux_list_item_next(&iter); + ++i; + } + + /* for selections with owner window, issue ConvertSelection */ + i = 0; + iter = xcb_selinux_list_selections_selections_iterator(reply); + while (iter.rem) { + if (update_is_managed(iter.data->name, selection)) { + octx = xcb_selinux_list_item_object_context(iter.data); + dctx = xcb_selinux_list_item_data_context(iter.data); + owner = xcb_get_selection_owner_reply(conn, cookies[i], &error); + if (error) + APP_ERR(error, "An error was received during GetSelectionOwner.\n"); + + transfer = malloc(sizeof(struct transfer)); + if (!transfer) + APP_ERR(NULL, "Out of memory.\n"); + transfer->owner = owner->owner; + transfer->octx = strdup(octx); + transfer->dctx = strdup(dctx); + + if (owner->owner != XCB_NONE) { + update_item(iter.data->name, owner->owner, octx, dctx, ""); + send_convert_request(iter.data, owner->owner, xa_text, time, transfer); + } + free(owner); + } + xcb_selinux_list_item_next(&iter); + ++i; + } + + free(reply); + free(cookies); + xcb_flush(conn); +} + +static void +handle_notify(xcb_selection_notify_event_t *event) +{ + xcb_atom_t type; + unsigned format; + void *data; + size_t len; + security_context_t octx, dctx; + struct transfer *transfer; + + transfer = g_hash_table_lookup(transfers, (void *)event->requestor); + octx = transfer->octx; + dctx = transfer->dctx; + + if (event->property == XCB_NONE) { + update_item(event->selection, transfer->owner, octx, dctx, + ""); + return; + } + + fetch_property_data(conn, event->requestor, event->property, FALSE, + &type, &format, &data, &len); + + if (event->target == xa_text) { + update_item(event->selection, transfer->owner, octx, dctx, + scrub_text(data)); + } + + free(data); + + g_hash_table_remove(transfers, (void *)event->requestor); + xcb_destroy_window(conn, event->requestor); +} + +static void +handle_xfixes(xcb_xfixes_selection_notify_event_t *event) +{ + update(event->selection, event->timestamp); +} + +static void +handle_client(xcb_client_message_event_t *event) +{ + fprintf(stderr, "Monitor: got a %s ClientMessage\n", + atom_lookup(conn, event->type)); +} + +static gboolean +handle_event(GIOChannel *source, GIOCondition condition, gpointer data) +{ + xcb_generic_event_t *event; + + while ((event = xcb_poll_for_event(conn))) { + switch (event->response_type) { + case XCB_MAPPING_NOTIFY: + /* do nothing */ + break; + + case XCB_SELECTION_NOTIFY: + case XCB_SELECTION_NOTIFY | 0x80: + handle_notify((xcb_selection_notify_event_t *)event); + break; + + case XCB_CLIENT_MESSAGE: + case XCB_CLIENT_MESSAGE | 0x80: + handle_client((xcb_client_message_event_t *)event); + break; + + case 0: + APP_ERR((xcb_generic_error_t *)event, "Monitor: Error in main loop!\n"); + + default: + if (event->response_type == xfixes_type) + handle_xfixes((xcb_xfixes_selection_notify_event_t *)event); + else + fprintf(stderr, "Monitor: %d event\n", event->response_type); + break; + } + free(event); + xcb_flush(conn); + } + + return TRUE; +} + +int +sel_monitor_init(struct thread_data *data) +{ + int fd; + GIOChannel *channel; + + store = GTK_LIST_STORE(data->store); + + transfers = g_hash_table_new_full(NULL, NULL, NULL, free_transfer); + + setup_x(data->display); + + fd = xcb_get_file_descriptor(conn); + channel = g_io_channel_unix_new(fd); + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_add_watch(channel, G_IO_IN|G_IO_ERR|G_IO_HUP, handle_event, NULL); + + update(XCB_NONE, XCB_CURRENT_TIME); + handle_event(channel, 0, NULL); + + return 0; +} diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..b38822a --- /dev/null +++ b/src/util.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include "app.h" + + +void +app_err(xcb_generic_error_t *error, + const char *func, const int line, const char *fmt, ...) +{ + va_list ap; + + fprintf(stderr, "At %s (line %d):\n", func, line); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (error) { + xcb_value_error_t *err = (xcb_value_error_t *)error; + fprintf(stderr, "X error received:\n"); + fprintf(stderr, "\tresponse_type: %u\n", err->response_type); + fprintf(stderr, "\terror_code: %u\n", err->error_code); + fprintf(stderr, "\tbad_value: %u\n", err->bad_value); + fprintf(stderr, "\tmajor: %d\n", err->major_opcode); + fprintf(stderr, "\tminor: %d\n", err->minor_opcode); + } + + exit(1); +} + +char * +get_colors(security_context_t octx, security_context_t dctx) +{ + char *result = strdup("#000000 #ffffff #000000 #ffffff"); + char *color; + + if (octx && selinux_raw_context_to_color(octx, &color) == 0) { + memcpy(result, color, 16); + free(color); + } + if (dctx && selinux_raw_context_to_color(dctx, &color) == 0) { + memcpy(result + 16, color, 16); + free(color); + } + + result[7] = result[15] = result[23] = result[31] = '\0'; + return result; +} + +char * +scrub_text(void *input) +{ + char *text = input; + int i, max = 64; + + for (i = 0; i < max; i++) { + if (text[i] == '\0' || text[i] == '\n') { + text[i] = '\0'; + return text; + } + } + + text[i] = '\0'; + return text; +} -- cgit v1.2.3