diff options
Diffstat (limited to 'src/selmon.c')
-rw-r--r-- | src/selmon.c | 397 |
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; +} |