win.c and xim.c each iterated the setup's roots to find the screen xcb_connect had chosen; xcb-util, already linked through imdkit, has xcb_aux_get_screen for that.
376 lines
9.1 KiB
C
376 lines
9.1 KiB
C
#include <xcb/xcb.h>
|
|
#include <xcb/xcb_aux.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 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();
|
|
}
|