The popup window is created once at startup and only mapped afterwards, and MapWindow does not restack, so every application window (or WM frame) opened after the daemon sat above it: candidates and preedit were invisible in those windows until the daemon restarted. Configure the window above its siblings on every show. The XIM live test now checks the popup stacks above a client window created after the daemon.
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;
|
|
|
|
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);
|
|
if(!textinit()){
|
|
fprint(2, "strans: popup disabled: no usable fonts\n");
|
|
wincleanup();
|
|
return 0;
|
|
}
|
|
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();
|
|
}
|