#include #include #include "dat.h" #include "fn.h" static xcb_connection_t *conn; static xcb_screen_t *scr; static xcb_window_t win; static xcb_gcontext_t gc; static xcb_pixmap_t pix; static u32int *img; static xcb_atom_t currentdesktop; static xcb_atom_t workarea; static int depth, hasrandr, imgh, imgw; static xcb_screen_t* getscr(xcb_connection_t *c, int n) { xcb_screen_iterator_t i; for(i = xcb_setup_roots_iterator(xcb_get_setup(c)); i.rem; xcb_screen_next(&i)) if(n-- == 0) return i.data; return nil; } static xcb_visualtype_t* getvisual(xcb_screen_t *s) { xcb_depth_iterator_t di; xcb_visualtype_iterator_t vi; for(di = xcb_screen_allowed_depths_iterator(s); di.rem; xcb_depth_next(&di)){ if(di.data->depth != s->root_depth) continue; for(vi = xcb_depth_visuals_iterator(di.data); vi.rem; xcb_visualtype_next(&vi)) if(vi.data->visual_id == s->root_visual) return vi.data; } return nil; } static int validformat(xcb_connection_t *c, xcb_screen_t *s) { const xcb_setup_t *setup; xcb_format_iterator_t fi; xcb_visualtype_t *v; u32int one; int lsb; setup = xcb_get_setup(c); v = getvisual(s); if(v == nil || s->root_depth != 24 || v->_class != XCB_VISUAL_CLASS_TRUE_COLOR || v->red_mask != 0xff0000 || v->green_mask != 0x00ff00 || v->blue_mask != 0x0000ff) return 0; one = 1; lsb = *(uchar*)&one != 0; if((lsb && setup->image_byte_order != XCB_IMAGE_ORDER_LSB_FIRST) || (!lsb && setup->image_byte_order != XCB_IMAGE_ORDER_MSB_FIRST)) return 0; for(fi = xcb_setup_pixmap_formats_iterator(setup); fi.rem; xcb_format_next(&fi)) if(fi.data->depth == 24 && fi.data->bits_per_pixel == 32 && fi.data->scanline_pad == 32) return 1; return 0; } static xcb_atom_t getatom(char *name, int exists) { xcb_intern_atom_cookie_t cookie; xcb_intern_atom_reply_t *reply; xcb_atom_t atom; cookie = xcb_intern_atom(conn, exists, strlen(name), name); reply = xcb_intern_atom_reply(conn, cookie, nil); atom = reply == nil ? XCB_ATOM_NONE : reply->atom; free(reply); return atom; } static int getcardinals(xcb_atom_t atom, u32int off, int n, u32int *v) { xcb_get_property_cookie_t cookie; xcb_get_property_reply_t *reply; int ok; if(atom == XCB_ATOM_NONE) return 0; cookie = xcb_get_property(conn, 0, scr->root, atom, XCB_ATOM_CARDINAL, off, n); reply = xcb_get_property_reply(conn, cookie, nil); ok = reply != nil && reply->type == XCB_ATOM_CARDINAL && reply->format == 32 && xcb_get_property_value_length(reply) == n*(int)sizeof v[0]; if(ok) memmove(v, xcb_get_property_value(reply), n*sizeof v[0]); free(reply); return ok; } static int getworkarea(Area *a) { u32int desktop, v[4]; if(!getcardinals(currentdesktop, 0, 1, &desktop) || desktop > 0x3fffffff || !getcardinals(workarea, desktop*4, 4, v) || v[0] > 0x7fffffff || v[1] > 0x7fffffff || v[2] == 0 || v[2] > 0x7fffffff || v[3] == 0 || v[3] > 0x7fffffff) return 0; a->x = v[0]; a->y = v[1]; a->w = v[2]; a->h = v[3]; return 1; } static void getrootarea(Area *a) { xcb_get_geometry_cookie_t cookie; xcb_get_geometry_reply_t *reply; a->x = 0; a->y = 0; a->w = scr->width_in_pixels; a->h = scr->height_in_pixels; cookie = xcb_get_geometry(conn, scr->root); reply = xcb_get_geometry_reply(conn, cookie, nil); if(reply != nil && reply->width > 0 && reply->height > 0){ a->w = reply->width; a->h = reply->height; } free(reply); } static void popupwork(int x, int y, Area *out) { xcb_generic_error_t *err; xcb_randr_get_monitors_cookie_t cookie; xcb_randr_get_monitors_reply_t *reply; xcb_randr_monitor_info_iterator_t it; Area root, net, *mon; int i, j, n; mon = nil; n = 0; err = nil; reply = nil; if(hasrandr){ cookie = xcb_randr_get_monitors(conn, scr->root, 1); reply = xcb_randr_get_monitors_reply(conn, cookie, &err); if(reply != nil) n = xcb_randr_get_monitors_monitors_length(reply); if(n > 0 && (uvlong)n <= (uvlong)(~(ulong)0)/sizeof mon[0]) mon = malloc(n*sizeof mon[0]); if(mon != nil){ it = xcb_randr_get_monitors_monitors_iterator(reply); for(i = j = 0; i < n && it.rem; i++, xcb_randr_monitor_info_next(&it)){ if(it.data->width == 0 || it.data->height == 0) continue; mon[j].x = it.data->x; mon[j].y = it.data->y; mon[j].w = it.data->width; mon[j].h = it.data->height; j++; } n = j; if(n == 0){ free(mon); mon = nil; } } } if(mon == nil){ getrootarea(&root); mon = &root; n = 1; } if(getworkarea(&net)) popuparea(mon, n, &net, x, y, out); else popuparea(mon, n, nil, x, y, out); if(mon != &root) free(mon); free(reply); free(err); } static void wincleanup(void) { textclose(); free(img); img = nil; if(conn != nil){ xcb_disconnect(conn); conn = nil; } scr = nil; win = 0; gc = 0; pix = 0; currentdesktop = XCB_ATOM_NONE; workarea = XCB_ATOM_NONE; hasrandr = 0; imgh = 0; imgw = 0; } static int wininit(void) { int n; u32int mask, vals[4]; xcb_intern_atom_cookie_t c1, c2; xcb_intern_atom_reply_t *r1, *r2; xcb_randr_query_version_cookie_t rc; xcb_randr_query_version_reply_t *rr; const xcb_query_extension_reply_t *rext; conn = xcb_connect(nil, &n); if(conn == nil || xcb_connection_has_error(conn)){ fprint(2, "strans: popup disabled: cannot connect to X display\n"); wincleanup(); return 0; } scr = getscr(conn, n); if(scr == nil || !validformat(conn, scr)){ fprint(2, "strans: popup disabled: unsupported X root format\n"); wincleanup(); return 0; } depth = scr->root_depth; rext = xcb_get_extension_data(conn, &xcb_randr_id); if(rext != nil && rext->present){ rc = xcb_randr_query_version(conn, 1, 5); rr = xcb_randr_query_version_reply(conn, rc, nil); hasrandr = rr != nil && (rr->major_version > 1 || (rr->major_version == 1 && rr->minor_version >= 5)); free(rr); } currentdesktop = getatom("_NET_CURRENT_DESKTOP", 0); workarea = getatom("_NET_WORKAREA", 0); win = xcb_generate_id(conn); mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_SAVE_UNDER; vals[0] = Colbg; vals[1] = 0; vals[2] = 1; vals[3] = 1; xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, scr->root, 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, scr->root_visual, mask, vals); c1 = xcb_intern_atom(conn, 0, strlen("_NET_WM_WINDOW_TYPE"), "_NET_WM_WINDOW_TYPE"); c2 = xcb_intern_atom(conn, 0, strlen("_NET_WM_WINDOW_TYPE_TOOLTIP"), "_NET_WM_WINDOW_TYPE_TOOLTIP"); r1 = xcb_intern_atom_reply(conn, c1, nil); r2 = xcb_intern_atom_reply(conn, c2, nil); if(r1 != nil && r2 != nil) xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win, r1->atom, XCB_ATOM_ATOM, 32, 1, &r2->atom); free(r1); free(r2); gc = xcb_generate_id(conn); xcb_create_gc(conn, gc, win, 0, nil); if(!textinit()){ fprint(2, "strans: popup disabled: no usable fonts\n"); wincleanup(); return 0; } return 1; } static int resizebacking(int w, int h) { xcb_generic_error_t *err; xcb_pixmap_t old, new; xcb_void_cookie_t cookie; uvlong pixels; int nh, nw; if(w <= 0 || h <= 0) return 0; if(w <= imgw && h <= imgh) return 1; nw = max(w, imgw); nh = max(h, imgh); pixels = (uvlong)nw * nh; if(pixels > (uvlong)(~(ulong)0)/sizeof img[0]) return 0; img = erealloc(img, pixels*sizeof img[0]); new = xcb_generate_id(conn); cookie = xcb_create_pixmap_checked(conn, depth, new, win, nw, nh); err = xcb_request_check(conn, cookie); if(err != nil){ free(err); return 0; } cookie = xcb_change_window_attributes_checked(conn, win, XCB_CW_BACK_PIXMAP, &new); err = xcb_request_check(conn, cookie); if(err != nil){ free(err); xcb_free_pixmap(conn, new); return 0; } old = pix; pix = new; imgw = nw; imgh = nh; if(old != 0) xcb_free_pixmap(conn, old); return 1; } static void putimage(int w, int h) { if(w <= 0 || w > imgw || h <= 0 || h > imgh) return; xcb_put_image(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc, w, h, 0, 0, 0, depth, w * h * 4, (u8int*)img); /* The retained background pixmap lets the server repaint exposures. */ xcb_clear_area(conn, 0, win, 0, 0, w, h); } static int winhide(void) { xcb_unmap_window(conn, win); return xcb_flush(conn) > 0; } static int winshow(Drawcmd *dc) { Area area; Popup p; int ax, ay, px, py; u32int vals[4]; xcb_query_pointer_reply_t *ptr; xcb_query_pointer_cookie_t cookie; if(dc->nkouho <= 0 && dc->pre.n == 0){ return winhide(); } px = py = 0; if(!dc->caret.valid){ cookie = xcb_query_pointer(conn, scr->root); ptr = xcb_query_pointer_reply(conn, cookie, nil); if(ptr == nil) return 0; px = ptr->root_x; py = ptr->root_y; free(ptr); } ax = dc->caret.valid ? dc->caret.x : px; ay = dc->caret.valid ? dc->caret.y : py; popupwork(ax, ay, &area); popuplayout(dc, area.w, area.h, &p); if(p.w <= 0 || p.h <= 0 || !resizebacking(p.w, p.h)) return 0; vals[2] = p.w; vals[3] = p.h; popupposition(&dc->caret, px, py, &area, p.w, p.h, &px, &py); vals[1] = py; vals[0] = px; xcb_configure_window(conn, win, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, vals); popupdraw(img, dc, &p); putimage(p.w, p.h); xcb_map_window(conn, win); return xcb_flush(conn) > 0; } void drawthread(void*) { Drawcmd dc; threadsetname("draw"); if(!wininit()) return; while(chanrecv(drawc, &dc) > 0){ while(channbrecv(drawc, &dc) > 0) ; if(dc.nkouho == 0 && dc.pre.n == 0){ if(!winhide()) break; }else if(!winshow(&dc)) break; } wincleanup(); }