]> git.neil.brown.name Git - edlib.git/blobdiff - display-x11-xcb.c
TODO: clean out done items.
[edlib.git] / display-x11-xcb.c
index aa710e8905e4ca491b7f0214b98bf8e808d1a938..3aa0f73c654f47d3bf37d87796d675774c6d5a1b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright Neil Brown ©2021 <neil@brown.name>
+ * Copyright Neil Brown ©2021-2023 <neil@brown.name>
  * May be distributed under terms of GPLv2 - see file:COPYING
  *
  * X11 display driver for edlib, using xcb, cairopango, libxkbcommon etc.
 #include <unistd.h>
 #include <stdlib.h>
 #include <stdio.h>
+#include <stdint.h>
+#include <fcntl.h>
 #include <string.h>
 #include <xcb/xcb.h>
+#include <stdarg.h>
+#include <sys/wait.h>
 #ifndef __CHECKER__
 #include <xcb/xkb.h>
 #else
@@ -136,17 +140,40 @@ void pango_layout_index_to_pos(PangoLayout*, int, PangoRectangle*);
 #include <xkbcommon/xkbcommon-compose.h>
 #include <xkbcommon/xkbcommon-x11.h>
 
+#include "xcb.h"
+
 #undef True
 #undef False
+
+#define PANE_DATA_TYPE struct xcb_data
 #include "core.h"
 
 enum my_atoms {
+       a_NONE = 0,
        a_WM_STATE, a_STATE_FULLSCREEN,
+       a_WM_NAME, a_NET_WM_NAME,
+       a_WM_ICON_NAME, a_NET_WM_ICON_NAME,
+       a_WM_PROTOCOLS, a_WM_DELETE_WINDOW,
+       a_NET_WM_PING,
+       a_NET_WM_ICON,
+       a_WM_CLIENT_MACHINE,
+       a_UTF8_STRING,
        NR_ATOMS
 };
-static char *atom_names[NR_ATOMS] = {
+static const char *atom_names[NR_ATOMS] = {
+       [a_NONE]                = "NONE",
        [a_WM_STATE]            = "_NET_WM_STATE",
        [a_STATE_FULLSCREEN]    = "_NET_WM_STATE_FULLSCREEN",
+       [a_WM_NAME]             = "WM_NAME",
+       [a_NET_WM_NAME]         = "_NET_WM_NAME",
+       [a_WM_ICON_NAME]        = "WM_ICON_NAME",
+       [a_NET_WM_ICON_NAME]    = "_NET_WM_ICON_NAME",
+       [a_WM_PROTOCOLS]        = "WM_PROTOCOLS",
+       [a_WM_DELETE_WINDOW]    = "WM_DELETE_WINDOW",
+       [a_NET_WM_PING]         = "_NET_WM_PING",
+       [a_NET_WM_ICON]         = "_NET_WM_ICON",
+       [a_WM_CLIENT_MACHINE]   = "WM_CLIENT_MACHINE",
+       [a_UTF8_STRING]         = "UTF8_STRING",
 };
 
 struct rgb {
@@ -156,6 +183,7 @@ struct rgb {
 struct xcb_data {
        xcb_connection_t        *conn safe;
        char                    *display safe;
+       char                    *disp_auth;
 
        const xcb_setup_t       *setup safe;
        const xcb_screen_t      *screen safe;
@@ -167,8 +195,8 @@ struct xcb_data {
        cairo_t                 *cairo safe;
        cairo_surface_t         *surface safe;
        PangoFontDescription    *fd safe;
-       char                    *noclose;
        int                     charwidth, lineheight;
+       cairo_region_t          *need_update;
 
        bool                    motion_blocked;
        bool                    in_focus;
@@ -181,17 +209,27 @@ struct xcb_data {
        struct xkb_compose_table *compose_table;
        struct xkb_keymap       *xkb_keymap;
 
+       struct pids {
+               pid_t           pid;
+               struct pids     *next;
+       }                       *pids;
+
        /* FIXME use hash?? */
        struct panes {
                struct panes    *next;
                struct pane     *p safe;
-               int             w,h;
-               cairo_t         *ctx safe;
+               cairo_rectangle_int_t r;
+               cairo_t         *ctx;
                struct rgb      bg;
                xcb_pixmap_t    draw;
-               cairo_surface_t *surface safe;
-       }                       *panes;
+               cairo_surface_t *surface;
+               cairo_region_t  *need_update;
+       } *panes;
 };
+#include "core-pane.h"
+
+/* panes->r.x is NEVER_DRAWN if the pane has not been drawn */
+#define NEVER_DRAWN (-60000)
 
 static struct map *xcb_map;
 DEF_LOOKUP_CMD(xcb_handle, xcb_map);
@@ -201,49 +239,63 @@ static struct panes *get_pixmap(struct pane *home safe,
 {
        struct xcb_data *xd = home->data;
        struct panes **pp, *ps;
-       cairo_surface_t *surface;
-       cairo_t *ctx = NULL;
 
        for (pp = &xd->panes; (ps = *pp) != NULL; pp = &(*pp)->next) {
                if (ps->p != p)
                        continue;
-               if (ps->w == p->w && ps->h == p->h)
+               if (ps->r.width == p->w && ps->r.height == p->h)
                        return ps;
                *pp = ps->next;
-               cairo_destroy(ps->ctx);
-               cairo_surface_destroy(ps->surface);
-               xcb_free_pixmap(xd->conn, ps->draw);
+               if (ps->r.x != NEVER_DRAWN) {
+                       if (!xd->need_update)
+                               xd->need_update = cairo_region_create();
+                       cairo_region_union_rectangle(xd->need_update, &ps->r);
+               }
+               if (ps->ctx)
+                       cairo_destroy(ps->ctx);
+               if (ps->surface)
+                       cairo_surface_destroy(ps->surface);
+               if (ps->draw)
+                       xcb_free_pixmap(xd->conn, ps->draw);
                free(ps);
                break;
        }
        alloc(ps, pane);
        ps->p = p;
-       ps->w = p->w;
-       ps->h = p->h;
-       ps->bg.g = -1;
-       ps->draw = xcb_generate_id(xd->conn);
-       xcb_create_pixmap(xd->conn, xd->screen->root_depth, ps->draw,
-                         xd->win, p->w, p->h);
-       surface = cairo_xcb_surface_create(
-               xd->conn, ps->draw, xd->visual, p->w, p->h);
-       if (!surface)
-               goto free_ps;
-       ctx = cairo_create(surface);
-       if (!ctx)
-               goto free_surface;
-       ps->ctx = ctx;
-       ps->surface = surface;
+       ps->r.x = ps->r.y = NEVER_DRAWN;
+       ps->r.width = p->w;
+       ps->r.height = p->h;
+       ps->bg.r = ps->bg.g = ps->bg.b = 0;
 
        pane_add_notify(home, p, "Notify:Close");
        ps->next = *pp;
        *pp = ps;
        return ps;
+}
+
+static void instantiate_pixmap(struct xcb_data *xd safe,
+                         struct panes *ps safe)
+{
+       ps->draw = xcb_generate_id(xd->conn);
+       xcb_create_pixmap(xd->conn, xd->screen->root_depth, ps->draw,
+                         xd->win, ps->r.width, ps->r.height);
+       ps->surface = cairo_xcb_surface_create(
+               xd->conn, ps->draw, xd->visual, ps->r.width, ps->r.height);
+       if (!ps->surface)
+               goto free_ps;
+       ps->ctx = cairo_create(ps->surface);
+       if (!ps->ctx)
+               goto free_surface;
+       cairo_set_source_rgb(ps->ctx, ps->bg.r, ps->bg.g, ps->bg.b);
+       cairo_paint(ps->ctx);
+       return;
+
 free_surface:
-       cairo_surface_destroy(surface);
+       cairo_surface_destroy(ps->surface);
+       ps->surface = NULL;
 free_ps:
        xcb_free_pixmap(xd->conn, ps->draw);
-       unalloc(ps, pane);
-       return NULL;
+       ps->draw = 0;
 }
 
 static struct panes *find_pixmap(struct xcb_data *xd safe, struct pane *p safe,
@@ -286,7 +338,7 @@ static void parse_attrs(
        char *fg = NULL, *bg = NULL;
        bool ul = False;
        bool inv = False;
-       int size = 10*1000;
+       int size = 12*1000;
        PangoFontDescription *fd = NULL;
        PangoStyle style = PANGO_STYLE_NORMAL;
        PangoVariant variant = PANGO_VARIANT_NORMAL;
@@ -295,11 +347,11 @@ static void parse_attrs(
        if (fdp) {
                fd = pango_font_description_new();
                *fdp = fd;
-               pango_font_description_set_family_static(fd, "mono");
+               pango_font_description_set_family_static(fd, "monospace");
        }
 
        while ((word = strsep(&ap, ",")) != NULL) {
-               if (fd && strncmp(word, "family:", 7) == 0)
+               if (fd && strstarts(word, "family:"))
                        pango_font_description_set_family(fd, word+7);
                if (strcmp(word, "large") == 0)
                        size = 14 * 1000;
@@ -327,14 +379,18 @@ static void parse_attrs(
                if (strcmp(word, "nobold") == 0)
                        weight = PANGO_WEIGHT_NORMAL;
 
-               if (strncmp(word, "fg:", 3) == 0)
+               if (strstarts(word, "fg:"))
                        fg = word + 3;
-               if (strncmp(word, "bg:", 3) == 0)
+               if (strstarts(word, "bg:"))
                        bg = word + 3;
                if (strcmp(word, "inverse") == 0)
                        inv = True;
+               if (strcmp(word, "noinverse") == 0)
+                       inv = False;
                if (strcmp(word, "underline") == 0)
                        ul = True;
+               if (strcmp(word, "nounderline") == 0)
+                       ul = False;
        }
 
        if (inv) {
@@ -365,7 +421,7 @@ static void parse_attrs(
        } else if (bgp)
                bgp->g = -1;
        if (fd) {
-               pango_font_description_set_size(fd, size * scale / PANGO_SCALE);
+               pango_font_description_set_size(fd, PANGO_SCALE * size /1000 * scale / 1000);
                if (style != PANGO_STYLE_NORMAL)
                        pango_font_description_set_style(fd, style);
                if (variant != PANGO_VARIANT_NORMAL)
@@ -386,41 +442,76 @@ DEF_CB(cnt_disp)
        return 1;
 }
 
-DEF_CMD(xcb_close_display)
+DEF_CMD_CLOSED(xcb_close_display)
 {
        /* If this is only display, then refuse to close this one */
        struct call_return cr;
-       struct xcb_data *xd = ci->home->data;
-       if (xd->noclose) {
-               call("Message", ci->focus, 0, NULL, xd->noclose);
+       char *nc = pane_attr_get(ci->home, "no-close");
+
+       if (nc) {
+               call("Message", ci->focus, 0, NULL, nc);
                return 1;
        }
        cr.c = cnt_disp;
        cr.i = 0;
        call_comm("editor:notify:all-displays", ci->focus, &cr.c);
        if (cr.i > 1)
-               pane_close(ci->home);
+               return Efallthrough;
        else
                call("Message", ci->focus, 0, NULL,
                     "Cannot close only window.");
        return 1;
 }
 
-DEF_CMD(xcb_set_noclose)
+static void wait_for(struct xcb_data *xd safe)
 {
-       struct xcb_data *xd = ci->home->data;
-
-       free(xd->noclose);
-       xd->noclose = NULL;
-       if (ci->str)
-               xd->noclose = strdup(ci->str);
-       return 1;
+       struct pids **pp = &xd->pids;
+
+       while (*pp) {
+               struct pids *p = *pp;
+               if (waitpid(p->pid, NULL, WNOHANG) > 0) {
+                       *pp = p->next;
+                       free(p);
+               } else
+                       pp = &p->next;
+       }
 }
 
 DEF_CMD(xcb_external_viewer)
 {
-       //struct xcb_data *xd = ci->home->data;
-       //FIXME
+       struct xcb_data *xd = ci->home->data;
+       const char *path = ci->str;
+       struct pids *p;
+       int pid;
+       int fd;
+
+       if (!path)
+               return Enoarg;
+       switch (pid = fork()) {
+       case -1:
+               return Efail;
+       case 0: /* Child */
+               setenv("DISPLAY", xd->display, 1);
+               if (xd->disp_auth)
+                       setenv("XAUTHORITY", xd->disp_auth, 1);
+               fd = open("/dev/null", O_RDWR);
+               if (fd) {
+                       dup2(fd, 0);
+                       dup2(fd, 1);
+                       dup2(fd, 2);
+                       if (fd > 2)
+                               close(fd);
+               }
+               execlp("xdg-open", "xdg-open", path, NULL);
+               exit(1);
+       default: /* parent */
+               p = malloc(sizeof(*p));
+               p->pid = pid;
+               p->next = xd->pids;
+               xd->pids = p;
+               break;
+       }
+       wait_for(xd);
        return 1;
 }
 
@@ -449,12 +540,40 @@ DEF_CMD(xcb_fullscreen)
        return 1;
 }
 
-DEF_CMD(xcb_close)
+static void panes_free(struct xcb_data *xd safe)
+{
+       while (xd->panes) {
+               struct panes *ps = xd->panes;
+               xd->panes = ps->next;
+               if (ps->ctx)
+                       cairo_destroy(ps->ctx);
+               if (ps->surface)
+                       cairo_surface_destroy(ps->surface);
+               if (ps->draw)
+                       xcb_free_pixmap(xd->conn, ps->draw);
+               free(ps);
+       }
+}
+
+static void kbd_free(struct xcb_data *xd safe);
+
+DEF_CMD_CLOSED(xcb_close)
 {
        struct xcb_data *xd = ci->home->data;
+
        xcb_destroy_window(xd->conn, xd->win);
+       kbd_free(xd);
+       panes_free(xd);
+
+       pango_font_description_free(xd->fd);
+       cairo_destroy(xd->cairo);
+       cairo_device_finish(cairo_surface_get_device(xd->surface));
+       cairo_surface_destroy(xd->surface);
+       free(xd->display);
+       free(xd->disp_auth);
        xcb_disconnect(xd->conn);
-       /* free stuff */
+       if (xd->need_update)
+               cairo_region_destroy(xd->need_update);
        return 1;
 }
 
@@ -464,7 +583,8 @@ DEF_CMD(xcb_clear)
        const char *attr = ci->str;
        struct panes *src = NULL, *dest;
        struct rgb bg;
-       int x, y;
+       int x=0, y=0;
+       cairo_rectangle_int_t r;
 
        if (attr) {
                parse_attrs(ci->home, attr, PANGO_SCALE, NULL, &bg, NULL, NULL);
@@ -478,6 +598,8 @@ DEF_CMD(xcb_clear)
                        bg.r = bg.g = bg.b = 1.0;
                else if (src->bg.g >= 0)
                        bg = src->bg;
+               else if (src->surface == NULL)
+                       bg.r = bg.g = bg.b = 1.0;
                else
                        bg.g = -1;
        }
@@ -486,18 +608,29 @@ DEF_CMD(xcb_clear)
        if (!dest)
                return 1;
        if (bg.g >= 0) {
-               cairo_set_source_rgb(dest->ctx, bg.r, bg.g, bg.b);
-               cairo_rectangle(dest->ctx, 0.0, 0.0,
-                               (double)ci->focus->w, (double)ci->focus->h);
-               cairo_fill(dest->ctx);
+               if (dest->ctx) {
+                       cairo_set_source_rgb(dest->ctx, bg.r, bg.g, bg.b);
+                       cairo_paint(dest->ctx);
+               }
                dest->bg = bg;
        } else if (src) {
-               cairo_set_source_surface(dest->ctx, src->surface, -x, -y);
-               cairo_paint(dest->ctx);
-               dest->bg.g = -1;
-       } else
-               LOG("ERROR neither src or bg");
+               if (!dest->ctx)
+                       instantiate_pixmap(xd, dest);
+               if (dest->ctx) {
+                       cairo_set_source_surface(dest->ctx, src->surface, -x, -y);
+                       cairo_paint(dest->ctx);
+                       dest->bg.g = -1;
+               }
+       }
        pane_damaged(ci->home, DAMAGED_POSTORDER);
+
+       if (!dest->need_update)
+               dest->need_update = cairo_region_create();
+       r.x = 0;
+       r.y = 0;
+       r.width = ci->focus->w;
+       r.height = ci->focus->h;
+       cairo_region_union_rectangle(dest->need_update, &r);
        return 1;
 }
 
@@ -515,8 +648,10 @@ DEF_CMD(xcb_text_size)
 
        if (scale <= 0)
                scale = 1000;
+       if (!utf8_valid(str))
+               str = "*INV*";
        parse_attrs(ci->home, attr, scale, NULL, NULL, NULL, &fd);
-       /* If we use an empty string, line-height it wrong */
+       /* If we use an empty string, line-height is wrong */
        layout = pango_cairo_create_layout(xd->cairo);
        pango_layout_set_text(layout, *str ? str : "M", -1);
        pango_layout_set_font_description(layout, fd);
@@ -528,7 +663,7 @@ DEF_CMD(xcb_text_size)
        else if (log.width <= ci->num)
                max_bytes = strlen(str);
        else
-               pango_layout_xy_to_index(layout, 1000*ci->num,
+               pango_layout_xy_to_index(layout, PANGO_SCALE*ci->num,
                                         baseline, &max_bytes, NULL);
 
        comm_call(ci->comm2, "cb", ci->focus, max_bytes, NULL, NULL,
@@ -563,8 +698,15 @@ DEF_CMD(xcb_draw_text)
        ps = find_pixmap(xd, ci->focus, &xo, &yo);
        if (!ps)
                return Einval;
+       if (!ps->ctx)
+               instantiate_pixmap(xd, ps);
        ps->bg.g = -1;
        ctx = ps->ctx;
+       if (!ctx)
+               return Efail;
+
+       if (!utf8_valid(str))
+               str = "*INV*";
 
        pane_damaged(ci->home, DAMAGED_POSTORDER);
 
@@ -580,6 +722,7 @@ DEF_CMD(xcb_draw_text)
        pango_layout_set_font_description(layout, fd);
        pango_layout_get_pixel_extents(layout, NULL, &log);
        baseline = pango_layout_get_baseline(layout) / PANGO_SCALE;
+       cairo_save(ctx);
        if (bg.g >= 0) {
                cairo_set_source_rgb(ctx, bg.r, bg.g, bg.b);
                cairo_rectangle(ctx, x+log.x, y - baseline + log.y,
@@ -605,6 +748,7 @@ DEF_CMD(xcb_draw_text)
                PangoRectangle curs;
                bool in_focus = xd->in_focus;
                struct pane *f = ci->focus;
+               double cx, cy, cw, ch;
 
                pango_layout_index_to_pos(layout, ci->num, &curs);
                if (curs.width <= 0) {
@@ -613,11 +757,6 @@ DEF_CMD(xcb_draw_text)
                        pango_layout_get_extents(layout, NULL, &log);
                        curs.width = log.width;
                }
-               cairo_rectangle(ctx, x+curs.x/PANGO_SCALE, y-baseline+curs.y/PANGO_SCALE,
-                               (curs.width - PANGO_SCALE/2) / PANGO_SCALE,
-                               (curs.height - PANGO_SCALE/2) / PANGO_SCALE);
-               cairo_set_line_width(ctx, 1.0);
-               cairo_stroke(ctx);
 
                while (in_focus && f->parent->parent != f &&
                       f->parent != ci->home) {
@@ -625,10 +764,21 @@ DEF_CMD(xcb_draw_text)
                                in_focus = False;
                        f = f->parent;
                }
-               if (in_focus) {
-                       if (fg.g >= 0)
-                               cairo_set_source_rgb(ctx, fg.r, fg.g, fg.b);
-                       cairo_rectangle(ctx, x+curs.x/PANGO_SCALE,
+               if (!in_focus) {
+                       /* Just an fg:rectangle around the fg:text */
+                       /* Add half to x,y as stroke is either side of the line */
+                       cx = x * PANGO_SCALE + curs.x + PANGO_SCALE/2;
+                       cy = (y - baseline) * PANGO_SCALE + curs.y + PANGO_SCALE/2;
+                       ch = curs.height - PANGO_SCALE;
+                       cw = curs.width - PANGO_SCALE;
+                       cairo_rectangle(ctx, cx/PANGO_SCALE, cy/PANGO_SCALE,
+                                       cw/PANGO_SCALE, ch/PANGO_SCALE);
+                       cairo_set_line_width(ctx, 1.0);
+                       cairo_stroke(ctx);
+               } else {
+                       /* solid fd:block with txt in bg color */
+                       cairo_rectangle(ctx,
+                                       x+curs.x/PANGO_SCALE,
                                        y-baseline+curs.y/PANGO_SCALE,
                                        curs.width / PANGO_SCALE,
                                        curs.height / PANGO_SCALE);
@@ -649,53 +799,120 @@ DEF_CMD(xcb_draw_text)
                        }
                }
        }
+       cairo_restore(ctx);
        pango_font_description_free(fd);
        g_object_unref(layout);
        return 1;
 }
 
+struct di_info {
+       struct command c;
+       MagickWand *wd safe;
+       int x,y,w,h;
+       int xo, yo;
+       struct panes *ps safe;
+};
+
+DEF_CB(xcb_draw_image_cb)
+{
+       struct di_info *dii = container_of(ci->comm, struct di_info, c);
+       int stride;
+       int fmt[2];
+       unsigned char *buf;
+       cairo_surface_t *surface;
+
+       switch (ci->key[0]) {
+       case 'w': /* width */
+               return MagickGetImageWidth(dii->wd);
+       case 'h': /* height */
+               return MagickGetImageHeight(dii->wd);
+       case 's': /* scale */
+               MagickResizeImage(dii->wd, ci->num, ci->num2, BoxFilter, 1.0);
+               return 1;
+       case 'c': /* crop or cursor */
+               if (ci->key[1] != 'u') {
+                       /* crop */
+                       dii->x = ci->x;
+                       dii->y = ci->y;
+                       dii->w = ci->num;
+                       dii->h = ci->num2;
+                       return 1;
+               } else {
+                       /* cursor */
+                       cairo_rectangle(dii->ps->ctx,
+                                       ci->x + dii->xo, ci->y + dii->yo,
+                                       ci->num, ci->num2);
+                       cairo_set_line_width(dii->ps->ctx, 1.0);
+                       cairo_set_source_rgb(dii->ps->ctx, 1.0, 0.0, 0.0);
+                       cairo_stroke(dii->ps->ctx);
+                       return 1;
+               }
+       case 'd': /* draw */
+               if (dii->w <= 0 || dii->h <= 0)
+                       return Efail;
+               stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24,
+                                                      dii->w);
+               buf = malloc(dii->h * stride);
+               // Cairo expects 32bit values with A in the high byte, then RGB.
+               // Magick provides 8bit values in the order requests.
+               // So depending on byte order, a different string is needed
+
+               fmt[0] = ('A'<<24) | ('R' << 16) | ('G' << 8) | ('B' << 0);
+               fmt[1] = 0;
+               MagickExportImagePixels(dii->wd, dii->x, dii->y,
+                                       dii->w, dii->h,
+                                       (char*)fmt, CharPixel, buf);
+               surface = cairo_image_surface_create_for_data(
+                       buf, CAIRO_FORMAT_ARGB32, dii->w, dii->h, stride);
+               cairo_set_source_surface(dii->ps->ctx, surface,
+                                        ci->num + dii->xo, ci->num2 + dii->yo);
+               cairo_paint(dii->ps->ctx);
+               cairo_surface_destroy(surface);
+               free(buf);
+               return 1;
+       default:
+               return Efail;
+       }
+}
+
 DEF_CMD(xcb_draw_image)
 {
        /* 'str' identifies the image. Options are:
         *     file:filename  - load file from fs
         *     comm:command   - run command collecting bytes
-        * 'num' is '1' if image should be stretched to fill pane
-        * if 'num is '0', then 'num2' is 'or' of
-        *   0,1,2 for left/middle/right in x direction
-        *   0,4,8 for top/middle/bottom in y direction
-        * only one of these can be used as image will fill pane in other direction.
+        * 'str2' and numbers are handled by Draw:scale-image.
         */
        struct xcb_data *xd = ci->home->data;
-       bool stretch = ci->num == 1;
-       int pos = ci->num2;
-       int w = ci->focus->w, h = ci->focus->h;
-       int x = 0, y = 0;
-       int xo, yo;
-       int stride;
+       struct di_info dii;
+       MagickWand *wd = NULL;
        struct panes *ps;
-       MagickBooleanType status;
-       MagickWand *wd;
-       int fmt[2];
-       unsigned char *buf;
-       cairo_surface_t *surface;
 
        if (!ci->str)
                return Enoarg;
-       ps = find_pixmap(xd, ci->focus, &xo, &yo);
+       ps = find_pixmap(xd, ci->focus, &dii.xo, &dii.yo);
        if (!ps)
                return Einval;
-       ps->bg.g = -1;
-       if (strncmp(ci->str, "file:", 5) == 0) {
+       dii.ps = ps;
+       if (!dii.ps->ctx)
+               instantiate_pixmap(xd, dii.ps);
+       dii.ps->bg.g = -1;
+       if (!dii.ps->ctx)
+               return Efail;
+       if (strstarts(ci->str, "file:")) {
+               MagickBooleanType status;
+
                wd = NewMagickWand();
                status = MagickReadImage(wd, ci->str + 5);
                if (status == MagickFalse) {
                        DestroyMagickWand(wd);
                        return Efail;
                }
-       } else if (strncmp(ci->str, "comm:", 5) == 0) {
+       } else if (strstarts(ci->str, "comm:")) {
+               MagickBooleanType status;
                struct call_return cr;
+
                wd = NewMagickWand();
-               cr = call_ret(bytes, ci->str+5, ci->focus, 0, NULL, ci->str2);
+               cr = call_ret(bytes, ci->str+5, ci->focus);
                if (!cr.s) {
                        DestroyMagickWand(wd);
                        return Efail;
@@ -706,60 +923,66 @@ DEF_CMD(xcb_draw_image)
                        DestroyMagickWand(wd);
                        return Efail;
                }
-       } else
+       }
+
+       if (!wd)
                return Einval;
 
        MagickAutoOrientImage(wd);
-       if (!stretch) {
-               int ih = MagickGetImageHeight(wd);
-               int iw = MagickGetImageWidth(wd);
+       dii.c = xcb_draw_image_cb;
+       dii.wd = wd;
+       dii.w = dii.h = dii.x = dii.y = dii.xo = dii.yo = 0;
+       call("Draw:scale-image", ci->focus,
+            ci->num, NULL, NULL, ci->num2, NULL, ci->str2,
+            ci->x, ci->y, &dii.c);
+
+       DestroyMagickWand(wd);
+
+       pane_damaged(ci->home, DAMAGED_POSTORDER);
 
-               if (iw <= 0 || iw <= 0) {
+       return 1;
+}
+
+DEF_CMD(xcb_image_size)
+{
+       MagickBooleanType status;
+       MagickWand *wd;
+       int ih, iw;
+
+       if (!ci->str)
+               return Enoarg;
+       if (strstarts(ci->str, "file:")) {
+               wd = NewMagickWand();
+               status = MagickReadImage(wd, ci->str + 5);
+               if (status == MagickFalse) {
                        DestroyMagickWand(wd);
                        return Efail;
                }
-               if (iw * h > ih * w) {
-                       /* Image is wider than space, use less height */
-                       ih = ih * w / iw;
-                       switch(pos & (8+4)) {
-                       case 4: /* center */
-                               y = (h - ih) / 2; break;
-                       case 8: /* bottom */
-                               y = h - ih; break;
-                       }
-                       h = ih;
-               } else {
-                       /* image is too tall, use less width */
-                       iw = iw * h / ih;
-                       switch (pos & (1+2)) {
-                       case 1: /* center */
-                               x = (w - iw) / 2; break;
-                       case 2: /* right */
-                               x = w - iw ; break;
-                       }
-                       w = iw;
+       } else if (strstarts(ci->str, "comm:")) {
+               struct call_return cr;
+               wd = NewMagickWand();
+               cr = call_ret(bytes, ci->str+5, ci->focus);
+               if (!cr.s) {
+                       DestroyMagickWand(wd);
+                       return Efail;
                }
-       }
-       MagickAdaptiveResizeImage(wd, w, h);
-       stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w);
-       buf = malloc(h * stride);
-       // Cairo expects 32bit values with A in the high byte, then RGB.
-       // Magick provides 8bit values in the order requests.
-       // So depending on byte order, a different string is needed
-       
-       fmt[0] = ('A'<<24) | ('R' << 16) | ('G' << 8) | ('B' << 0);
-       fmt[1] = 0;
-       MagickExportImagePixels(wd, 0, 0, w, h, (char*)fmt, CharPixel, buf);
-       surface = cairo_image_surface_create_for_data(buf, CAIRO_FORMAT_ARGB32,
-                                                     w, h, stride);
-       cairo_set_source_surface(ps->ctx, surface, x + xo, y + yo);
-       cairo_paint(ps->ctx);
-       cairo_surface_destroy(surface);
-       free(buf);
-       DestroyMagickWand(wd);
+               status = MagickReadImageBlob(wd, cr.s, cr.i);
+               free(cr.s);
+               if (status == MagickFalse) {
+                       DestroyMagickWand(wd);
+                       return Efail;
+               }
+       } else
+               return Einval;
 
-       pane_damaged(ci->home, DAMAGED_POSTORDER);
+       MagickAutoOrientImage(wd);
+       ih = MagickGetImageHeight(wd);
+       iw = MagickGetImageWidth(wd);
 
+       DestroyMagickWand(wd);
+       comm_call(ci->comm2, "callback:size", ci->focus,
+                 0, NULL, NULL, 0, NULL, NULL,
+                 iw, ih);
        return 1;
 }
 
@@ -779,10 +1002,10 @@ static struct panes *sort_split(struct panes *p)
                 * 'end', and make 'end' point to &p->next.
                 */
                next = p->next;
-               if (p->p->abs_z <= next->p->abs_z)
-                       continue;
-               *end = next;
-               end = &p->next;
+               if (p->p->abs_z < next->p->abs_z) {
+                       *end = next;
+                       end = &p->next;
+               }
        }
        *end = NULL;
        return ret;
@@ -792,28 +1015,29 @@ static struct panes *sort_merge(struct panes *p1, struct panes *p2)
 {
        /* merge p1 and p2 and return result */
        struct panes *ret, **end = &ret;
-       int lastz = -100;
+       struct panes *prev = NULL;
 
        while (p1 && p2) {
-               /* if both arg large or smaller than lastz, choose
-                * least, else choose largest
+               /* Make p1 the largest (or be added first.
+                * Then in prev is between them add p2, else p1
                 */
-               struct panes *lo, *hi, *choice;
-               if (p1->p->abs_z <= p2->p->abs_z) {
-                       lo = p1; hi = p2;
-               } else {
-                       lo = p2; hi = p1;
+               if (p1->p->abs_z < p2->p->abs_z) {
+                       struct panes *t = p1;
+                       p1 = p2;
+                       p2 = t;
                }
-               if (lo->p->abs_z >= lastz || hi->p->abs_z <= lastz)
-                       choice = lo;
-               else
-                       choice = hi;
-               *end = choice;
-               end = &choice->next;
-               if (choice == p1)
-                       p1 = p1->next;
-               else
+               if (prev &&
+                   p1->p->abs_z > prev->p->abs_z &&
+                   prev->p->abs_z >= p2->p->abs_z) {
+                       /* p2 is the better choice */
+                       prev = p2;
                        p2 = p2->next;
+               } else {
+                       prev = p1;
+                       p1 = p1->next;
+               }
+               *end = prev;
+               end = &prev->next;
        }
        if (p1)
                *end = p1;
@@ -832,28 +1056,96 @@ DEF_CMD(xcb_refresh_post)
        while ((ps = sort_split(xd->panes)) != NULL)
                xd->panes = sort_merge(xd->panes, ps);
 
-       /* Now copy all panes onto the window */
-       for (ps = xd->panes; ps; ps = ps->next) {
-               double lox, hix, loy, hiy;
+       /* Then merge all update rectanges, checking for movement */
+       if (!xd->need_update)
+               xd->need_update = cairo_region_create();
+       for (ps = xd->panes; ps ; ps = ps->next)
+       {
+               struct xy rel;
+
+               rel = pane_mapxy(ps->p, ci->home, 0, 0, False);
+               if (ps->r.x == NEVER_DRAWN) {
+                       ps->r.x = rel.x;
+                       ps->r.y = rel.y;
+                       cairo_region_union_rectangle(xd->need_update, &ps->r);
+               } else if (rel.x != ps->r.x || rel.y != ps->r.y) {
+                       /* Moved, so refresh all.
+                        * This rectangle might be too big if it is clipped,
+                        * but that doesn't really matter.
+                        */
+                       cairo_region_union_rectangle(xd->need_update, &ps->r);
+                       ps->r.x = rel.x;
+                       ps->r.y = rel.y;
+                       cairo_region_union_rectangle(xd->need_update, &ps->r);
+               } else if (ps->need_update) {
+                       cairo_region_translate(ps->need_update, rel.x, rel.y);
+                       cairo_region_union(xd->need_update, ps->need_update);
+               }
+               if (ps->need_update)
+                       cairo_region_destroy(ps->need_update);
+               ps->need_update = NULL;
+       }
+       /* Now copy all panes onto the window where an update is needed */
+       for (ps = xd->panes; ps ; ps = ps->next) {
                struct xy rel, lo, hi;
+               cairo_region_t *cr;
+               cairo_rectangle_int_t r;
+               int nr, i;
+
+               cr = cairo_region_copy(xd->need_update);
 
                rel = pane_mapxy(ps->p, ci->home, 0, 0, False);
-               lo = pane_mapxy(ps->p, ci->home, 0, 0, True);
-               hi = pane_mapxy(ps->p, ci->home, ps->p->w, ps->p->h, True);
-               lox = lo.x; loy = lo.y;
-               hix = hi.x; hiy = hi.y;
+
                cairo_save(xd->cairo);
-               cairo_set_source_surface(xd->cairo, ps->surface,
-                                        rel.x, rel.y);
-               cairo_rectangle(xd->cairo, lox, loy, hix-lox, hiy-loy);
-               cairo_fill(xd->cairo);
+               if (ps->bg.g >= 0)
+                       cairo_set_source_rgb(xd->cairo,
+                                            ps->bg.r, ps->bg.g, ps->bg.b);
+               else
+                       cairo_set_source_surface(xd->cairo, ps->surface,
+                                                rel.x, rel.y);
+
+               lo = pane_mapxy(ps->p, ci->home, 0, 0, True);
+               hi = pane_mapxy(ps->p, ci->home, ps->r.width, ps->r.height, True);
+               r.x = lo.x; r.y = lo.y;
+               r.width = hi.x - lo.x; r.height = hi.y - lo.y;
+               cairo_region_intersect_rectangle(cr, &r);
+               cairo_region_subtract_rectangle(xd->need_update, &r);
+               nr = cairo_region_num_rectangles(cr);
+               for (i = 0; i < nr; i++) {
+                       cairo_region_get_rectangle(cr, i, &r);
+                       cairo_rectangle(xd->cairo, r.x, r.y, r.width, r.height);
+                       cairo_fill(xd->cairo);
+               }
                cairo_restore(xd->cairo);
        }
+
+       cairo_region_destroy(xd->need_update);
+       xd->need_update = NULL;
        time_stop(TIME_WINDOW);
        xcb_flush(xd->conn);
        return 1;
 }
 
+DEF_CMD(xcb_refresh_size)
+{
+       /* FIXME: should I consider resizing the window?
+        * For now, just ensure we redraw everything.
+        */
+       struct xcb_data *xd = ci->home->data;
+       cairo_rectangle_int_t r = {
+               .x = 0,
+               .y = 0,
+               .width = ci->home->w,
+               .height = ci->home->h,
+       };
+
+       if (!xd->need_update)
+               xd->need_update = cairo_region_create();
+       cairo_region_union_rectangle(xd->need_update, &r);
+       /* Ask common code to notify children */
+       return Efallthrough;
+}
+
 DEF_CMD(xcb_pane_close)
 {
        struct xcb_data *xd = ci->home->data;
@@ -862,8 +1154,16 @@ DEF_CMD(xcb_pane_close)
        for (pp = &xd->panes; (ps = *pp) != NULL; pp = &(*pp)->next) {
                if (ps->p != ci->focus)
                        continue;
+
+               if (!xd->need_update)
+                       xd->need_update = cairo_region_create();
+               if (ps->r.x != NEVER_DRAWN)
+                       cairo_region_union_rectangle(xd->need_update, &ps->r);
+
                *pp = ps->next;
                ps->next = NULL;
+               if (ps->need_update)
+                       cairo_region_destroy(ps->need_update);
                cairo_destroy(ps->ctx);
                cairo_surface_destroy(ps->surface);
                xcb_free_pixmap(xd->conn, ps->draw);
@@ -889,6 +1189,8 @@ static void handle_button(struct pane *home safe,
        char mod[2+2+2+1];
        char key[2+2+2+9+1+1];
 
+       xcb_set_input_focus(xd->conn, XCB_INPUT_FOCUS_POINTER_ROOT,
+                           xd->win, XCB_CURRENT_TIME);
        mod[0] = 0;
        if (press) {
                xd->motion_blocked = False;
@@ -928,6 +1230,10 @@ static void handle_motion(struct pane *home safe,
                   3, NULL, NULL, x, y);
        if (ret <= 0)
                xd->motion_blocked = True;
+
+       /* This doesn't seem to be needed, but the spec says
+        * I should do this when using POINTER_MOTION_HINT
+        */
        c = xcb_query_pointer(xd->conn, xd->win);
        qpr = xcb_query_pointer_reply(xd->conn, c, NULL);
        free(qpr);
@@ -941,7 +1247,7 @@ static void handle_focus(struct pane *home safe, xcb_focus_in_event_t *fie safe)
        struct mark *pt;
 
        xd->in_focus = in;
-       p = pane_leaf(home);
+       p = pane_focus(home);
        pt = call_ret(mark, "doc:point", p);
        if (pt)
                call("view:changed", p, 0, pt);
@@ -1130,6 +1436,8 @@ static void handle_key_press(struct pane *home safe,
        char                            mods[32];
        bool                            shift=False, ctrl=False, alt=False;
 
+       xd->last_event = time(NULL);
+
        keysym = xkb_state_key_get_one_sym(xd->xkb_state,
                                           keycode);
        if (xd->compose_state)
@@ -1199,7 +1507,7 @@ static void handle_key_press(struct pane *home safe,
                        s[1] += '@';
                        if (s[1] < 'A' || s[1] > 'Z')
                                shift = False;
-               } else if (s[0] == '-' && !s[1]) {
+               } else if (s[0] == '-' && !s[1] && strcmp(key, "space") == 0) {
                        /* 'nul' becomes "C- " (ctrl-space) */
                        ctrl = True;
                        s[1] = ' ';
@@ -1268,12 +1576,67 @@ static void handle_configure(struct pane *home safe,
        cairo_xcb_surface_set_size(xd->surface, cne->width, cne->height);
 }
 
+static void handle_expose(struct pane *home safe,
+                         xcb_expose_event_t *ee safe)
+{
+       struct xcb_data *xd = home->data;
+       cairo_rectangle_int_t r = {
+               .x = ee->x,
+               .y = ee->y,
+               .width = ee->width,
+               .height = ee->height,
+       };
+
+       if (!xd->need_update)
+               xd->need_update = cairo_region_create();
+       cairo_region_union_rectangle(xd->need_update, &r);
+       if (ee->count == 0)
+               pane_damaged(home, DAMAGED_POSTORDER);
+}
+
+static void handle_client_message(struct pane *home safe,
+                                 xcb_client_message_event_t *cme safe)
+{
+       struct xcb_data *xd = home->data;
+
+       if (cme->type == xd->atoms[a_WM_PROTOCOLS] &&
+           cme->format == 32 &&
+           cme->window == xd->win &&
+           cme->data.data32[0] == xd->atoms[a_WM_DELETE_WINDOW]) {
+               call("Window:close", pane_focus(home));
+               return;
+       }
+
+       if (cme->type == xd->atoms[a_WM_PROTOCOLS] &&
+           cme->format == 32 &&
+           cme->window == xd->win &&
+           cme->data.data32[0] == xd->atoms[a_NET_WM_PING]) {
+               cme->window = xd->screen->root;
+               xcb_send_event(xd->conn, 0, xd->screen->root,
+                              XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
+                              XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
+                              (void*)cme);
+               return;
+       }
+       LOG("x11 %s got unexpected client message type=%d/%d win=%x data=%d",
+           xd->display,
+           cme->type, cme->format, cme->window, cme->data.data32[0]);
+
+}
+
 DEF_CMD(xcb_input)
 {
        struct xcb_data *xd = ci->home->data;
        xcb_generic_event_t *ev;
+       int ret = 1;
+
+       wait_for(xd);
+       if (ci->num < 0)
+               /* This is a poll - only return 1 on something happening */
+               ret = Efalse;
 
        while ((ev = xcb_poll_for_event(xd->conn)) != NULL) {
+               ret = 1;
                switch (ev->response_type & 0x7f) {
                case XCB_KEY_PRESS:
                        time_start(TIME_KEY);
@@ -1301,34 +1664,147 @@ DEF_CMD(xcb_input)
                        time_stop(TIME_WINDOW);
                        break;
                case XCB_EXPOSE:
-                       pane_damaged(ci->home, DAMAGED_POSTORDER);
+                       time_start(TIME_WINDOW);
+                       handle_expose(ci->home, (void*)ev);
+                       time_stop(TIME_WINDOW);
                        break;
                case XCB_CONFIGURE_NOTIFY:
                        time_start(TIME_WINDOW);
                        handle_configure(ci->home, (void*)ev);
                        time_stop(TIME_WINDOW);
                        break;
+               case XCB_CLIENT_MESSAGE:
+                       time_start(TIME_WINDOW);
+                       handle_client_message(ci->home, (void*)ev);
+                       time_stop(TIME_WINDOW);
+                       break;
                case XCB_REPARENT_NOTIFY:
                        /* Not interested */
                        break;
                case XCB_MAP_NOTIFY:
                case XCB_UNMAP_NOTIFY:
+               case XCB_MAPPING_NOTIFY:
                        /* FIXME what to do?? */
                        break;
+               case 0:
+                       /* Don't know what this means, but I get a lot
+                        * of them so I don't want to log that it was
+                        * ignored.
+                        */
+                       break;
                default:
                        if ((ev->response_type & 0x7f) ==
                            xd->first_xkb_event) {
                                handle_xkb_event(ci->home, ev);
                                break;
                        }
-                       LOG("ignored %x", ev->response_type);
+                       LOG("Ignored X11 event %d", ev->response_type);
                }
                xcb_flush(xd->conn);
        }
-       return 1;
+       if (xcb_connection_has_error(xd->conn)) {
+               call("Window:close", ci->home->parent);
+               pane_close(ci->home);
+       }
+       return ret;
+}
+
+static void set_str_prop(struct xcb_data *xd safe,
+                        enum my_atoms a, const char *str safe)
+{
+       xcb_change_property(xd->conn,
+                           XCB_PROP_MODE_REPLACE,
+                           xd->win, xd->atoms[a], XCB_ATOM_STRING,
+                           8, strlen(str), str);
+}
+
+static void set_utf8_prop(struct xcb_data *xd safe,
+                        enum my_atoms a, const char *str safe)
+{
+       xcb_change_property(xd->conn,
+                           XCB_PROP_MODE_REPLACE,
+                           xd->win, xd->atoms[a],
+                           xd->atoms[a_UTF8_STRING],
+                           8, strlen(str), str);
+}
+
+static void set_card32_property(struct xcb_data *xd safe,
+                               enum my_atoms a,
+                               const uint32_t *data, int cnt)
+{
+       xcb_change_property(xd->conn,
+                           XCB_PROP_MODE_REPLACE,
+                           xd->win, xd->atoms[a],
+                           XCB_ATOM_CARDINAL, 32,
+                           cnt, data);
+}
+
+static void set_atom_prop(struct xcb_data *xd safe,
+                         enum my_atoms prop, enum my_atoms alist, ...)
+{
+       uint32_t atoms[16];
+       int anum = 0;
+       va_list ap;
+       enum my_atoms a;
+
+       atoms[anum++] = xd->atoms[alist];
+       va_start(ap, alist);
+       while ((a = va_arg(ap, enum my_atoms)) != a_NONE)
+               if (anum < 16)
+                       atoms[anum++] = xd->atoms[a];
+       va_end(ap);
+       xcb_change_property(xd->conn,
+                           XCB_PROP_MODE_REPLACE,
+                           xd->win, xd->atoms[prop],
+                           XCB_ATOM_ATOM,
+                           32, anum, atoms);
 }
 
-static struct pane *xcb_display_init(const char *d safe, struct pane *focus safe)
+static void xcb_load_icon(struct pane *p safe,
+                         struct xcb_data *xd safe,
+                         char *file safe)
+{
+       char *path;
+       int h, w, n;
+       unsigned int *data;
+       MagickBooleanType status;
+       MagickWand *wd;
+       uint32_t fmt[2];
+
+       path = call_ret(str, "xdg-find-edlib-file", p, 0, NULL,
+                       file, 0, NULL, "data");
+       if (!path)
+               return;
+
+       wd = NewMagickWand();
+       status = MagickReadImage(wd, path);
+       free(path);
+       if (status == MagickFalse)
+               goto done;
+
+       h = MagickGetImageHeight(wd);
+       w = MagickGetImageWidth(wd);
+       n = 2 + w*h;
+       data = malloc(sizeof(data[0]) * n);
+       if (!data)
+               goto done;
+       data[0] = w;
+       data[1] = h;
+       /* Need host-endian ARGB data */
+       fmt[0] = ('A'<<24) | ('R' << 16) | ('G' << 8) | ('B' << 0);
+       fmt[1] = 0;
+       MagickExportImagePixels(wd, 0, 0, w, h, (char*)fmt,
+                               CharPixel, data+2);
+       set_card32_property(xd, a_NET_WM_ICON, data, n);
+       free(data);
+done:
+       DestroyMagickWand(wd);
+       return;
+}
+
+static struct pane *xcb_display_init(const char *d safe,
+                                    const char *disp_auth,
+                                    struct pane *focus safe)
 {
        struct xcb_data *xd;
        struct pane *p;
@@ -1337,6 +1813,7 @@ static struct pane *xcb_display_init(const char *d safe, struct pane *focus safe
        xcb_screen_iterator_t iter;
        xcb_depth_iterator_t di;
        char scale[20];
+       char hostname[128];
        uint32_t valwin[2];
        int screen = 0;
        int i;
@@ -1348,17 +1825,25 @@ static struct pane *xcb_display_init(const char *d safe, struct pane *focus safe
        // FIXME SCALE from environ?? or pango_cairo_context_set_resolution dpi
        // 254 * width_in_pixels / width_in_millimeters / 10
 
-       conn = xcb_connect(d, &screen);
-       if (!conn)
+       conn = safe_cast xcb_connect_auth(d, disp_auth, &screen);
+       if (xcb_connection_has_error(conn))
                return NULL;
 
-       alloc(xd, pane);
+       p = call_ret(pane, "attach-window-core", focus);
+       if (!p)
+               return NULL;
+       p = pane_register(p, 1, &xcb_handle.c);
+       if (!p)
+               return NULL;
+       xd = p->data;
 
        xd->motion_blocked = True;
        xd->in_focus = True;
 
        xd->conn = conn;
        xd->display = strdup(d);
+       if (disp_auth)
+               xd->disp_auth = strdup(disp_auth);
        xd->setup = safe_cast xcb_get_setup(conn);
        iter = xcb_setup_roots_iterator(xd->setup);
        for (i = 0; i < screen; i++)
@@ -1372,7 +1857,7 @@ static struct pane *xcb_display_init(const char *d safe, struct pane *focus safe
        xd->visual = xcb_depth_visuals(di.data);
 
        for (i = 0; i < NR_ATOMS; i++) {
-               char *n = atom_names[i];
+               const char *n = atom_names[i];
                if (!n)
                        continue;
                cookies[i] = xcb_intern_atom(conn, 0, strlen(n), n);
@@ -1389,8 +1874,8 @@ static struct pane *xcb_display_init(const char *d safe, struct pane *focus safe
                     XCB_EVENT_MASK_FOCUS_CHANGE |
                     XCB_EVENT_MASK_STRUCTURE_NOTIFY |
                     XCB_EVENT_MASK_EXPOSURE |
-                    XCB_EVENT_MASK_POINTER_MOTION |
-                    // FIXME XCB_EVENT_MASK_POINTER_MOTION_HINT |
+                    XCB_EVENT_MASK_BUTTON_MOTION |
+                    XCB_EVENT_MASK_POINTER_MOTION_HINT |
                     0);
 
        xcb_create_window(conn, XCB_COPY_FROM_PARENT, xd->win,
@@ -1409,15 +1894,15 @@ static struct pane *xcb_display_init(const char *d safe, struct pane *focus safe
        if (!surface)
                goto abort;
        xd->surface = surface;
-       cairo = cairo_create(xd->surface);
-       if (!cairo)
+       cairo = safe_cast cairo_create(xd->surface);
+       if (cairo_status(cairo) != CAIRO_STATUS_SUCCESS)
                goto abort;
        xd->cairo = cairo;
        fd = pango_font_description_new();
        if (!fd)
                goto abort;
        xd->fd = fd;
-       pango_font_description_set_family(xd->fd, "mono");
+       pango_font_description_set_family(xd->fd, "monospace");
        pango_font_description_set_size(xd->fd, 12 * PANGO_SCALE);
 
        layout = pango_cairo_create_layout(xd->cairo);
@@ -1446,21 +1931,42 @@ static struct pane *xcb_display_init(const char *d safe, struct pane *focus safe
        }
 
        /* FIXME set:
-        * WM_NAME
-        * WM_PROTOCOLS WM_DELETE_WINDOW WM_TAKE_FOCUS _NET_WM_PING  _NET_WM_SYN_REQUEST??
+        *
+        * WM_PROTOCOLS _NET_WM_SYN_REQUEST??
         * WM_NORMAL_HINTS WM_HINTS
         * WM_CLIENT_MACHINE
         */
+       set_str_prop(xd, a_WM_NAME, "EdLib");
+       set_utf8_prop(xd, a_NET_WM_NAME, "EdLib");
+       set_str_prop(xd, a_WM_ICON_NAME, "EdLib");
+       set_utf8_prop(xd, a_NET_WM_ICON_NAME, "EdLib");
+       gethostname(hostname, sizeof(hostname));
+       set_str_prop(xd, a_WM_CLIENT_MACHINE, hostname);
+       set_atom_prop(xd, a_WM_PROTOCOLS, a_WM_DELETE_WINDOW, a_NET_WM_PING, 0);
+
+       /* Configure passive grabs - shift, lock, and control only */
+       xcb_grab_button(xd->conn, 0, xd->win,
+                       XCB_EVENT_MASK_BUTTON_PRESS |
+                       XCB_EVENT_MASK_BUTTON_RELEASE |
+                       XCB_EVENT_MASK_BUTTON_MOTION,
+                       XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
+                       XCB_WINDOW_NONE, XCB_CURSOR_NONE,
+                       XCB_BUTTON_INDEX_ANY,
+                       XCB_MOD_MASK_SHIFT |
+                       XCB_MOD_MASK_LOCK |
+                       XCB_MOD_MASK_CONTROL);
+
+       xcb_load_icon(focus, xd, "{COMM}-icon.png");
        xcb_map_window(conn, xd->win);
        xcb_flush(conn);
-       p = pane_register(pane_root(focus), 1, &xcb_handle.c, xd);
-       if (!p)
-               goto abort;
        pane_resize(p, 0, 0, xd->charwidth*80, xd->lineheight*26);
        call_comm("event:read", p, &xcb_input, xcb_get_file_descriptor(conn));
+       call_comm("event:poll", p, &xcb_input);
        attr_set_str(&p->attrs, "DISPLAY", d);
+       attr_set_str(&p->attrs, "XAUTHORITY", disp_auth);
        snprintf(scale, sizeof(scale), "%dx%d", xd->charwidth, xd->lineheight);
        attr_set_str(&p->attrs, "scale:M", scale);
+       xd->last_event = time(NULL);
        call("editor:request:all-displays", p);
        return p;
 abort:
@@ -1468,60 +1974,56 @@ abort:
        cairo_destroy(xd->cairo);
        cairo_surface_destroy(xd->surface);
        xcb_disconnect(conn);
-       // FIXME free stuff;
-       unalloc(xd, pane);
+       free(xd->display);
+       free(xd->disp_auth);
        return NULL;
 }
 
-DEF_CMD(display_xcb)
+DEF_CMD(xcb_new_display)
 {
        struct pane *p;
        const char *d = ci->str;
+       const char *disp_auth = ci->str2;
 
        if (!d)
-               return Enoarg;
-       p = xcb_display_init(d, ci->focus);
-       if (p)
-               return comm_call(ci->comm2, "cb", p);
-       return Efail;
-}
-
-DEF_CMD(xcb_new_display)
-{
-       struct pane *p;
-       char *d = pane_attr_get(ci->focus, "DISPLAY");
+               d = pane_attr_get(ci->focus, "DISPLAY");
+       if (!disp_auth)
+               disp_auth = pane_attr_get(ci->focus, "XAUTHORITY");
+       if (!disp_auth)
+               disp_auth = getenv("XAUTHORITY");
 
        if (!d)
                return Enoarg;
-       p = xcb_display_init(d, ci->focus);
-       if (p)
-               p = call_ret(pane, "editor:activate-display", p);
+       p = xcb_display_init(d, disp_auth, ci->focus);
+       if (strcmp(ci->key, "interactive-cmd-x11window") == 0)
+               p = home_call_ret(pane, p,
+                                 "Window:activate-display", ci->focus);
        if (p)
-               home_call(ci->focus, "doc:attach-view", p, 1);
+               comm_call(ci->comm2, "cb", p);
        return 1;
 }
 
 void edlib_init(struct pane *ed safe)
 {
-       call_comm("global-set-command", ed, &display_xcb, 0, NULL,
+       call_comm("global-set-command", ed, &xcb_new_display, 0, NULL,
                  "attach-display-x11");
        call_comm("global-set-command", ed, &xcb_new_display, 0, NULL,
                  "interactive-cmd-x11window");
 
        xcb_map = key_alloc();
 
-       key_add(xcb_map, "Display:close", &xcb_close_display);
-       key_add(xcb_map, "Display:set-noclose", &xcb_set_noclose);
-       key_add(xcb_map, "Display:external-viewer", &xcb_external_viewer);
-       key_add(xcb_map, "Display:fullscreen", &xcb_fullscreen);
-       key_add(xcb_map, "Display:new", &xcb_new_display);
+       key_add(xcb_map, "Window:close", &xcb_close_display);
+       key_add(xcb_map, "Window:external-viewer", &xcb_external_viewer);
+       key_add(xcb_map, "Window:fullscreen", &xcb_fullscreen);
+       key_add(xcb_map, "Window:new", &xcb_new_display);
 
        key_add(xcb_map, "Close", &xcb_close);
-       key_add(xcb_map, "Free", &edlib_do_free);
        key_add(xcb_map, "Draw:clear", &xcb_clear);
        key_add(xcb_map, "Draw:text-size", &xcb_text_size);
        key_add(xcb_map, "Draw:text", &xcb_draw_text);
        key_add(xcb_map, "Draw:image", &xcb_draw_image);
+       key_add(xcb_map, "Draw:image-size", &xcb_image_size);
+       key_add(xcb_map, "Refresh:size", &xcb_refresh_size);
        key_add(xcb_map, "Refresh:postorder", &xcb_refresh_post);
        key_add(xcb_map, "all-displays", &xcb_notify_display);
        key_add(xcb_map, "Notify:Close", &xcb_pane_close);