#include #include #include #include "app.h" static xcb_connection_t *conn; static GtkTreeStore *store; xcb_window_t root; static void setup_x(const gchar *display) { const xcb_setup_t *setup; xcb_screen_iterator_t iter; uint32_t mask, list; make_connection(display, &conn, NULL); 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) { free(error); goto out; } 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; } out: 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) { /* window might have gone away */ if (error->error_code == XCB_WINDOW) { free(error); return; } 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; }