/* * Attempt to implement picture viewer XCB with kbd and pointer grabbing, fullscreen * * Author: Peter Wu * Date: 2012-09-24 * gcc -o xcbviewfs xcbviewfs.c -lxcb-image -lxcb-shm -lxcb */ #include #include #include #include #include #include #include #include /* defined in colorlookup.c which is generated by colorlookupgen.c */ extern int color_lookup(char *name, uint8_t color[3]); #define bail(lbl, msg) do { \ fprintf(stderr, msg); \ goto lbl; \ } while (0) static unsigned char empty_cursor_data[32]; static int load_xpm_image(char *filename, unsigned char **data, unsigned int *len, unsigned int *width, unsigned int *height); int main(int argc, char **argv) { xcb_connection_t *c; xcb_screen_t *screen; xcb_window_t win; xcb_cursor_t cursor; char *filename; unsigned char *image_data; unsigned int image_data_len; unsigned int image_width, image_height; xcb_pixmap_t pmap = 0; xcb_gcontext_t gc = 0; /* options */ int grab_pointer = 0; int hide_cursor = 0; int grab_kbd = 0; int fullscreen = 0; int draw_picture = 0; //grab_kbd = !(fullscreen = grab_pointer = hide_cursor = 0 ); draw_picture = grab_kbd = fullscreen = grab_pointer = hide_cursor = 1; xcb_keycode_t magic_keys[] = {40, 31, 26}; /* DIE */ if (argc < 2) { fprintf(stderr, "Usage: %s filename\n", *argv); return 1; } filename = argv[1]; c = xcb_connect(NULL, NULL); if (!c) { fprintf(stderr, "Cannot open display\n"); return 1; } if (!load_xpm_image(filename, &image_data, &image_data_len, &image_width, &image_height)) bail(disconnect, "Failed to load XPM image\n"); screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data; win = xcb_generate_id(c); /* For each mask, need to add valwin item in order as defined by the * enum, see See http://xcb.freedesktop.org/tutorial/events/ */ uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; uint32_t valwin[] = { screen->black_pixel, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS, }; xcb_create_window(c, 0, win, /* conn, depth, WinID */ screen->root, /* parent */ 0, 0, /* x y */ screen->width_in_pixels, screen->height_in_pixels, 0, /* border_width */ XCB_WINDOW_CLASS_INPUT_OUTPUT, /* class */ screen->root_visual, mask, valwin); if (hide_cursor) { xcb_pixmap_t cp = xcb_create_pixmap_from_bitmap_data(c, win, empty_cursor_data, 16, 16, 1, 0, 0, 0); cursor = xcb_generate_id(c); xcb_create_cursor(c, cursor, cp, cp, 0, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 8, 8); xcb_free_pixmap(c, cp); /* pointer is already changed by xcb_grab_pointer */ if (!grab_pointer) { xcb_change_window_attributes(c, win, XCB_CW_CURSOR, &cursor); xcb_flush(c); } } if (grab_pointer) { xcb_grab_pointer_cookie_t cookie; xcb_grab_pointer_reply_t *reply; cookie = xcb_grab_pointer(c, 1, screen->root, XCB_NONE, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE, hide_cursor ? cursor : XCB_NONE, XCB_CURRENT_TIME); if (!(reply = xcb_grab_pointer_reply(c, cookie, NULL))) { bail(disconnect, "No pointer grab reply\n"); } if (reply->status != XCB_GRAB_STATUS_SUCCESS) { bail(disconnect, "Grab pointer FAILED\n"); } free(reply); } if (hide_cursor) xcb_free_cursor(c, cursor); if (grab_kbd) { xcb_grab_keyboard_cookie_t cookie; xcb_grab_keyboard_reply_t *reply; cookie = xcb_grab_keyboard(c, 1, screen->root, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); if (!(reply = xcb_grab_keyboard_reply(c, cookie, NULL))) { bail(ungrab_pointer, "No kbd grab reply\n"); } if (reply->status != XCB_GRAB_STATUS_SUCCESS) { bail(ungrab_pointer, "Grab kbd FAILED\n"); } free(reply); } if (fullscreen) { xcb_intern_atom_cookie_t cookie; xcb_intern_atom_reply_t *reply; xcb_atom_t property, data; cookie = xcb_intern_atom(c, 0, 13, "_NET_WM_STATE"); if (!(reply = xcb_intern_atom_reply(c, cookie, 0))) { bail(ungrab_kbd, "Failed to get _NET_WM_STATE atom\n"); } property = reply->atom; free(reply); cookie = xcb_intern_atom(c, 0, 24, "_NET_WM_STATE_FULLSCREEN"); if (!(reply = xcb_intern_atom_reply(c, cookie, 0))) { bail(ungrab_kbd, "Failed to get _NET_WM_STATE_FULLSCREEN atom\n"); } data = reply->atom; free(reply); xcb_change_property(c, XCB_PROP_MODE_REPLACE, win, property, XCB_ATOM_ATOM, 32, 1, &data); xcb_flush(c); } if (draw_picture) { /** * X-Bitmap, XY-Pixmap or Z-Pixmap? See * http://www.linux.ie/old-list/23540.html */ xcb_image_t *img; img = xcb_image_create_native(c, image_width, image_height, XCB_IMAGE_FORMAT_Z_PIXMAP, 24, /* depth */ image_data, image_data_len, image_data); pmap = xcb_generate_id(c); xcb_create_pixmap(c, 24, pmap, win, image_width, image_height); gc = xcb_generate_id(c); xcb_create_gc(c, gc, pmap, 0, NULL); xcb_image_put(c, pmap, gc, img, 0, 0, 0); xcb_flush(c); xcb_image_destroy(img); } xcb_map_window(c, win); xcb_flush(c); size_t magic_level = 0; size_t magic_len = sizeof(magic_keys)/sizeof(*magic_keys); xcb_generic_event_t *e; int running = 1; while (running && (e = xcb_wait_for_event(c))) { switch (e->response_type) { case XCB_EXPOSE: if (!draw_picture) break; xcb_expose_event_t *ee = (xcb_expose_event_t *)e; xcb_copy_area(c, pmap, win, gc, ee->x, ee->y, ee->x, ee->y, ee->width, ee->height); xcb_flush(c); break; case XCB_KEY_PRESS: ; xcb_button_press_event_t *press = (xcb_button_press_event_t *)e; xcb_keycode_t key = press->detail; if (magic_keys[magic_level] == key) { if (++magic_level >= magic_len) running = 0; } else { magic_level = 0; } break; } free(e); } if (pmap) xcb_free_pixmap(c, pmap); /* if closed normally */ if (running) xcb_destroy_window(c, win); ungrab_kbd: xcb_ungrab_keyboard(c, XCB_CURRENT_TIME); ungrab_pointer: xcb_ungrab_pointer(c, XCB_CURRENT_TIME); disconnect: if (!draw_picture) free(image_data); /* TODO: check other error paths */ xcb_disconnect(c); return 0; } #define print_once(...) do { \ static int printed = 0; \ if (!printed) { \ fprintf(stderr, __VA_ARGS__); \ printed = 1; \ } \ } while(0) static void xpm_parse_color_line(char *line, uint8_t color[3]) { int search_color = 0; char c = *line; while (c) { char *next_ws; while (c == ' ' || c == '\t') c = *++line; next_ws = strchr(line, ' '); if (!next_ws) next_ws = strchr(line, '\t'); if (next_ws) *next_ws = 0; if (search_color) { if (line[0] == '#') { unsigned int hex = strtol(line + 1, NULL, 16); if (strlen(line) == 4) { /* #RGB */ color[0] = ((hex >> 2) & 0xF) * 0x11; color[1] = ((hex >> 1) & 0xF) * 0x11; color[2] = (hex & 0xF) * 0x11; } else { /* assume #RRGGBB */ color[0] = (hex >>16) & 0xFF; color[1] = (hex >> 8) & 0xFF; color[2] = hex & 0xFF; } return; } else if (line[0] == '%') { /* HSV not implemented */ print_once("HSV not implemented\n"); } else if (strcasecmp("None", line) == 0) { /* transparant wut? */ memset(color, 0, 3); return; } else if (color_lookup(line, color)) { /* TODO: handle colors with whitespace */ return; } else { print_once("Color %s unimplemented\n", line); } search_color = 0; } else if (strcmp(line, "c") == 0) { search_color = 1; } else { /* not implemented */ print_once("Color type not implemented: %s\n", line); } /* continue searching if there are more tokens */ if (next_ws) line = next_ws + 1; else break; } color[0] = color[1] = color[2] = 0x42; print_once("No color found\n"); } typedef struct _ColorPair ColorPair; struct _ColorPair { unsigned int key; /* assume that the key has less or equal bytes */ uint8_t color[3]; /* RGB */ }; int colorpaircmp(const void *a, const void *b) { const ColorPair *left = a, *right = b; return left->key - right->key; } static int load_xpm_image(char *filename, unsigned char **data, unsigned int *len, unsigned int *width, unsigned int *height) { unsigned int w, h; char **xpm_data; int error; if ((error = XpmReadFileToData(filename, &xpm_data))) { fprintf(stderr, "Cannot load image: %s\n", XpmGetErrorString(error)); return 0; } /* 1: width height ncolors chars_per_pixel [opt_hotspot_x opt_hotspot_y] * ncolors: char (of len chars_per_pixel) (key color)+ * key m(ono), s(ymbolic name), g(rayscale)4(level), g(rayscale w/ * more than 4 levels), c(color visual). "s None" is transparant * height: width*chars_per_pixels * (ignore Extensions), array size = 1 + ncolors + height * XCB image: width * height * 4 bytes * See also http://www.net.uom.gr/Books/Manuals/xpm-3-paper.pdf */ unsigned int ncolors, cpp; unsigned int i; /* assume XPM library is not stupid */ sscanf(xpm_data[0], "%u %u %u %u", &w, &h, &ncolors, &cpp); if (cpp > sizeof(unsigned int)) { bail(free_xpm, "CPP does not fit in integer data type\n"); } *width = w; *height = h; *len = w * h * 4; *data = malloc(w * h * 4); if (!data) { bail(free_xpm, "Cannot allocate memory for image\n"); } ColorPair *colors = malloc(ncolors * sizeof(ColorPair)); for (i=1; i<=ncolors; i++) { char *line = xpm_data[i]; ColorPair *color = &colors[i - 1]; color->key = 0; memcpy(&color->key, line, cpp); /* skip color key */ xpm_parse_color_line(line + cpp, color->color); } qsort(colors, ncolors, sizeof(ColorPair), colorpaircmp); /* fill data */ unsigned int x, y; char **all_pixels = &xpm_data[1 + ncolors]; for (y=0; ycolor; unsigned char *p = data_line + 4 * x; *(p++) = c[2]; /* b */ *(p++) = c[1]; /* g */ *(p++) = c[0]; /* r */ } else { fprintf(stderr, "No color found\n"); } } } error = 1; /* success */ free(colors); free_xpm: XpmFree(xpm_data); return error; }