bits_per_rgb_value is the colormap component precision, not the pixel layout; the NVIDIA driver reports 11 for its ordinary x8r8g8b8 visuals, which left the popup disabled there. Depth, class, masks, byte order and the pixmap format already pin the layout that putimage writes.
412 lines
9.3 KiB
C
412 lines
9.3 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 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();
|
|
}
|