summaryrefslogtreecommitdiff
path: root/src/selmon.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/selmon.c')
-rw-r--r--src/selmon.c397
1 files changed, 397 insertions, 0 deletions
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;
+}