/** * @file * @section AUTHORS * * Copyright (C) 2004-2005 Norman Feske * * Authors: * Eamon Walsh * Edward A. Schneider * Norman Feske * * @section LICENSE * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; under version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * @section DESCRIPTION * * This is the linpicker-track program. * * The Linpicker version of track has been completely rewritten from scratch. * The purpose of the program is to keep track of top level X Windows and * report their position, size, and visibility to linpicker-server via a * libvchan connection. */ #include #include #include #include #include #include #include #include #include #include #include "libvchan.h" #include "common.h" #include "comm_structs.h" static struct libvchan *ctrl; static int vchan_active; static int time_to_die; static void signal_handler(int signal) { time_to_die = 1; } static void send_to_server(void *buf, int size) { int ret; if (!vchan_active) return; while (1) { ret = libvchan_send(ctrl, buf, size); if (ret > 0) return; if (ret <= 0) { perror("write"); return; } libvchan_wait(ctrl); } } /*** SESSION ENDED ***/ static void remove_windows(void) { struct lin_message m = { LINPICKER_RESET }; send_to_server(&m, sizeof(m)); } static void scan_windows(xcb_connection_t *conn, xcb_window_t root) { xcb_window_t *children; xcb_query_tree_cookie_t qcook; xcb_query_tree_reply_t *qrep; xcb_generic_error_t *e; xcb_get_window_attributes_cookie_t *acook; xcb_get_window_attributes_reply_t *arep; xcb_get_geometry_cookie_t *gcook; xcb_get_geometry_reply_t *grep; unsigned int n, i; struct lin_message m = { LINPICKER_CREATE }; /* issue query request */ qcook = xcb_query_tree(conn, root); qrep = xcb_query_tree_reply(conn, qcook, &e); if (e || !qrep) { free(e); return; } children = xcb_query_tree_children(qrep); n = xcb_query_tree_children_length(qrep); if (n > 0) { /* allocate memory */ acook = malloc(n * sizeof(*acook)); gcook = malloc(n * sizeof(*gcook)); if (!acook || !gcook) { perror("malloc"); exit(1); } /* issue getattr requests */ for (i = 0; i < n; i++) { acook[i] = xcb_get_window_attributes(conn, children[i]); gcook[i] = xcb_get_geometry(conn, children[i]); } /* collect replies */ for (i = 0; i < n; i++) { arep = xcb_get_window_attributes_reply(conn, acook[i], &e); if (e || !arep) { free(e); arep = NULL; } grep = xcb_get_geometry_reply(conn, gcook[i], &e); if (e || !grep) { free(e); grep = NULL; } if (arep && grep) { m.view = children[i]; m.flags = LINPICKER_FLAG_TOPMOST | LINPICKER_FLAG_POSITION; m.flags |= (arep->_class == XCB_WINDOW_CLASS_INPUT_OUTPUT && arep->map_state == XCB_MAP_STATE_VIEWABLE) ? LINPICKER_FLAG_SHOWN : LINPICKER_FLAG_HIDDEN; m.x = grep->x; m.y = grep->y; m.w = grep->width; m.h = grep->height; send_to_server(&m, sizeof(m)); } free(arep); free(grep); } free(acook); free(gcook); } free(qrep); } static void get_position(xcb_connection_t *conn, xcb_window_t win, struct lin_message *m) { xcb_get_geometry_cookie_t cook; xcb_get_geometry_reply_t *rep; xcb_generic_error_t *e; cook = xcb_get_geometry(conn, win); rep = xcb_get_geometry_reply(conn, cook, &e); if (e || !rep) { free(e); rep = NULL; } if (rep) { m->flags |= LINPICKER_FLAG_POSITION; m->x = rep->x; m->y = rep->y; m->w = rep->width; m->h = rep->height; free(rep); } } /*** HANDLE X WINDOW SYSTEM EVENTS ***/ static void x_event(xcb_connection_t *conn, xcb_window_t root, xcb_generic_event_t *ev) { xcb_create_notify_event_t *cre; xcb_circulate_notify_event_t *cir; xcb_configure_notify_event_t *con; xcb_destroy_notify_event_t *des; xcb_reparent_notify_event_t *rep; xcb_map_notify_event_t *map; xcb_unmap_notify_event_t *unm; struct lin_message m = { 0 }; switch (ev->response_type) { case XCB_CREATE_NOTIFY: cre = (typeof(cre))ev; m.type = LINPICKER_CREATE; m.view = cre->window; m.flags = LINPICKER_FLAG_POSITION | LINPICKER_FLAG_TOPMOST; m.x = cre->x; m.y = cre->y; m.w = cre->width; m.h = cre->height; break; case XCB_CIRCULATE_NOTIFY: cir = (typeof(cir))ev; m.type = LINPICKER_UPDATE; m.view = cir->window; m.flags = (cir->place == XCB_PLACE_ON_TOP) ? LINPICKER_FLAG_TOPMOST : LINPICKER_FLAG_BOTMOST; break; case XCB_CONFIGURE_NOTIFY: con = (typeof(con))ev; m.type = LINPICKER_UPDATE; m.view = con->window; m.flags = LINPICKER_FLAG_POSITION | LINPICKER_FLAG_SIBLING; m.sibling = con->above_sibling; m.x = con->x; m.y = con->y; m.w = con->width; m.h = con->height; break; case XCB_DESTROY_NOTIFY: des = (typeof(des))ev; m.type = LINPICKER_DESTROY; m.view = des->window; break; case XCB_REPARENT_NOTIFY: rep = (typeof(rep))ev; if (rep->parent != root) m.type = LINPICKER_DESTROY; else { m.type = LINPICKER_CREATE; get_position(conn, rep->window, &m); } m.view = rep->window; break; case XCB_UNMAP_NOTIFY: unm = (typeof(unm))ev; m.type = LINPICKER_UPDATE; m.view = unm->window; m.flags = LINPICKER_FLAG_HIDDEN; break; case XCB_MAP_NOTIFY: map = (typeof(map))ev; m.type = LINPICKER_UPDATE; m.view = map->window; m.flags = LINPICKER_FLAG_SHOWN; break; default: free(ev); return; } free(ev); send_to_server(&m, sizeof(m)); } /*** HANDLE VCHAN EVENTS FROM DISPLAY SERVER ***/ static void vchan_event(xcb_connection_t *conn, xcb_window_t root) { char buf; int ret; /* Messages from server are a single byte */ ret = libvchan_recv(ctrl, &buf, 1); if (ret == 1) switch(buf) { case LINPICKER_REQUEST_UPDATE: vchan_active = 1; remove_windows(); scan_windows(conn, root); break; default: break; } } /*** MAIN PROGRAM ***/ static void eventloop(xcb_connection_t *conn, xcb_window_t root) { xcb_generic_event_t *ev; fd_set readfds; int rc, xfd, vfd, maxfd; xfd = xcb_get_file_descriptor(conn); vfd = libvchan_fd_for_select(ctrl); maxfd = MAX(xfd, vfd) + 1; while (1) { if (time_to_die) break; FD_ZERO(&readfds); FD_SET(xfd, &readfds); FD_SET(vfd, &readfds); rc = select(maxfd, &readfds, NULL, NULL, NULL); if (rc < 0 && errno != EINTR) { perror("select"); break; } if (rc <= 0) continue; if (FD_ISSET(xfd, &readfds)) while((ev = xcb_poll_for_event(conn)) != NULL) x_event(conn, root, ev); if (FD_ISSET(vfd, &readfds)) { libvchan_wait(ctrl); while (libvchan_data_ready(ctrl) > 0) vchan_event(conn, root); } } } static int setup_vchan() { struct xs_handle *xs; char* res; xs = xs_domain_open(); if (!xs) { perror("xs_domain_open"); return -1; } // Assume the provider of the VFB is who we talk to res = xs_read(xs, 0, "device/vfb/0/backend-id", NULL); xs_daemon_close(xs); if (!res) { perror("xs_read of device/vfb/0/backend-id"); return -1; } ctrl = libvchan_server_init(atoi(res), LINPICKER_VPORT, 0, 0); free(res); if (!ctrl) { perror("libvchan_init_server"); return -1; } ctrl->server_persist = 1; return 0; } int main(int argc, char **argv) { struct sigaction action; xcb_connection_t *conn; xcb_window_t root; const xcb_setup_t *setup; xcb_screen_iterator_t iter; int screen; u_int32_t mask; /* install signal handler for quitting nicely on ctrl-c */ memset(&action, 0, sizeof(action)); action.sa_handler = signal_handler; sigaction(SIGTERM, &action, NULL); sigaction(SIGINT, &action, NULL); sigaction(SIGHUP, &action, NULL); if (setup_vchan() < 0) return 1; /* init X stuffs */ conn = xcb_connect(NULL, &screen); if (xcb_connection_has_error(conn)) { printf("Error - cannot open display\n"); return 1; } setup = xcb_get_setup(conn); iter = xcb_setup_roots_iterator(setup); while (screen--) xcb_screen_next(&iter); root = iter.data->root; /* start listening for events */ mask = XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, &mask); /* start sniffing */ eventloop(conn, root); /* kill everything */ remove_windows(); libvchan_close(ctrl); return 0; }