#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; make_connection(display, &conn, NULL); 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; }