summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEamon Walsh <ewalsh@tycho.nsa.gov>2009-07-09 22:12:55 -0400
committerEamon Walsh <ewalsh@tycho.nsa.gov>2009-07-09 22:12:55 -0400
commitdabd471bffe1f772c3cf4770dffbce240761917d (patch)
treea690ac51d942307dfb8fb9c0186e84aab7fc20bb /src
Initial commit.
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am9
-rw-r--r--src/app.h107
-rw-r--r--src/atom.c162
-rw-r--r--src/config.h.in25
-rw-r--r--src/conn.c104
-rw-r--r--src/inputmon.c466
-rw-r--r--src/main.c360
-rw-r--r--src/propmon.c466
-rw-r--r--src/selmgr.c481
-rw-r--r--src/selmon.c397
-rw-r--r--src/util.c67
11 files changed, 2644 insertions, 0 deletions
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 <selinux/selinux.h>
+#include <selinux/avc.h>
+#include <xcb/xcb.h>
+#include <xcb/xproto.h>
+#include <xcb/xfixes.h>
+#include <xcb/xselinux.h>
+#include <gtk/gtk.h>
+
+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 <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#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 <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#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 <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <stdlib.h>
+
+#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 <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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, "<Unknown>");
+ 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,
+ "<No data available>");
+ 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 <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#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;
+}