#include #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 hasrandr, imgh, imgw; static int shown, ptrx, ptry; /* the popup, and where the pointer was as it came up */ 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; } /* putimage writes native-endian x8r8g8b8; the root must take that. */ 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) { xcb_intern_atom_cookie_t cookie; xcb_intern_atom_reply_t *reply; xcb_atom_t atom; cookie = xcb_intern_atom(conn, 0, 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); } /* The area the popup may use: the RandR monitor under (x, y), or the * root, cut down to the EWMH work area. */ static void popupwork(int x, int y, Area *out) { 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 n; mon = nil; n = 0; reply = nil; if(hasrandr){ cookie = xcb_randr_get_monitors(conn, scr->root, 1); reply = xcb_randr_get_monitors_reply(conn, cookie, nil); } if(reply != nil && xcb_randr_get_monitors_monitors_length(reply) > 0){ mon = emalloc(xcb_randr_get_monitors_monitors_length(reply) * sizeof mon[0]); it = xcb_randr_get_monitors_monitors_iterator(reply); for(; it.rem; xcb_randr_monitor_info_next(&it)){ if(it.data->width == 0 || it.data->height == 0) continue; mon[n].x = it.data->x; mon[n].y = it.data->y; mon[n].w = it.data->width; mon[n].h = it.data->height; n++; } } if(n == 0){ free(mon); getrootarea(&root); mon = &root; n = 1; } popuparea(mon, n, getworkarea(&net) ? &net : nil, x, y, out); if(mon != &root) free(mon); free(reply); } static void wincleanup(void) { textclose(); free(img); xcb_disconnect(conn); } static int wininit(void) { int n; u32int mask, vals[5]; xcb_atom_t type, tooltip; xcb_randr_query_version_cookie_t rc; xcb_randr_query_version_reply_t *rr; const xcb_query_extension_reply_t *rext; char *scale; scale = getenv("GDK_SCALE"); if(scale != nil && atoi(scale) > 1) popupscale = min(atoi(scale), 4); conn = xcb_connect(nil, &n); if(xcb_connection_has_error(conn)){ fprint(2, "strans: popup disabled: cannot connect to X display\n"); wincleanup(); return 0; } scr = xcb_aux_get_screen(conn, n); if(scr == nil || !validformat(conn, scr)){ fprint(2, "strans: popup disabled: unsupported X root format\n"); wincleanup(); return 0; } 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"); workarea = getatom("_NET_WORKAREA"); win = xcb_generate_id(conn); /* Clicks on the popup go nowhere, not to the root window under it. */ mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_SAVE_UNDER | XCB_CW_DONT_PROPAGATE; vals[0] = Colbg; vals[1] = Colsep; vals[2] = 1; vals[3] = 1; vals[4] = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE; xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, scr->root, 0, 0, 1, 1, PopupBorder, XCB_WINDOW_CLASS_INPUT_OUTPUT, scr->root_visual, mask, vals); type = getatom("_NET_WM_WINDOW_TYPE"); tooltip = getatom("_NET_WM_WINDOW_TYPE_TOOLTIP"); if(type != XCB_ATOM_NONE && tooltip != XCB_ATOM_NONE) xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win, type, XCB_ATOM_ATOM, 32, 1, &tooltip); gc = xcb_generate_id(conn); xcb_create_gc(conn, gc, win, 0, nil); textinit(); return 1; } /* The image and its retained pixmap only ever grow. */ static int resizebacking(int w, int h) { xcb_generic_error_t *err; xcb_pixmap_t old, new; xcb_void_cookie_t cookie; int nh, nw; if(w <= imgw && h <= imgh) return 1; nw = max(w, imgw); nh = max(h, imgh); img = erealloc(img, (ulong)nw * nh * sizeof img[0]); new = xcb_generate_id(conn); cookie = xcb_create_pixmap_checked(conn, scr->root_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) { xcb_put_image(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc, w, h, 0, 0, 0, scr->root_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 void winhide(void) { xcb_unmap_window(conn, win); xcb_flush(conn); shown = 0; } /* * Draws dc at the caret, or by the pointer when the caret is unknown: * where the pointer was as the popup came up, so that it does not * follow the mouse from key to key. */ static void winshow(Drawcmd *dc) { Area area; Popup p; int ax, ay, x, y; u32int vals[5]; xcb_query_pointer_reply_t *ptr; xcb_query_pointer_cookie_t cookie; if(dc->nkouho == 0 && dc->pre.n == 0){ winhide(); return; } if(!dc->caret.valid && !shown){ cookie = xcb_query_pointer(conn, scr->root); ptr = xcb_query_pointer_reply(conn, cookie, nil); if(ptr == nil) return; ptrx = ptr->root_x; ptry = ptr->root_y; free(ptr); } ax = dc->caret.valid ? dc->caret.x : ptrx; ay = dc->caret.valid ? dc->caret.y : ptry; popupwork(ax, ay, &area); popuplayout(dc, area.w, area.h, &p); if(p.w <= 0 || p.h <= 0){ winhide(); return; } if(!resizebacking(p.w, p.h)) return; popupposition(&dc->caret, ptrx, ptry, &area, p.w + 2*PopupBorder, p.h + 2*PopupBorder, &x, &y); /* Mapping does not restack: raise above windows opened since. */ vals[0] = x; vals[1] = y; vals[2] = p.w; vals[3] = p.h; vals[4] = XCB_STACK_MODE_ABOVE; xcb_configure_window(conn, win, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_STACK_MODE, vals); popupdraw(img, dc, &p); putimage(p.w, p.h); xcb_map_window(conn, win); xcb_flush(conn); shown = 1; } void drawthread(void*) { Drawcmd dc; threadsetname("draw"); if(!wininit()) return; while(chanrecv(drawc, &dc) > 0 && !xcb_connection_has_error(conn)) winshow(&dc); wincleanup(); }