Files
strans/win.c
Hojun-Cho c1971c8096 fix(popup): accept depth-24 roots regardless of colormap precision
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.
2026-08-16 15:45:09 +09:00

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();
}