popuplayout had two refit stages for a work area shorter than one padded row — 40 pixels — and popupdraw re-checked the layout it had just been handed, against an Imgh that bounded no buffer any more. Pango and Cairo abort rather than return nil, so textinit is void and the layout is never nil; the stride Cairo computes for RGB24 is w*4 by definition; and textdraw set the layout's width and ellipsis twice.
371 lines
8.8 KiB
C
371 lines
8.8 KiB
C
#include <xcb/xcb.h>
|
|
#include <xcb/randr.h>
|
|
#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 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;
|
|
}
|
|
|
|
/* 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[4];
|
|
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 = getscr(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);
|
|
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);
|
|
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 int
|
|
winhide(void)
|
|
{
|
|
xcb_unmap_window(conn, win);
|
|
return xcb_flush(conn) > 0;
|
|
}
|
|
|
|
/* Draws dc at the caret, or by the pointer when the caret is unknown. */
|
|
static int
|
|
winshow(Drawcmd *dc)
|
|
{
|
|
Area area;
|
|
Popup p;
|
|
int ax, ay, px, py, 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)
|
|
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;
|
|
popupposition(&dc->caret, px, py, &area, p.w, p.h, &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);
|
|
return xcb_flush(conn) > 0;
|
|
}
|
|
|
|
void
|
|
drawthread(void*)
|
|
{
|
|
Drawcmd dc;
|
|
|
|
threadsetname("draw");
|
|
if(!wininit())
|
|
return;
|
|
while(chanrecv(drawc, &dc) > 0)
|
|
if(!winshow(&dc))
|
|
break;
|
|
wincleanup();
|
|
}
|