]> git.neil.brown.name Git - edlib.git/blob - display-x11-xcb.c
TODO: clean out done items.
[edlib.git] / display-x11-xcb.c
1 /*
2  * Copyright Neil Brown ©2021-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * X11 display driver for edlib, using xcb, cairopango, libxkbcommon etc.
6  *
7  * A different connection to the server will be created for each
8  * display.  Maybe that can be optimised one day.
9  */
10
11 #include <unistd.h>
12 #include <stdlib.h>
13 #include <stdio.h>
14 #include <stdint.h>
15 #include <fcntl.h>
16 #include <string.h>
17 #include <xcb/xcb.h>
18 #include <stdarg.h>
19 #include <sys/wait.h>
20 #ifndef __CHECKER__
21 #include <xcb/xkb.h>
22 #else
23 /* xkb.h has a 'long' in an enum :-( */
24 enum {
25         XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY,
26         XCB_XKB_EVENT_TYPE_MAP_NOTIFY,
27         XCB_XKB_NEW_KEYBOARD_NOTIFY,
28         XCB_XKB_EVENT_TYPE_STATE_NOTIFY,
29         XCB_XKB_MAP_PART_MODIFIER_MAP,
30         XCB_XKB_STATE_PART_MODIFIER_LOCK,
31         XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS,
32         XCB_XKB_STATE_PART_GROUP_BASE,
33         XCB_XKB_MAP_PART_KEY_ACTIONS,
34         XCB_XKB_STATE_PART_GROUP_LATCH,
35         XCB_XKB_MAP_PART_VIRTUAL_MODS,
36         XCB_XKB_STATE_PART_GROUP_LOCK,
37         XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP,
38         XCB_XKB_NKN_DETAIL_KEYCODES,
39         XCB_XKB_MAP_PART_KEY_TYPES,
40         XCB_XKB_MAP_PART_KEY_SYMS,
41         XCB_XKB_STATE_PART_MODIFIER_BASE,
42         XCB_XKB_STATE_PART_MODIFIER_LATCH,
43         XCB_XKB_MAP_NOTIFY,
44         XCB_XKB_STATE_NOTIFY,
45 };
46 typedef uint16_t xcb_xkb_device_spec_t;
47 typedef struct xcb_xkb_select_events_details_t {
48         uint16_t affectNewKeyboard;
49         uint16_t newKeyboardDetails;
50         uint16_t affectState;
51         uint16_t stateDetails;
52         /* and other fields */
53 } xcb_xkb_select_events_details_t;
54 typedef struct xcb_xkb_new_keyboard_notify_event_t {
55         uint8_t         deviceID;
56         uint16_t        changed;
57         /* and other fields */
58 } xcb_xkb_new_keyboard_notify_event_t;
59 typedef struct xcb_xkb_state_notify_event_t {
60         uint8_t         deviceID;
61         uint8_t         baseMods;
62         uint8_t         latchedMods;
63         uint8_t         lockedMods;
64         int16_t         baseGroup;
65         int16_t         latchedGroup;
66         uint8_t         lockedGroup;
67         /* and other fields */
68 } xcb_xkb_state_notify_event_t;
69 typedef struct xcb_xkb_map_notify_event_t {
70         uint8_t         deviceID;
71 } xcb_xkb_map_notify_event_t;
72 xcb_void_cookie_t
73 xcb_xkb_select_events_aux_checked(xcb_connection_t              *c,
74                                   xcb_xkb_device_spec_t         deviceSpec,
75                                   uint16_t                      affectWhich,
76                                   uint16_t                      clear,
77                                   uint16_t                      selectAll,
78                                   uint16_t                      affectMap,
79                                   uint16_t                      map,
80                                   const xcb_xkb_select_events_details_t *details);
81
82 #endif
83 #include <xcb/xcbext.h>
84 #include <ctype.h>
85 #include <math.h>
86 #include <locale.h>
87
88 #include <cairo.h>
89 #include <cairo-xcb.h>
90
91 #include <wand/MagickWand.h>
92 #ifdef __CHECKER__
93 // enums confuse sparse...
94 #define MagickBooleanType int
95 #endif
96
97 #ifndef __CHECKER__
98 #include <pango/pango.h>
99 #include <pango/pangocairo.h>
100 #else
101 typedef struct PangoFontDescription {} PangoFontDescription;
102 typedef struct PangoLayout {} PangoLayout;
103 typedef struct PangoContext {} PangoContext;
104 typedef struct PangoFontMetrics {} PangoFontMetrics;
105 typedef struct PangoRectangle { int x,y,width,height;} PangoRectangle;
106 typedef enum { PANGO_STYLE_NORMAL, PANGO_STYLE_OBLIQUE, PANGO_STYLE_ITALIC
107 } PangoStyle;
108 typedef enum { PANGO_VARIANT_NORMAL, PANGO_VARIANT_SMALL_CAPS } PangoVariant;
109 typedef enum { PANGO_WEIGHT_NORMAL, PANGO_WEIGHT_BOLD } PangoWeight;
110 PangoFontDescription *pango_font_description_new(void);
111 void pango_font_description_set_family_static(PangoFontDescription*, char*);
112 void pango_font_description_set_family(PangoFontDescription*, char*);
113 void pango_font_description_set_size(PangoFontDescription*, int);
114 void pango_font_description_set_style(PangoFontDescription*, PangoStyle);
115 void pango_font_description_set_variant(PangoFontDescription*, PangoVariant);
116 void pango_font_description_set_weight(PangoFontDescription*, PangoWeight);
117 #define PANGO_SCALE (1024)
118
119 PangoLayout *pango_cairo_create_layout(cairo_t*);
120 void g_object_unref(PangoLayout*);
121 PangoContext *pango_cairo_create_context(cairo_t *);
122 void pango_cairo_show_layout(cairo_t *, PangoLayout *);
123 PangoFontMetrics *pango_context_get_metrics(PangoContext*, PangoFontDescription*, void*);
124 void pango_font_description_free(PangoFontDescription*);
125 int pango_font_metrics_get_approximate_char_width(PangoFontMetrics *);
126 int pango_font_metrics_get_ascent(PangoFontMetrics *);
127 int pango_font_metrics_get_descent(PangoFontMetrics *);
128 void pango_font_metrics_unref(PangoFontMetrics *);
129 PangoContext* pango_layout_get_context(PangoLayout *);
130 int pango_layout_get_baseline(PangoLayout *);
131 void pango_layout_get_extents(PangoLayout *, PangoRectangle *, PangoRectangle *);
132 void pango_layout_get_pixel_extents(PangoLayout *, PangoRectangle *, PangoRectangle *);
133 void pango_layout_set_font_description(PangoLayout *, PangoFontDescription *);
134 void pango_layout_set_text(PangoLayout*, const char *, int);
135 void pango_layout_xy_to_index(PangoLayout*, int, int, int*, int*);
136 void pango_layout_index_to_pos(PangoLayout*, int, PangoRectangle*);
137 #endif
138
139 #include <xkbcommon/xkbcommon.h>
140 #include <xkbcommon/xkbcommon-compose.h>
141 #include <xkbcommon/xkbcommon-x11.h>
142
143 #include "xcb.h"
144
145 #undef True
146 #undef False
147
148 #define PANE_DATA_TYPE struct xcb_data
149 #include "core.h"
150
151 enum my_atoms {
152         a_NONE = 0,
153         a_WM_STATE, a_STATE_FULLSCREEN,
154         a_WM_NAME, a_NET_WM_NAME,
155         a_WM_ICON_NAME, a_NET_WM_ICON_NAME,
156         a_WM_PROTOCOLS, a_WM_DELETE_WINDOW,
157         a_NET_WM_PING,
158         a_NET_WM_ICON,
159         a_WM_CLIENT_MACHINE,
160         a_UTF8_STRING,
161         NR_ATOMS
162 };
163 static const char *atom_names[NR_ATOMS] = {
164         [a_NONE]                = "NONE",
165         [a_WM_STATE]            = "_NET_WM_STATE",
166         [a_STATE_FULLSCREEN]    = "_NET_WM_STATE_FULLSCREEN",
167         [a_WM_NAME]             = "WM_NAME",
168         [a_NET_WM_NAME]         = "_NET_WM_NAME",
169         [a_WM_ICON_NAME]        = "WM_ICON_NAME",
170         [a_NET_WM_ICON_NAME]    = "_NET_WM_ICON_NAME",
171         [a_WM_PROTOCOLS]        = "WM_PROTOCOLS",
172         [a_WM_DELETE_WINDOW]    = "WM_DELETE_WINDOW",
173         [a_NET_WM_PING]         = "_NET_WM_PING",
174         [a_NET_WM_ICON]         = "_NET_WM_ICON",
175         [a_WM_CLIENT_MACHINE]   = "WM_CLIENT_MACHINE",
176         [a_UTF8_STRING]         = "UTF8_STRING",
177 };
178
179 struct rgb {
180         double r,g,b;
181 };
182
183 struct xcb_data {
184         xcb_connection_t        *conn safe;
185         char                    *display safe;
186         char                    *disp_auth;
187
188         const xcb_setup_t       *setup safe;
189         const xcb_screen_t      *screen safe;
190         xcb_atom_t              atoms[NR_ATOMS];
191
192         long                    last_event;
193         xcb_window_t            win;
194         xcb_visualtype_t        *visual;
195         cairo_t                 *cairo safe;
196         cairo_surface_t         *surface safe;
197         PangoFontDescription    *fd safe;
198         int                     charwidth, lineheight;
199         cairo_region_t          *need_update;
200
201         bool                    motion_blocked;
202         bool                    in_focus;
203
204         struct xkb_context      *xkb;
205         uint8_t                 first_xkb_event;
206         int32_t                 xkb_device_id;
207         struct xkb_state        *xkb_state;
208         struct xkb_compose_state *compose_state;
209         struct xkb_compose_table *compose_table;
210         struct xkb_keymap       *xkb_keymap;
211
212         struct pids {
213                 pid_t           pid;
214                 struct pids     *next;
215         }                       *pids;
216
217         /* FIXME use hash?? */
218         struct panes {
219                 struct panes    *next;
220                 struct pane     *p safe;
221                 cairo_rectangle_int_t r;
222                 cairo_t         *ctx;
223                 struct rgb      bg;
224                 xcb_pixmap_t    draw;
225                 cairo_surface_t *surface;
226                 cairo_region_t  *need_update;
227         } *panes;
228 };
229 #include "core-pane.h"
230
231 /* panes->r.x is NEVER_DRAWN if the pane has not been drawn */
232 #define NEVER_DRAWN (-60000)
233
234 static struct map *xcb_map;
235 DEF_LOOKUP_CMD(xcb_handle, xcb_map);
236
237 static struct panes *get_pixmap(struct pane *home safe,
238                                 struct pane *p safe)
239 {
240         struct xcb_data *xd = home->data;
241         struct panes **pp, *ps;
242
243         for (pp = &xd->panes; (ps = *pp) != NULL; pp = &(*pp)->next) {
244                 if (ps->p != p)
245                         continue;
246                 if (ps->r.width == p->w && ps->r.height == p->h)
247                         return ps;
248                 *pp = ps->next;
249                 if (ps->r.x != NEVER_DRAWN) {
250                         if (!xd->need_update)
251                                 xd->need_update = cairo_region_create();
252                         cairo_region_union_rectangle(xd->need_update, &ps->r);
253                 }
254                 if (ps->ctx)
255                         cairo_destroy(ps->ctx);
256                 if (ps->surface)
257                         cairo_surface_destroy(ps->surface);
258                 if (ps->draw)
259                         xcb_free_pixmap(xd->conn, ps->draw);
260                 free(ps);
261                 break;
262         }
263         alloc(ps, pane);
264         ps->p = p;
265         ps->r.x = ps->r.y = NEVER_DRAWN;
266         ps->r.width = p->w;
267         ps->r.height = p->h;
268         ps->bg.r = ps->bg.g = ps->bg.b = 0;
269
270         pane_add_notify(home, p, "Notify:Close");
271         ps->next = *pp;
272         *pp = ps;
273         return ps;
274 }
275
276 static void instantiate_pixmap(struct xcb_data *xd safe,
277                           struct panes *ps safe)
278 {
279         ps->draw = xcb_generate_id(xd->conn);
280         xcb_create_pixmap(xd->conn, xd->screen->root_depth, ps->draw,
281                           xd->win, ps->r.width, ps->r.height);
282         ps->surface = cairo_xcb_surface_create(
283                 xd->conn, ps->draw, xd->visual, ps->r.width, ps->r.height);
284         if (!ps->surface)
285                 goto free_ps;
286         ps->ctx = cairo_create(ps->surface);
287         if (!ps->ctx)
288                 goto free_surface;
289         cairo_set_source_rgb(ps->ctx, ps->bg.r, ps->bg.g, ps->bg.b);
290         cairo_paint(ps->ctx);
291         return;
292
293 free_surface:
294         cairo_surface_destroy(ps->surface);
295         ps->surface = NULL;
296 free_ps:
297         xcb_free_pixmap(xd->conn, ps->draw);
298         ps->draw = 0;
299 }
300
301 static struct panes *find_pixmap(struct xcb_data *xd safe, struct pane *p safe,
302                                  int *xp safe, int *yp safe)
303 {
304         int x = 0, y = 0;
305         struct panes *ret = NULL;
306
307         while (!ret && p->parent != p) {
308                 struct panes *ps;
309                 for (ps = xd->panes; ps ; ps = ps->next)
310                         if (ps->p == p) {
311                                 ret = ps;
312                                 break;
313                         }
314                 if (!ret) {
315                         x += p->x;
316                         y += p->y;
317                         p = p->parent;
318                 }
319         }
320         *xp = x;
321         *yp = y;
322         return ret;
323 }
324
325 static inline double cvt(int i)
326 {
327         return (float)i / 1000.0;
328 }
329
330 static void parse_attrs(
331         struct pane *home safe, const char *cattrs, int scale,
332         struct rgb *fgp, struct rgb *bgp, bool *underline,
333         PangoFontDescription **fdp)
334 {
335         char *attrs = strdup(cattrs ?: "");
336         char *ap = attrs;
337         char *word;
338         char *fg = NULL, *bg = NULL;
339         bool ul = False;
340         bool inv = False;
341         int size = 12*1000;
342         PangoFontDescription *fd = NULL;
343         PangoStyle style = PANGO_STYLE_NORMAL;
344         PangoVariant variant = PANGO_VARIANT_NORMAL;
345         PangoWeight weight = PANGO_WEIGHT_NORMAL;
346
347         if (fdp) {
348                 fd = pango_font_description_new();
349                 *fdp = fd;
350                 pango_font_description_set_family_static(fd, "monospace");
351         }
352
353         while ((word = strsep(&ap, ",")) != NULL) {
354                 if (fd && strstarts(word, "family:"))
355                         pango_font_description_set_family(fd, word+7);
356                 if (strcmp(word, "large") == 0)
357                         size = 14 * 1000;
358                 if (strcmp(word, "small") == 0)
359                         size = 9 * 1000;
360                 if (isdigit(word[0])) {
361                         char *end = NULL;
362                         double s = strtod(word, &end);
363                         if (end && end != word && !*end)
364                                 size = trunc(s * 1000.0);
365                         else
366                                 size = 10*1000;
367                 }
368                 if (strcmp(word, "oblique") == 0)
369                         style = PANGO_STYLE_OBLIQUE;
370                 if (strcmp(word, "italic") == 0)
371                         style = PANGO_STYLE_ITALIC;
372                 if (strcmp(word, "normal") == 0)
373                         style = PANGO_STYLE_NORMAL;
374                 if (strcmp(word, "small-caps") == 0)
375                         variant = PANGO_VARIANT_SMALL_CAPS;
376
377                 if (strcmp(word, "bold") == 0)
378                         weight = PANGO_WEIGHT_BOLD;
379                 if (strcmp(word, "nobold") == 0)
380                         weight = PANGO_WEIGHT_NORMAL;
381
382                 if (strstarts(word, "fg:"))
383                         fg = word + 3;
384                 if (strstarts(word, "bg:"))
385                         bg = word + 3;
386                 if (strcmp(word, "inverse") == 0)
387                         inv = True;
388                 if (strcmp(word, "noinverse") == 0)
389                         inv = False;
390                 if (strcmp(word, "underline") == 0)
391                         ul = True;
392                 if (strcmp(word, "nounderline") == 0)
393                         ul = False;
394         }
395
396         if (inv) {
397                 char *t = bg;
398                 bg = fg;
399                 fg = t;
400                 if (!fg)
401                         fg = "white";
402                 if (!bg)
403                         bg = "black";
404         } else if (!fg)
405                 fg = "black";
406
407         if (fg && fgp) {
408                 struct call_return ret = call_ret(all, "colour:map", home,
409                                                   0, NULL, fg);
410                 fgp->r = cvt(ret.i);
411                 fgp->g = cvt(ret.i2);
412                 fgp->b = cvt(ret.x);
413         } else if (fgp)
414                 fgp->g = -1;
415         if (bg && bgp) {
416                 struct call_return ret = call_ret(all, "colour:map", home,
417                                                   0, NULL, bg);
418                 bgp->r = cvt(ret.i);
419                 bgp->g = cvt(ret.i2);
420                 bgp->b = cvt(ret.x);
421         } else if (bgp)
422                 bgp->g = -1;
423         if (fd) {
424                 pango_font_description_set_size(fd, PANGO_SCALE * size /1000 * scale / 1000);
425                 if (style != PANGO_STYLE_NORMAL)
426                         pango_font_description_set_style(fd, style);
427                 if (variant != PANGO_VARIANT_NORMAL)
428                         pango_font_description_set_variant(fd, variant);
429                 if (weight != PANGO_WEIGHT_NORMAL)
430                         pango_font_description_set_weight(fd, weight);
431         }
432         if (underline)
433                 *underline = ul;
434         free(attrs);
435 }
436
437 DEF_CB(cnt_disp)
438 {
439         struct call_return *cr = container_of(ci->comm, struct call_return, c);
440
441         cr->i += 1;
442         return 1;
443 }
444
445 DEF_CMD_CLOSED(xcb_close_display)
446 {
447         /* If this is only display, then refuse to close this one */
448         struct call_return cr;
449         char *nc = pane_attr_get(ci->home, "no-close");
450
451         if (nc) {
452                 call("Message", ci->focus, 0, NULL, nc);
453                 return 1;
454         }
455         cr.c = cnt_disp;
456         cr.i = 0;
457         call_comm("editor:notify:all-displays", ci->focus, &cr.c);
458         if (cr.i > 1)
459                 return Efallthrough;
460         else
461                 call("Message", ci->focus, 0, NULL,
462                      "Cannot close only window.");
463         return 1;
464 }
465
466 static void wait_for(struct xcb_data *xd safe)
467 {
468         struct pids **pp = &xd->pids;
469
470         while (*pp) {
471                 struct pids *p = *pp;
472                 if (waitpid(p->pid, NULL, WNOHANG) > 0) {
473                         *pp = p->next;
474                         free(p);
475                 } else
476                         pp = &p->next;
477         }
478 }
479
480 DEF_CMD(xcb_external_viewer)
481 {
482         struct xcb_data *xd = ci->home->data;
483         const char *path = ci->str;
484         struct pids *p;
485         int pid;
486         int fd;
487
488         if (!path)
489                 return Enoarg;
490         switch (pid = fork()) {
491         case -1:
492                 return Efail;
493         case 0: /* Child */
494                 setenv("DISPLAY", xd->display, 1);
495                 if (xd->disp_auth)
496                         setenv("XAUTHORITY", xd->disp_auth, 1);
497                 fd = open("/dev/null", O_RDWR);
498                 if (fd) {
499                         dup2(fd, 0);
500                         dup2(fd, 1);
501                         dup2(fd, 2);
502                         if (fd > 2)
503                                 close(fd);
504                 }
505                 execlp("xdg-open", "xdg-open", path, NULL);
506                 exit(1);
507         default: /* parent */
508                 p = malloc(sizeof(*p));
509                 p->pid = pid;
510                 p->next = xd->pids;
511                 xd->pids = p;
512                 break;
513         }
514         wait_for(xd);
515         return 1;
516 }
517
518 DEF_CMD(xcb_fullscreen)
519 {
520         struct xcb_data *xd = ci->home->data;
521         xcb_client_message_event_t msg = {};
522
523         msg.response_type = XCB_CLIENT_MESSAGE;
524         msg.format = 32;
525         msg.window = xd->win;
526         msg.type = xd->atoms[a_WM_STATE];
527         if (ci->num > 0)
528                 msg.data.data32[0] = 1; /* ADD */
529         else
530                 msg.data.data32[0] = 0; /* REMOVE */
531         msg.data.data32[1] = xd->atoms[a_STATE_FULLSCREEN];
532         msg.data.data32[2] = 0;
533         msg.data.data32[3] = 1; /* source indicator */
534
535         xcb_send_event(xd->conn, 0, xd->screen->root,
536                        XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
537                        XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
538                        (void*)&msg);
539         xcb_flush(xd->conn);
540         return 1;
541 }
542
543 static void panes_free(struct xcb_data *xd safe)
544 {
545         while (xd->panes) {
546                 struct panes *ps = xd->panes;
547                 xd->panes = ps->next;
548                 if (ps->ctx)
549                         cairo_destroy(ps->ctx);
550                 if (ps->surface)
551                         cairo_surface_destroy(ps->surface);
552                 if (ps->draw)
553                         xcb_free_pixmap(xd->conn, ps->draw);
554                 free(ps);
555         }
556 }
557
558 static void kbd_free(struct xcb_data *xd safe);
559
560 DEF_CMD_CLOSED(xcb_close)
561 {
562         struct xcb_data *xd = ci->home->data;
563
564         xcb_destroy_window(xd->conn, xd->win);
565         kbd_free(xd);
566         panes_free(xd);
567
568         pango_font_description_free(xd->fd);
569         cairo_destroy(xd->cairo);
570         cairo_device_finish(cairo_surface_get_device(xd->surface));
571         cairo_surface_destroy(xd->surface);
572         free(xd->display);
573         free(xd->disp_auth);
574         xcb_disconnect(xd->conn);
575         if (xd->need_update)
576                 cairo_region_destroy(xd->need_update);
577         return 1;
578 }
579
580 DEF_CMD(xcb_clear)
581 {
582         struct xcb_data *xd = ci->home->data;
583         const char *attr = ci->str;
584         struct panes *src = NULL, *dest;
585         struct rgb bg;
586         int x=0, y=0;
587         cairo_rectangle_int_t r;
588
589         if (attr) {
590                 parse_attrs(ci->home, attr, PANGO_SCALE, NULL, &bg, NULL, NULL);
591                 if (bg.g < 0)
592                         bg.r = bg.g = bg.b = 1.0;
593         } else {
594                 src = find_pixmap(xd, ci->focus->parent, &x, &y);
595                 x += ci->focus->x;
596                 y += ci->focus->y;
597                 if (!src)
598                         bg.r = bg.g = bg.b = 1.0;
599                 else if (src->bg.g >= 0)
600                         bg = src->bg;
601                 else if (src->surface == NULL)
602                         bg.r = bg.g = bg.b = 1.0;
603                 else
604                         bg.g = -1;
605         }
606
607         dest = get_pixmap(ci->home, ci->focus);
608         if (!dest)
609                 return 1;
610         if (bg.g >= 0) {
611                 if (dest->ctx) {
612                         cairo_set_source_rgb(dest->ctx, bg.r, bg.g, bg.b);
613                         cairo_paint(dest->ctx);
614                 }
615                 dest->bg = bg;
616         } else if (src) {
617                 if (!dest->ctx)
618                         instantiate_pixmap(xd, dest);
619                 if (dest->ctx) {
620                         cairo_set_source_surface(dest->ctx, src->surface, -x, -y);
621                         cairo_paint(dest->ctx);
622                         dest->bg.g = -1;
623                 }
624         }
625         pane_damaged(ci->home, DAMAGED_POSTORDER);
626
627         if (!dest->need_update)
628                 dest->need_update = cairo_region_create();
629         r.x = 0;
630         r.y = 0;
631         r.width = ci->focus->w;
632         r.height = ci->focus->h;
633         cairo_region_union_rectangle(dest->need_update, &r);
634         return 1;
635 }
636
637 DEF_CMD(xcb_text_size)
638 {
639         struct xcb_data *xd = ci->home->data;
640         const char *attr = ci->str2 ?: "";
641         const char *str = ci->str ?: "";
642         int scale = ci->num2;
643         PangoLayout *layout;
644         PangoFontDescription *fd;
645         PangoRectangle log;
646         int baseline;
647         int max_bytes;
648
649         if (scale <= 0)
650                 scale = 1000;
651         if (!utf8_valid(str))
652                 str = "*INV*";
653         parse_attrs(ci->home, attr, scale, NULL, NULL, NULL, &fd);
654         /* If we use an empty string, line-height is wrong */
655         layout = pango_cairo_create_layout(xd->cairo);
656         pango_layout_set_text(layout, *str ? str : "M", -1);
657         pango_layout_set_font_description(layout, fd);
658         pango_layout_get_pixel_extents(layout, NULL, &log);
659         baseline = pango_layout_get_baseline(layout) / PANGO_SCALE;
660
661         if (ci->num < 0)
662                 max_bytes = 0;
663         else if (log.width <= ci->num)
664                 max_bytes = strlen(str);
665         else
666                 pango_layout_xy_to_index(layout, PANGO_SCALE*ci->num,
667                                          baseline, &max_bytes, NULL);
668
669         comm_call(ci->comm2, "cb", ci->focus, max_bytes, NULL, NULL,
670                   baseline, NULL, NULL,
671                   str && *str ? log.width : 0,
672                   log.height);
673
674         pango_font_description_free(fd);
675         g_object_unref(layout);
676         return 1;
677 }
678
679 DEF_CMD(xcb_draw_text)
680 {
681         struct xcb_data *xd = ci->home->data;
682         const char *str = ci->str;
683         const char *attr = ci->str2;
684         int scale = 1000;
685         struct panes *ps;
686         cairo_t *ctx;
687         PangoLayout *layout;
688         PangoFontDescription *fd;
689         PangoRectangle log;
690         struct rgb fg, bg;
691         bool ul;
692         int baseline;
693         int xo = 0, yo = 0;
694         int x,y;
695
696         if (!str)
697                 return Enoarg;
698         ps = find_pixmap(xd, ci->focus, &xo, &yo);
699         if (!ps)
700                 return Einval;
701         if (!ps->ctx)
702                 instantiate_pixmap(xd, ps);
703         ps->bg.g = -1;
704         ctx = ps->ctx;
705         if (!ctx)
706                 return Efail;
707
708         if (!utf8_valid(str))
709                 str = "*INV*";
710
711         pane_damaged(ci->home, DAMAGED_POSTORDER);
712
713         if (ci->num2 > 0)
714                 scale = ci->num2 * 10 / xd->charwidth;
715
716         parse_attrs(ci->home, attr, scale, &fg, &bg, &ul, &fd);
717
718         x = ci->x + xo;
719         y = ci->y + yo;
720         layout = pango_cairo_create_layout(ctx);
721         pango_layout_set_text(layout, str, -1);
722         pango_layout_set_font_description(layout, fd);
723         pango_layout_get_pixel_extents(layout, NULL, &log);
724         baseline = pango_layout_get_baseline(layout) / PANGO_SCALE;
725         cairo_save(ctx);
726         if (bg.g >= 0) {
727                 cairo_set_source_rgb(ctx, bg.r, bg.g, bg.b);
728                 cairo_rectangle(ctx, x+log.x, y - baseline + log.y,
729                                 log.width, log.height);
730                 cairo_fill(ctx);
731         }
732         cairo_set_source_rgb(ctx, fg.r, fg.g, fg.b);
733         if (ul) {
734                 /* Draw an underline */
735                 cairo_rectangle(ctx, x+log.x, y+2+log.y,
736                                 log.width, 1);
737                 cairo_fill(ctx);
738         }
739
740         cairo_move_to(ctx, x, y - baseline);
741         pango_cairo_show_layout(ctx, layout);
742         cairo_stroke(ctx);
743
744         if (ci->num >= 0) {
745                 /* draw a cursor - outline box if not in-focus,
746                  * inverse-video if it is.
747                  */
748                 PangoRectangle curs;
749                 bool in_focus = xd->in_focus;
750                 struct pane *f = ci->focus;
751                 double cx, cy, cw, ch;
752
753                 pango_layout_index_to_pos(layout, ci->num, &curs);
754                 if (curs.width <= 0) {
755                         /* EOL?*/
756                         pango_layout_set_text(layout, "M", 1);
757                         pango_layout_get_extents(layout, NULL, &log);
758                         curs.width = log.width;
759                 }
760
761                 while (in_focus && f->parent->parent != f &&
762                        f->parent != ci->home) {
763                         if (f->parent->focus != f && f->z >= 0)
764                                 in_focus = False;
765                         f = f->parent;
766                 }
767                 if (!in_focus) {
768                         /* Just an fg:rectangle around the fg:text */
769                         /* Add half to x,y as stroke is either side of the line */
770                         cx = x * PANGO_SCALE + curs.x + PANGO_SCALE/2;
771                         cy = (y - baseline) * PANGO_SCALE + curs.y + PANGO_SCALE/2;
772                         ch = curs.height - PANGO_SCALE;
773                         cw = curs.width - PANGO_SCALE;
774                         cairo_rectangle(ctx, cx/PANGO_SCALE, cy/PANGO_SCALE,
775                                         cw/PANGO_SCALE, ch/PANGO_SCALE);
776                         cairo_set_line_width(ctx, 1.0);
777                         cairo_stroke(ctx);
778                 } else {
779                         /* solid fd:block with txt in bg color */
780                         cairo_rectangle(ctx,
781                                         x+curs.x/PANGO_SCALE,
782                                         y-baseline+curs.y/PANGO_SCALE,
783                                         curs.width / PANGO_SCALE,
784                                         curs.height / PANGO_SCALE);
785                         cairo_fill(ctx);
786                         if (ci->num < (int)strlen(str)) {
787                                 const char *cp = str + ci->num;
788                                 get_utf8(&cp, NULL);
789                                 pango_layout_set_text(layout, str + ci->num,
790                                                       cp - (str + ci->num));
791                                 if (bg.g >= 0)
792                                         cairo_set_source_rgb(ctx, bg.r, bg.g, bg.b);
793                                 else
794                                         cairo_set_source_rgb(ctx, 1.0, 1.0, 1.0);
795                                 cairo_move_to(ctx,
796                                               x + curs.x / PANGO_SCALE,
797                                               y - baseline + curs.y / PANGO_SCALE);
798                                 pango_cairo_show_layout(ctx, layout);
799                         }
800                 }
801         }
802         cairo_restore(ctx);
803         pango_font_description_free(fd);
804         g_object_unref(layout);
805         return 1;
806 }
807
808 struct di_info {
809         struct command c;
810         MagickWand *wd safe;
811         int x,y,w,h;
812         int xo, yo;
813         struct panes *ps safe;
814 };
815
816 DEF_CB(xcb_draw_image_cb)
817 {
818         struct di_info *dii = container_of(ci->comm, struct di_info, c);
819         int stride;
820         int fmt[2];
821         unsigned char *buf;
822         cairo_surface_t *surface;
823
824         switch (ci->key[0]) {
825         case 'w': /* width */
826                 return MagickGetImageWidth(dii->wd);
827         case 'h': /* height */
828                 return MagickGetImageHeight(dii->wd);
829         case 's': /* scale */
830                 MagickResizeImage(dii->wd, ci->num, ci->num2, BoxFilter, 1.0);
831                 return 1;
832         case 'c': /* crop or cursor */
833                 if (ci->key[1] != 'u') {
834                         /* crop */
835                         dii->x = ci->x;
836                         dii->y = ci->y;
837                         dii->w = ci->num;
838                         dii->h = ci->num2;
839                         return 1;
840                 } else {
841                         /* cursor */
842                         cairo_rectangle(dii->ps->ctx,
843                                         ci->x + dii->xo, ci->y + dii->yo,
844                                         ci->num, ci->num2);
845                         cairo_set_line_width(dii->ps->ctx, 1.0);
846                         cairo_set_source_rgb(dii->ps->ctx, 1.0, 0.0, 0.0);
847                         cairo_stroke(dii->ps->ctx);
848                         return 1;
849                 }
850         case 'd': /* draw */
851                 if (dii->w <= 0 || dii->h <= 0)
852                         return Efail;
853                 stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24,
854                                                        dii->w);
855                 buf = malloc(dii->h * stride);
856                 // Cairo expects 32bit values with A in the high byte, then RGB.
857                 // Magick provides 8bit values in the order requests.
858                 // So depending on byte order, a different string is needed
859
860                 fmt[0] = ('A'<<24) | ('R' << 16) | ('G' << 8) | ('B' << 0);
861                 fmt[1] = 0;
862                 MagickExportImagePixels(dii->wd, dii->x, dii->y,
863                                         dii->w, dii->h,
864                                         (char*)fmt, CharPixel, buf);
865                 surface = cairo_image_surface_create_for_data(
866                         buf, CAIRO_FORMAT_ARGB32, dii->w, dii->h, stride);
867                 cairo_set_source_surface(dii->ps->ctx, surface,
868                                          ci->num + dii->xo, ci->num2 + dii->yo);
869                 cairo_paint(dii->ps->ctx);
870                 cairo_surface_destroy(surface);
871                 free(buf);
872                 return 1;
873         default:
874                 return Efail;
875         }
876 }
877
878 DEF_CMD(xcb_draw_image)
879 {
880         /* 'str' identifies the image. Options are:
881          *     file:filename  - load file from fs
882          *     comm:command   - run command collecting bytes
883          * 'str2' and numbers are handled by Draw:scale-image.
884          */
885         struct xcb_data *xd = ci->home->data;
886         struct di_info dii;
887         MagickWand *wd = NULL;
888         struct panes *ps;
889
890         if (!ci->str)
891                 return Enoarg;
892         ps = find_pixmap(xd, ci->focus, &dii.xo, &dii.yo);
893         if (!ps)
894                 return Einval;
895         dii.ps = ps;
896         if (!dii.ps->ctx)
897                 instantiate_pixmap(xd, dii.ps);
898         dii.ps->bg.g = -1;
899         if (!dii.ps->ctx)
900                 return Efail;
901         if (strstarts(ci->str, "file:")) {
902                 MagickBooleanType status;
903
904                 wd = NewMagickWand();
905                 status = MagickReadImage(wd, ci->str + 5);
906                 if (status == MagickFalse) {
907                         DestroyMagickWand(wd);
908                         return Efail;
909                 }
910         } else if (strstarts(ci->str, "comm:")) {
911                 MagickBooleanType status;
912                 struct call_return cr;
913
914                 wd = NewMagickWand();
915                 cr = call_ret(bytes, ci->str+5, ci->focus);
916                 if (!cr.s) {
917                         DestroyMagickWand(wd);
918                         return Efail;
919                 }
920                 status = MagickReadImageBlob(wd, cr.s, cr.i);
921                 free(cr.s);
922                 if (status == MagickFalse) {
923                         DestroyMagickWand(wd);
924                         return Efail;
925                 }
926         }
927
928         if (!wd)
929                 return Einval;
930
931         MagickAutoOrientImage(wd);
932         dii.c = xcb_draw_image_cb;
933         dii.wd = wd;
934         dii.w = dii.h = dii.x = dii.y = dii.xo = dii.yo = 0;
935         call("Draw:scale-image", ci->focus,
936              ci->num, NULL, NULL, ci->num2, NULL, ci->str2,
937              ci->x, ci->y, &dii.c);
938
939         DestroyMagickWand(wd);
940
941         pane_damaged(ci->home, DAMAGED_POSTORDER);
942
943         return 1;
944 }
945
946 DEF_CMD(xcb_image_size)
947 {
948         MagickBooleanType status;
949         MagickWand *wd;
950         int ih, iw;
951
952         if (!ci->str)
953                 return Enoarg;
954         if (strstarts(ci->str, "file:")) {
955                 wd = NewMagickWand();
956                 status = MagickReadImage(wd, ci->str + 5);
957                 if (status == MagickFalse) {
958                         DestroyMagickWand(wd);
959                         return Efail;
960                 }
961         } else if (strstarts(ci->str, "comm:")) {
962                 struct call_return cr;
963                 wd = NewMagickWand();
964                 cr = call_ret(bytes, ci->str+5, ci->focus);
965                 if (!cr.s) {
966                         DestroyMagickWand(wd);
967                         return Efail;
968                 }
969                 status = MagickReadImageBlob(wd, cr.s, cr.i);
970                 free(cr.s);
971                 if (status == MagickFalse) {
972                         DestroyMagickWand(wd);
973                         return Efail;
974                 }
975         } else
976                 return Einval;
977
978         MagickAutoOrientImage(wd);
979         ih = MagickGetImageHeight(wd);
980         iw = MagickGetImageWidth(wd);
981
982         DestroyMagickWand(wd);
983         comm_call(ci->comm2, "callback:size", ci->focus,
984                   0, NULL, NULL, 0, NULL, NULL,
985                   iw, ih);
986         return 1;
987 }
988
989 static struct panes *sort_split(struct panes *p)
990 {
991         /* consider 'p' to be a list of panes with
992          * ordered subsets (ordered by p->abs_z).
993          * Remove every other such subset and return them
994          * linked together.
995          * If p is ordered, this means we return NULL.
996          */
997         struct panes *ret, **end = &ret;
998         struct panes *next;
999
1000         for (; p && p->next; p = next) {
1001                 /* If these are not ordered, attach p->next at
1002                  * 'end', and make 'end' point to &p->next.
1003                  */
1004                 next = p->next;
1005                 if (p->p->abs_z < next->p->abs_z) {
1006                         *end = next;
1007                         end = &p->next;
1008                 }
1009         }
1010         *end = NULL;
1011         return ret;
1012 }
1013
1014 static struct panes *sort_merge(struct panes *p1, struct panes *p2)
1015 {
1016         /* merge p1 and p2 and return result */
1017         struct panes *ret, **end = &ret;
1018         struct panes *prev = NULL;
1019
1020         while (p1 && p2) {
1021                 /* Make p1 the largest (or be added first.
1022                  * Then in prev is between them add p2, else p1
1023                  */
1024                 if (p1->p->abs_z < p2->p->abs_z) {
1025                         struct panes *t = p1;
1026                         p1 = p2;
1027                         p2 = t;
1028                 }
1029                 if (prev &&
1030                     p1->p->abs_z > prev->p->abs_z &&
1031                     prev->p->abs_z >= p2->p->abs_z) {
1032                         /* p2 is the better choice */
1033                         prev = p2;
1034                         p2 = p2->next;
1035                 } else {
1036                         prev = p1;
1037                         p1 = p1->next;
1038                 }
1039                 *end = prev;
1040                 end = &prev->next;
1041         }
1042         if (p1)
1043                 *end = p1;
1044         else
1045                 *end = p2;
1046         return ret;
1047 }
1048
1049 DEF_CMD(xcb_refresh_post)
1050 {
1051         struct xcb_data *xd = ci->home->data;
1052         struct panes *ps;
1053
1054         time_start(TIME_WINDOW);
1055         /* First: ensure panes are sorted */
1056         while ((ps = sort_split(xd->panes)) != NULL)
1057                 xd->panes = sort_merge(xd->panes, ps);
1058
1059         /* Then merge all update rectanges, checking for movement */
1060         if (!xd->need_update)
1061                 xd->need_update = cairo_region_create();
1062         for (ps = xd->panes; ps ; ps = ps->next)
1063         {
1064                 struct xy rel;
1065
1066                 rel = pane_mapxy(ps->p, ci->home, 0, 0, False);
1067                 if (ps->r.x == NEVER_DRAWN) {
1068                         ps->r.x = rel.x;
1069                         ps->r.y = rel.y;
1070                         cairo_region_union_rectangle(xd->need_update, &ps->r);
1071                 } else if (rel.x != ps->r.x || rel.y != ps->r.y) {
1072                         /* Moved, so refresh all.
1073                          * This rectangle might be too big if it is clipped,
1074                          * but that doesn't really matter.
1075                          */
1076                         cairo_region_union_rectangle(xd->need_update, &ps->r);
1077                         ps->r.x = rel.x;
1078                         ps->r.y = rel.y;
1079                         cairo_region_union_rectangle(xd->need_update, &ps->r);
1080                 } else if (ps->need_update) {
1081                         cairo_region_translate(ps->need_update, rel.x, rel.y);
1082                         cairo_region_union(xd->need_update, ps->need_update);
1083                 }
1084                 if (ps->need_update)
1085                         cairo_region_destroy(ps->need_update);
1086                 ps->need_update = NULL;
1087         }
1088         /* Now copy all panes onto the window where an update is needed */
1089         for (ps = xd->panes; ps ; ps = ps->next) {
1090                 struct xy rel, lo, hi;
1091                 cairo_region_t *cr;
1092                 cairo_rectangle_int_t r;
1093                 int nr, i;
1094
1095                 cr = cairo_region_copy(xd->need_update);
1096
1097                 rel = pane_mapxy(ps->p, ci->home, 0, 0, False);
1098
1099                 cairo_save(xd->cairo);
1100                 if (ps->bg.g >= 0)
1101                         cairo_set_source_rgb(xd->cairo,
1102                                              ps->bg.r, ps->bg.g, ps->bg.b);
1103                 else
1104                         cairo_set_source_surface(xd->cairo, ps->surface,
1105                                                  rel.x, rel.y);
1106
1107                 lo = pane_mapxy(ps->p, ci->home, 0, 0, True);
1108                 hi = pane_mapxy(ps->p, ci->home, ps->r.width, ps->r.height, True);
1109                 r.x = lo.x; r.y = lo.y;
1110                 r.width = hi.x - lo.x; r.height = hi.y - lo.y;
1111                 cairo_region_intersect_rectangle(cr, &r);
1112                 cairo_region_subtract_rectangle(xd->need_update, &r);
1113                 nr = cairo_region_num_rectangles(cr);
1114                 for (i = 0; i < nr; i++) {
1115                         cairo_region_get_rectangle(cr, i, &r);
1116                         cairo_rectangle(xd->cairo, r.x, r.y, r.width, r.height);
1117                         cairo_fill(xd->cairo);
1118                 }
1119                 cairo_restore(xd->cairo);
1120         }
1121
1122         cairo_region_destroy(xd->need_update);
1123         xd->need_update = NULL;
1124         time_stop(TIME_WINDOW);
1125         xcb_flush(xd->conn);
1126         return 1;
1127 }
1128
1129 DEF_CMD(xcb_refresh_size)
1130 {
1131         /* FIXME: should I consider resizing the window?
1132          * For now, just ensure we redraw everything.
1133          */
1134         struct xcb_data *xd = ci->home->data;
1135         cairo_rectangle_int_t r = {
1136                 .x = 0,
1137                 .y = 0,
1138                 .width = ci->home->w,
1139                 .height = ci->home->h,
1140         };
1141
1142         if (!xd->need_update)
1143                 xd->need_update = cairo_region_create();
1144         cairo_region_union_rectangle(xd->need_update, &r);
1145         /* Ask common code to notify children */
1146         return Efallthrough;
1147 }
1148
1149 DEF_CMD(xcb_pane_close)
1150 {
1151         struct xcb_data *xd = ci->home->data;
1152         struct panes **pp, *ps;
1153
1154         for (pp = &xd->panes; (ps = *pp) != NULL; pp = &(*pp)->next) {
1155                 if (ps->p != ci->focus)
1156                         continue;
1157
1158                 if (!xd->need_update)
1159                         xd->need_update = cairo_region_create();
1160                 if (ps->r.x != NEVER_DRAWN)
1161                         cairo_region_union_rectangle(xd->need_update, &ps->r);
1162
1163                 *pp = ps->next;
1164                 ps->next = NULL;
1165                 if (ps->need_update)
1166                         cairo_region_destroy(ps->need_update);
1167                 cairo_destroy(ps->ctx);
1168                 cairo_surface_destroy(ps->surface);
1169                 xcb_free_pixmap(xd->conn, ps->draw);
1170                 free(ps);
1171                 pane_damaged(ci->home, DAMAGED_POSTORDER);
1172                 break;
1173         }
1174         return 1;
1175 }
1176
1177 DEF_CMD(xcb_notify_display)
1178 {
1179         struct xcb_data *xd = ci->home->data;
1180         comm_call(ci->comm2, "callback:display", ci->home, xd->last_event);
1181         return 1;
1182 }
1183
1184 static void handle_button(struct pane *home safe,
1185                           xcb_button_press_event_t *be safe)
1186 {
1187         struct xcb_data *xd = home->data;
1188         bool press = (be->response_type & 0x7f) == XCB_BUTTON_PRESS;
1189         char mod[2+2+2+1];
1190         char key[2+2+2+9+1+1];
1191
1192         xcb_set_input_focus(xd->conn, XCB_INPUT_FOCUS_POINTER_ROOT,
1193                             xd->win, XCB_CURRENT_TIME);
1194         mod[0] = 0;
1195         if (press) {
1196                 xd->motion_blocked = False;
1197                 if (be->state & XCB_KEY_BUT_MASK_MOD_1)
1198                         strcat(mod, ":A");
1199                 if (be->state & XCB_KEY_BUT_MASK_CONTROL)
1200                         strcat(mod, ":C");
1201                 if (be->state & XCB_KEY_BUT_MASK_SHIFT)
1202                         strcat(mod, ":S");
1203                 strcpy(key, mod);
1204                 strcat(key, ":Press-X");
1205         } else if (be->detail >= 4)
1206                 /* ignore 'release' for scroll wheel */
1207                 return;
1208         else
1209                 strcpy(key, ":Release-X");
1210
1211         key[strlen(key) - 1] = '0' + be->detail;
1212         xd->last_event = time(NULL);
1213         call("Mouse-event", home, be->detail, NULL, key,
1214              press?1:2, NULL, mod,
1215              be->event_x, be->event_y);
1216 }
1217
1218 static void handle_motion(struct pane *home safe,
1219                           xcb_motion_notify_event_t *mne safe)
1220 {
1221         struct xcb_data *xd = home->data;
1222         xcb_query_pointer_cookie_t c;
1223         xcb_query_pointer_reply_t *qpr;
1224         int ret;
1225         int x = mne->event_x, y = mne->event_y;
1226
1227         if (xd->motion_blocked)
1228                 return;
1229         ret = call("Mouse-event", home, 0, NULL, ":Motion",
1230                    3, NULL, NULL, x, y);
1231         if (ret <= 0)
1232                 xd->motion_blocked = True;
1233
1234         /* This doesn't seem to be needed, but the spec says
1235          * I should do this when using POINTER_MOTION_HINT
1236          */
1237         c = xcb_query_pointer(xd->conn, xd->win);
1238         qpr = xcb_query_pointer_reply(xd->conn, c, NULL);
1239         free(qpr);
1240 }
1241
1242 static void handle_focus(struct pane *home safe, xcb_focus_in_event_t *fie safe)
1243 {
1244         struct xcb_data *xd = home->data;
1245         bool in = (fie->response_type & 0x7f) == XCB_FOCUS_IN;
1246         struct pane *p;
1247         struct mark *pt;
1248
1249         xd->in_focus = in;
1250         p = pane_focus(home);
1251         pt = call_ret(mark, "doc:point", p);
1252         if (pt)
1253                 call("view:changed", p, 0, pt);
1254         if (in)
1255                 call("pane:refocus", home);
1256 }
1257
1258 static bool select_xkb_events_for_device(xcb_connection_t *conn,
1259                                          int32_t device_id)
1260 {
1261         xcb_generic_error_t *error;
1262         xcb_void_cookie_t cookie;
1263
1264         enum {
1265                 required_events =
1266                         (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
1267                          XCB_XKB_EVENT_TYPE_MAP_NOTIFY |
1268                          XCB_XKB_EVENT_TYPE_STATE_NOTIFY),
1269
1270                 required_nkn_details =
1271                         (XCB_XKB_NKN_DETAIL_KEYCODES),
1272
1273                 required_map_parts =
1274                         (XCB_XKB_MAP_PART_KEY_TYPES |
1275                          XCB_XKB_MAP_PART_KEY_SYMS |
1276                          XCB_XKB_MAP_PART_MODIFIER_MAP |
1277                          XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
1278                          XCB_XKB_MAP_PART_KEY_ACTIONS |
1279                          XCB_XKB_MAP_PART_VIRTUAL_MODS |
1280                          XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP),
1281
1282                 required_state_details =
1283                         (XCB_XKB_STATE_PART_MODIFIER_BASE |
1284                          XCB_XKB_STATE_PART_MODIFIER_LATCH |
1285                          XCB_XKB_STATE_PART_MODIFIER_LOCK |
1286                          XCB_XKB_STATE_PART_GROUP_BASE |
1287                          XCB_XKB_STATE_PART_GROUP_LATCH |
1288                          XCB_XKB_STATE_PART_GROUP_LOCK),
1289         };
1290
1291         static const xcb_xkb_select_events_details_t details = {
1292                 .affectNewKeyboard = required_nkn_details,
1293                 .newKeyboardDetails = required_nkn_details,
1294                 .affectState = required_state_details,
1295                 .stateDetails = required_state_details,
1296         };
1297
1298         cookie = xcb_xkb_select_events_aux_checked(
1299                 conn,
1300                 device_id,
1301                 required_events,        /* affectWhich */
1302                 0,                      /* clear */
1303                 0,                      /* selectAll */
1304                 required_map_parts,     /* affectMap */
1305                 required_map_parts,     /* map */
1306                 &details);              /* details */
1307
1308         error = xcb_request_check(conn, cookie);
1309         if (error) {
1310                 free(error);
1311                 return False;
1312         }
1313
1314         return True;
1315 }
1316
1317 static bool update_keymap(struct xcb_data *xd safe)
1318 {
1319         struct xkb_keymap *new_keymap;
1320         struct xkb_state *new_state;
1321
1322         new_keymap = xkb_x11_keymap_new_from_device(xd->xkb, xd->conn,
1323                                                     xd->xkb_device_id,
1324                                                     XKB_KEYMAP_COMPILE_NO_FLAGS);
1325         if (!new_keymap)
1326                 return False;
1327
1328         new_state = xkb_x11_state_new_from_device(new_keymap, xd->conn,
1329                                                   xd->xkb_device_id);
1330         if (!new_state) {
1331                 xkb_keymap_unref(new_keymap);
1332                 return False;
1333         }
1334
1335         xkb_state_unref(xd->xkb_state);
1336         xkb_keymap_unref(xd->xkb_keymap);
1337         xd->xkb_keymap = new_keymap;
1338         xd->xkb_state = new_state;
1339         return True;
1340 }
1341
1342 static bool kbd_setup(struct xcb_data *xd safe)
1343 {
1344         int ret;
1345         const char *locale;
1346
1347         ret = xkb_x11_setup_xkb_extension(xd->conn,
1348                                           XKB_X11_MIN_MAJOR_XKB_VERSION,
1349                                           XKB_X11_MIN_MINOR_XKB_VERSION,
1350                                           XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
1351                                           NULL, NULL, &xd->first_xkb_event,
1352                                           NULL);
1353
1354         if (!ret)
1355                 return False;
1356         xd->xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
1357         if (!xd->xkb)
1358                 return False;
1359         xd->xkb_device_id = xkb_x11_get_core_keyboard_device_id(xd->conn);
1360         if (xd->xkb_device_id == -1)
1361                 return False;
1362
1363         if (!update_keymap(xd))
1364                 return False;
1365
1366         if (!select_xkb_events_for_device(xd->conn, xd->xkb_device_id))
1367                 return False;
1368
1369         locale = setlocale(LC_CTYPE, NULL);
1370         xd->compose_table =
1371                 xkb_compose_table_new_from_locale(xd->xkb, locale,
1372                                                   XKB_COMPOSE_COMPILE_NO_FLAGS);
1373         if (xd->compose_table)
1374                 xd->compose_state =
1375                         xkb_compose_state_new(xd->compose_table,
1376                                               XKB_COMPOSE_STATE_NO_FLAGS);
1377         return True;
1378 }
1379
1380 static void kbd_free(struct xcb_data *xd safe)
1381 {
1382         if (xd->compose_table)
1383                 xkb_compose_table_unref(xd->compose_table);
1384         if (xd->xkb_keymap)
1385                 xkb_keymap_unref(xd->xkb_keymap);
1386         if (xd->xkb)
1387                 xkb_context_unref(xd->xkb);
1388 }
1389
1390 static struct {
1391         char *from safe, *to safe;
1392 } key_map[] = {
1393         { "Return",      ":Enter"},
1394         { "Tab",         ":Tab"},
1395         { "ISO_Left_Tab",":Tab"},
1396         { "Escape",      ":ESC"},
1397         { "Linefeed",    ":LF"},
1398         { "Down",        ":Down"},
1399         { "Up",          ":Up"},
1400         { "Left",        ":Left"},
1401         { "Right",       ":Right"},
1402         { "Home",        ":Home"},
1403         { "End",         ":End"},
1404         { "BackSpace",   ":Backspace"},
1405         { "Delete",      ":Del"},
1406         { "Insert",      ":Ins"},
1407         { "Next",        ":Prior"},
1408         { "Prior",       ":Next"},
1409         { "F1",          ":F1"},
1410         { "F2",          ":F2"},
1411         { "F3",          ":F3"},
1412         { "F4",          ":F4"},
1413         { "F5",          ":F5"},
1414         { "F6",          ":F6"},
1415         { "F7",          ":F7"},
1416         { "F8",          ":F8"},
1417         { "F9",          ":F9"},
1418         { "F10",         ":F11"},
1419         { "F11",         ":F11"},
1420         { "F12",         ":F12"},
1421 };
1422
1423 static void handle_key_press(struct pane *home safe,
1424                              xcb_key_press_event_t *kpe safe)
1425 {
1426         struct xcb_data                 *xd = home->data;
1427         xkb_keycode_t                   keycode = kpe->detail;
1428         xcb_keysym_t                    keysym;
1429         xkb_keysym_t                    sym;
1430         const xkb_keysym_t              *syms;
1431         int                             nsyms;
1432         enum xkb_compose_status         status;
1433         xkb_mod_index_t                 mod;
1434         char                            s[16];
1435         char                            key[16];
1436         char                            mods[32];
1437         bool                            shift=False, ctrl=False, alt=False;
1438
1439         xd->last_event = time(NULL);
1440
1441         keysym = xkb_state_key_get_one_sym(xd->xkb_state,
1442                                            keycode);
1443         if (xd->compose_state)
1444                 xkb_compose_state_feed(xd->compose_state, keysym);
1445         nsyms = xkb_state_key_get_syms(xd->xkb_state, keycode,
1446                                        &syms);
1447         if (nsyms <= 0)
1448                 return;
1449         status = XKB_COMPOSE_NOTHING;
1450         if (xd->compose_state)
1451                 status = xkb_compose_state_get_status(xd->compose_state);
1452         if (status == XKB_COMPOSE_COMPOSING ||
1453             status == XKB_COMPOSE_CANCELLED)
1454                 return;
1455
1456         for (mod = 0; mod < xkb_keymap_num_mods(xd->xkb_keymap); mod++) {
1457                 const char *n;
1458                 if (xkb_state_mod_index_is_active(
1459                             xd->xkb_state, mod,
1460                             XKB_STATE_MODS_EFFECTIVE) <= 0)
1461                         continue;
1462                 /* This does tells me "shift" is consumed for :C:S-l ...
1463                 if (xkb_state_mod_index_is_consumed2(
1464                             xd->xkb_state, keycode, mod,
1465                             XKB_CONSUMED_MODE_XKB))
1466                         continue;
1467                  */
1468                 n = xkb_keymap_mod_get_name(xd->xkb_keymap, mod);
1469                 if (n && strcmp(n, "Shift") == 0)
1470                         shift = True;
1471                 if (n && strcmp(n, "Control") == 0)
1472                         ctrl = True;
1473                 if (n && strcmp(n, "Mod1") == 0)
1474                         alt = True;
1475         }
1476
1477         if (status == XKB_COMPOSE_COMPOSED) {
1478                 sym = xkb_compose_state_get_one_sym(xd->compose_state);
1479                 syms = &sym;
1480                 nsyms = 1;
1481                 s[0] = '-';
1482                 xkb_compose_state_get_utf8(xd->compose_state,
1483                                            s+1, sizeof(s)-1);
1484                 key[0] = 0;
1485                 shift = False;
1486                 ctrl = False;
1487                 /* Mod1 can still apply to a composed char */
1488         } else if (nsyms == 1) {
1489                 unsigned int i;
1490                 sym = xkb_state_key_get_one_sym(xd->xkb_state, keycode);
1491                 syms = &sym;
1492                 s[0] = '-';
1493                 xkb_state_key_get_utf8(xd->xkb_state, keycode,
1494                                        s+1, sizeof(s)-1);
1495                 xkb_keysym_get_name(syms[0], key, sizeof(key));
1496                 for (i = 0; i < ARRAY_SIZE(key_map); i++) {
1497                         if (strcmp(key, key_map[i].from) == 0) {
1498                                 strcpy(s, key_map[i].to);
1499                                 break;
1500                         }
1501                 }
1502                 if (s[0] == '-' && s[1] >= ' ' && s[1] < 0x7f)
1503                         /* Shift is included */
1504                         shift = False;
1505                 if (s[0] == '-' && s[1] && (unsigned char)(s[1]) < ' ') {
1506                         ctrl = True;
1507                         s[1] += '@';
1508                         if (s[1] < 'A' || s[1] > 'Z')
1509                                 shift = False;
1510                 } else if (s[0] == '-' && !s[1] && strcmp(key, "space") == 0) {
1511                         /* 'nul' becomes "C- " (ctrl-space) */
1512                         ctrl = True;
1513                         s[1] = ' ';
1514                         s[2] = 0;
1515                 }
1516         }
1517
1518         if (xd->compose_state &&
1519             (status == XKB_COMPOSE_CANCELLED ||
1520              status == XKB_COMPOSE_COMPOSED))
1521                 xkb_compose_state_reset(xd->compose_state);
1522
1523         if (s[1]) {
1524                 mods[0] = 0;
1525                 if (alt)
1526                         strcat(mods, ":A");
1527                 if (ctrl)
1528                         strcat(mods, ":C");
1529                 if (shift)
1530                         strcat(mods, ":S");
1531                 strcat(mods, s);
1532                 call("Keystroke", home, 0, NULL, mods);
1533         }
1534 }
1535
1536 static void handle_xkb_event(struct pane *home safe,
1537                              xcb_generic_event_t *ev safe)
1538 {
1539         struct xcb_data *xd = home->data;
1540
1541         switch (ev->pad0) {
1542                 xcb_xkb_new_keyboard_notify_event_t     *nkne;
1543                 xcb_xkb_state_notify_event_t            *sne;
1544                 xcb_xkb_map_notify_event_t              *mne;
1545         case XCB_XKB_NEW_KEYBOARD_NOTIFY:
1546                 nkne = (void*)ev;
1547                 if (nkne->deviceID == xd->xkb_device_id &&
1548                     nkne->changed & XCB_XKB_NKN_DETAIL_KEYCODES)
1549                         update_keymap(xd);
1550                 break;
1551         case XCB_XKB_MAP_NOTIFY:
1552                 mne = (void*)ev;
1553                 if (mne->deviceID == xd->xkb_device_id)
1554                         update_keymap(xd);
1555                 break;
1556         case XCB_XKB_STATE_NOTIFY:
1557                 sne = (void*)ev;
1558                 if (sne->deviceID == xd->xkb_device_id)
1559                         xkb_state_update_mask(xd->xkb_state,
1560                                               sne->baseMods,
1561                                               sne->latchedMods,
1562                                               sne->lockedMods,
1563                                               sne->baseGroup,
1564                                               sne->latchedGroup,
1565                                               sne->lockedGroup);
1566                 break;
1567         }
1568 }
1569
1570 static void handle_configure(struct pane *home safe,
1571                              xcb_configure_notify_event_t *cne safe)
1572 {
1573         struct xcb_data *xd = home->data;
1574
1575         pane_resize(home, 0, 0, cne->width, cne->height);
1576         cairo_xcb_surface_set_size(xd->surface, cne->width, cne->height);
1577 }
1578
1579 static void handle_expose(struct pane *home safe,
1580                           xcb_expose_event_t *ee safe)
1581 {
1582         struct xcb_data *xd = home->data;
1583         cairo_rectangle_int_t r = {
1584                 .x = ee->x,
1585                 .y = ee->y,
1586                 .width = ee->width,
1587                 .height = ee->height,
1588         };
1589
1590         if (!xd->need_update)
1591                 xd->need_update = cairo_region_create();
1592         cairo_region_union_rectangle(xd->need_update, &r);
1593         if (ee->count == 0)
1594                 pane_damaged(home, DAMAGED_POSTORDER);
1595 }
1596
1597 static void handle_client_message(struct pane *home safe,
1598                                   xcb_client_message_event_t *cme safe)
1599 {
1600         struct xcb_data *xd = home->data;
1601
1602         if (cme->type == xd->atoms[a_WM_PROTOCOLS] &&
1603             cme->format == 32 &&
1604             cme->window == xd->win &&
1605             cme->data.data32[0] == xd->atoms[a_WM_DELETE_WINDOW]) {
1606                 call("Window:close", pane_focus(home));
1607                 return;
1608         }
1609
1610         if (cme->type == xd->atoms[a_WM_PROTOCOLS] &&
1611             cme->format == 32 &&
1612             cme->window == xd->win &&
1613             cme->data.data32[0] == xd->atoms[a_NET_WM_PING]) {
1614                 cme->window = xd->screen->root;
1615                 xcb_send_event(xd->conn, 0, xd->screen->root,
1616                                XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
1617                                XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
1618                                (void*)cme);
1619                 return;
1620         }
1621         LOG("x11 %s got unexpected client message type=%d/%d win=%x data=%d",
1622             xd->display,
1623             cme->type, cme->format, cme->window, cme->data.data32[0]);
1624
1625 }
1626
1627 DEF_CMD(xcb_input)
1628 {
1629         struct xcb_data *xd = ci->home->data;
1630         xcb_generic_event_t *ev;
1631         int ret = 1;
1632
1633         wait_for(xd);
1634         if (ci->num < 0)
1635                 /* This is a poll - only return 1 on something happening */
1636                 ret = Efalse;
1637
1638         while ((ev = xcb_poll_for_event(xd->conn)) != NULL) {
1639                 ret = 1;
1640                 switch (ev->response_type & 0x7f) {
1641                 case XCB_KEY_PRESS:
1642                         time_start(TIME_KEY);
1643                         handle_key_press(ci->home, safe_cast (void*)ev);
1644                         time_stop(TIME_KEY);
1645                         break;
1646                 case XCB_KEY_RELEASE:
1647                         /* Ignore for now */
1648                         break;
1649                 case XCB_BUTTON_PRESS:
1650                 case XCB_BUTTON_RELEASE:
1651                         time_start(TIME_KEY);
1652                         handle_button(ci->home, (void*)ev);
1653                         time_stop(TIME_KEY);
1654                         break;
1655                 case XCB_MOTION_NOTIFY:
1656                         time_start(TIME_KEY);
1657                         handle_motion(ci->home, (void*)ev);
1658                         time_stop(TIME_KEY);
1659                         break;
1660                 case XCB_FOCUS_IN:
1661                 case XCB_FOCUS_OUT:
1662                         time_start(TIME_WINDOW);
1663                         handle_focus(ci->home, (void*)ev);
1664                         time_stop(TIME_WINDOW);
1665                         break;
1666                 case XCB_EXPOSE:
1667                         time_start(TIME_WINDOW);
1668                         handle_expose(ci->home, (void*)ev);
1669                         time_stop(TIME_WINDOW);
1670                         break;
1671                 case XCB_CONFIGURE_NOTIFY:
1672                         time_start(TIME_WINDOW);
1673                         handle_configure(ci->home, (void*)ev);
1674                         time_stop(TIME_WINDOW);
1675                         break;
1676                 case XCB_CLIENT_MESSAGE:
1677                         time_start(TIME_WINDOW);
1678                         handle_client_message(ci->home, (void*)ev);
1679                         time_stop(TIME_WINDOW);
1680                         break;
1681                 case XCB_REPARENT_NOTIFY:
1682                         /* Not interested */
1683                         break;
1684                 case XCB_MAP_NOTIFY:
1685                 case XCB_UNMAP_NOTIFY:
1686                 case XCB_MAPPING_NOTIFY:
1687                         /* FIXME what to do?? */
1688                         break;
1689                 case 0:
1690                         /* Don't know what this means, but I get a lot
1691                          * of them so I don't want to log that it was
1692                          * ignored.
1693                          */
1694                         break;
1695                 default:
1696                         if ((ev->response_type & 0x7f) ==
1697                             xd->first_xkb_event) {
1698                                 handle_xkb_event(ci->home, ev);
1699                                 break;
1700                         }
1701                         LOG("Ignored X11 event %d", ev->response_type);
1702                 }
1703                 xcb_flush(xd->conn);
1704         }
1705         if (xcb_connection_has_error(xd->conn)) {
1706                 call("Window:close", ci->home->parent);
1707                 pane_close(ci->home);
1708         }
1709         return ret;
1710 }
1711
1712 static void set_str_prop(struct xcb_data *xd safe,
1713                          enum my_atoms a, const char *str safe)
1714 {
1715         xcb_change_property(xd->conn,
1716                             XCB_PROP_MODE_REPLACE,
1717                             xd->win, xd->atoms[a], XCB_ATOM_STRING,
1718                             8, strlen(str), str);
1719 }
1720
1721 static void set_utf8_prop(struct xcb_data *xd safe,
1722                          enum my_atoms a, const char *str safe)
1723 {
1724         xcb_change_property(xd->conn,
1725                             XCB_PROP_MODE_REPLACE,
1726                             xd->win, xd->atoms[a],
1727                             xd->atoms[a_UTF8_STRING],
1728                             8, strlen(str), str);
1729 }
1730
1731 static void set_card32_property(struct xcb_data *xd safe,
1732                                 enum my_atoms a,
1733                                 const uint32_t *data, int cnt)
1734 {
1735         xcb_change_property(xd->conn,
1736                             XCB_PROP_MODE_REPLACE,
1737                             xd->win, xd->atoms[a],
1738                             XCB_ATOM_CARDINAL, 32,
1739                             cnt, data);
1740 }
1741
1742 static void set_atom_prop(struct xcb_data *xd safe,
1743                           enum my_atoms prop, enum my_atoms alist, ...)
1744 {
1745         uint32_t atoms[16];
1746         int anum = 0;
1747         va_list ap;
1748         enum my_atoms a;
1749
1750         atoms[anum++] = xd->atoms[alist];
1751         va_start(ap, alist);
1752         while ((a = va_arg(ap, enum my_atoms)) != a_NONE)
1753                 if (anum < 16)
1754                         atoms[anum++] = xd->atoms[a];
1755         va_end(ap);
1756         xcb_change_property(xd->conn,
1757                             XCB_PROP_MODE_REPLACE,
1758                             xd->win, xd->atoms[prop],
1759                             XCB_ATOM_ATOM,
1760                             32, anum, atoms);
1761 }
1762
1763 static void xcb_load_icon(struct pane *p safe,
1764                           struct xcb_data *xd safe,
1765                           char *file safe)
1766 {
1767         char *path;
1768         int h, w, n;
1769         unsigned int *data;
1770         MagickBooleanType status;
1771         MagickWand *wd;
1772         uint32_t fmt[2];
1773
1774         path = call_ret(str, "xdg-find-edlib-file", p, 0, NULL,
1775                         file, 0, NULL, "data");
1776         if (!path)
1777                 return;
1778
1779         wd = NewMagickWand();
1780         status = MagickReadImage(wd, path);
1781         free(path);
1782         if (status == MagickFalse)
1783                 goto done;
1784
1785         h = MagickGetImageHeight(wd);
1786         w = MagickGetImageWidth(wd);
1787         n = 2 + w*h;
1788         data = malloc(sizeof(data[0]) * n);
1789         if (!data)
1790                 goto done;
1791         data[0] = w;
1792         data[1] = h;
1793         /* Need host-endian ARGB data */
1794         fmt[0] = ('A'<<24) | ('R' << 16) | ('G' << 8) | ('B' << 0);
1795         fmt[1] = 0;
1796         MagickExportImagePixels(wd, 0, 0, w, h, (char*)fmt,
1797                                 CharPixel, data+2);
1798         set_card32_property(xd, a_NET_WM_ICON, data, n);
1799         free(data);
1800 done:
1801         DestroyMagickWand(wd);
1802         return;
1803 }
1804
1805 static struct pane *xcb_display_init(const char *d safe,
1806                                      const char *disp_auth,
1807                                      struct pane *focus safe)
1808 {
1809         struct xcb_data *xd;
1810         struct pane *p;
1811         xcb_connection_t *conn;
1812         xcb_intern_atom_cookie_t cookies[NR_ATOMS];
1813         xcb_screen_iterator_t iter;
1814         xcb_depth_iterator_t di;
1815         char scale[20];
1816         char hostname[128];
1817         uint32_t valwin[2];
1818         int screen = 0;
1819         int i;
1820         PangoLayout *layout;
1821         PangoRectangle log;
1822         cairo_t *cairo;
1823         cairo_surface_t *surface;
1824         PangoFontDescription *fd;
1825         // FIXME SCALE from environ?? or pango_cairo_context_set_resolution dpi
1826         // 254 * width_in_pixels / width_in_millimeters / 10
1827
1828         conn = safe_cast xcb_connect_auth(d, disp_auth, &screen);
1829         if (xcb_connection_has_error(conn))
1830                 return NULL;
1831
1832         p = call_ret(pane, "attach-window-core", focus);
1833         if (!p)
1834                 return NULL;
1835         p = pane_register(p, 1, &xcb_handle.c);
1836         if (!p)
1837                 return NULL;
1838         xd = p->data;
1839
1840         xd->motion_blocked = True;
1841         xd->in_focus = True;
1842
1843         xd->conn = conn;
1844         xd->display = strdup(d);
1845         if (disp_auth)
1846                 xd->disp_auth = strdup(disp_auth);
1847         xd->setup = safe_cast xcb_get_setup(conn);
1848         iter = xcb_setup_roots_iterator(xd->setup);
1849         for (i = 0; i < screen; i++)
1850                 xcb_screen_next(&iter);
1851         xd->screen = safe_cast iter.data;
1852
1853         di = xcb_screen_allowed_depths_iterator(xd->screen);
1854         while (di.data && di.data->depth < 24)
1855                 xcb_depth_next(&di);
1856         //?? look for class = TrueColor??
1857         xd->visual = xcb_depth_visuals(di.data);
1858
1859         for (i = 0; i < NR_ATOMS; i++) {
1860                 const char *n = atom_names[i];
1861                 if (!n)
1862                         continue;
1863                 cookies[i] = xcb_intern_atom(conn, 0, strlen(n), n);
1864         }
1865
1866         xd->win = xcb_generate_id(conn);
1867         valwin[0] = xd->screen->white_pixel;
1868         valwin[1] = (XCB_EVENT_MASK_KEY_PRESS |
1869                      XCB_EVENT_MASK_KEY_RELEASE |
1870                      XCB_EVENT_MASK_BUTTON_PRESS |
1871                      XCB_EVENT_MASK_BUTTON_RELEASE |
1872                      // XCB_EVENT_MASK_ENTER_WINDOW |
1873                      // XCB_EVENT_MASK_LEAVE_WINDOW |
1874                      XCB_EVENT_MASK_FOCUS_CHANGE |
1875                      XCB_EVENT_MASK_STRUCTURE_NOTIFY |
1876                      XCB_EVENT_MASK_EXPOSURE |
1877                      XCB_EVENT_MASK_BUTTON_MOTION |
1878                      XCB_EVENT_MASK_POINTER_MOTION_HINT |
1879                      0);
1880
1881         xcb_create_window(conn, XCB_COPY_FROM_PARENT, xd->win,
1882                           xd->screen->root,
1883                           0, 0,
1884                           100, 100,
1885                           0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
1886                           xd->screen->root_visual,
1887                           XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
1888                           valwin);
1889         xcb_flush(conn);
1890         kbd_setup(xd);
1891
1892         surface = cairo_xcb_surface_create(
1893                 conn, xd->win, xd->visual, 100, 100);
1894         if (!surface)
1895                 goto abort;
1896         xd->surface = surface;
1897         cairo = safe_cast cairo_create(xd->surface);
1898         if (cairo_status(cairo) != CAIRO_STATUS_SUCCESS)
1899                 goto abort;
1900         xd->cairo = cairo;
1901         fd = pango_font_description_new();
1902         if (!fd)
1903                 goto abort;
1904         xd->fd = fd;
1905         pango_font_description_set_family(xd->fd, "monospace");
1906         pango_font_description_set_size(xd->fd, 12 * PANGO_SCALE);
1907
1908         layout = pango_cairo_create_layout(xd->cairo);
1909         pango_layout_set_font_description(layout, fd);
1910         pango_layout_set_text(layout, "M", 1);
1911         pango_layout_get_pixel_extents(layout, NULL, &log);
1912         g_object_unref(layout);
1913         xd->lineheight = log.height;
1914         xd->charwidth = log.width;
1915
1916         valwin[0] = xd->charwidth * 80;
1917         valwin[1] = xd->lineheight * 26;
1918         xcb_configure_window(conn, xd->win,
1919                              XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
1920                              valwin);
1921         cairo_xcb_surface_set_size(xd->surface, valwin[0], valwin[1]);
1922
1923         /* Now resolve all those cookies */
1924         for (i = 0; i < NR_ATOMS; i++) {
1925                 xcb_intern_atom_reply_t *r;
1926                 r = xcb_intern_atom_reply(conn, cookies[i], NULL);
1927                 if (!r)
1928                         goto abort;
1929                 xd->atoms[i] = r->atom;
1930                 free(r);
1931         }
1932
1933         /* FIXME set:
1934          *
1935          * WM_PROTOCOLS _NET_WM_SYN_REQUEST??
1936          * WM_NORMAL_HINTS WM_HINTS
1937          * WM_CLIENT_MACHINE
1938          */
1939         set_str_prop(xd, a_WM_NAME, "EdLib");
1940         set_utf8_prop(xd, a_NET_WM_NAME, "EdLib");
1941         set_str_prop(xd, a_WM_ICON_NAME, "EdLib");
1942         set_utf8_prop(xd, a_NET_WM_ICON_NAME, "EdLib");
1943         gethostname(hostname, sizeof(hostname));
1944         set_str_prop(xd, a_WM_CLIENT_MACHINE, hostname);
1945         set_atom_prop(xd, a_WM_PROTOCOLS, a_WM_DELETE_WINDOW, a_NET_WM_PING, 0);
1946
1947         /* Configure passive grabs - shift, lock, and control only */
1948         xcb_grab_button(xd->conn, 0, xd->win,
1949                         XCB_EVENT_MASK_BUTTON_PRESS |
1950                         XCB_EVENT_MASK_BUTTON_RELEASE |
1951                         XCB_EVENT_MASK_BUTTON_MOTION,
1952                         XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
1953                         XCB_WINDOW_NONE, XCB_CURSOR_NONE,
1954                         XCB_BUTTON_INDEX_ANY,
1955                         XCB_MOD_MASK_SHIFT |
1956                         XCB_MOD_MASK_LOCK |
1957                         XCB_MOD_MASK_CONTROL);
1958
1959         xcb_load_icon(focus, xd, "{COMM}-icon.png");
1960         xcb_map_window(conn, xd->win);
1961         xcb_flush(conn);
1962         pane_resize(p, 0, 0, xd->charwidth*80, xd->lineheight*26);
1963         call_comm("event:read", p, &xcb_input, xcb_get_file_descriptor(conn));
1964         call_comm("event:poll", p, &xcb_input);
1965         attr_set_str(&p->attrs, "DISPLAY", d);
1966         attr_set_str(&p->attrs, "XAUTHORITY", disp_auth);
1967         snprintf(scale, sizeof(scale), "%dx%d", xd->charwidth, xd->lineheight);
1968         attr_set_str(&p->attrs, "scale:M", scale);
1969         xd->last_event = time(NULL);
1970         call("editor:request:all-displays", p);
1971         return p;
1972 abort:
1973         kbd_free(xd);
1974         cairo_destroy(xd->cairo);
1975         cairo_surface_destroy(xd->surface);
1976         xcb_disconnect(conn);
1977         free(xd->display);
1978         free(xd->disp_auth);
1979         return NULL;
1980 }
1981
1982 DEF_CMD(xcb_new_display)
1983 {
1984         struct pane *p;
1985         const char *d = ci->str;
1986         const char *disp_auth = ci->str2;
1987
1988         if (!d)
1989                 d = pane_attr_get(ci->focus, "DISPLAY");
1990         if (!disp_auth)
1991                 disp_auth = pane_attr_get(ci->focus, "XAUTHORITY");
1992         if (!disp_auth)
1993                 disp_auth = getenv("XAUTHORITY");
1994
1995         if (!d)
1996                 return Enoarg;
1997         p = xcb_display_init(d, disp_auth, ci->focus);
1998         if (strcmp(ci->key, "interactive-cmd-x11window") == 0)
1999                 p = home_call_ret(pane, p,
2000                                   "Window:activate-display", ci->focus);
2001         if (p)
2002                 comm_call(ci->comm2, "cb", p);
2003         return 1;
2004 }
2005
2006 void edlib_init(struct pane *ed safe)
2007 {
2008         call_comm("global-set-command", ed, &xcb_new_display, 0, NULL,
2009                   "attach-display-x11");
2010         call_comm("global-set-command", ed, &xcb_new_display, 0, NULL,
2011                   "interactive-cmd-x11window");
2012
2013         xcb_map = key_alloc();
2014
2015         key_add(xcb_map, "Window:close", &xcb_close_display);
2016         key_add(xcb_map, "Window:external-viewer", &xcb_external_viewer);
2017         key_add(xcb_map, "Window:fullscreen", &xcb_fullscreen);
2018         key_add(xcb_map, "Window:new", &xcb_new_display);
2019
2020         key_add(xcb_map, "Close", &xcb_close);
2021         key_add(xcb_map, "Draw:clear", &xcb_clear);
2022         key_add(xcb_map, "Draw:text-size", &xcb_text_size);
2023         key_add(xcb_map, "Draw:text", &xcb_draw_text);
2024         key_add(xcb_map, "Draw:image", &xcb_draw_image);
2025         key_add(xcb_map, "Draw:image-size", &xcb_image_size);
2026         key_add(xcb_map, "Refresh:size", &xcb_refresh_size);
2027         key_add(xcb_map, "Refresh:postorder", &xcb_refresh_post);
2028         key_add(xcb_map, "all-displays", &xcb_notify_display);
2029         key_add(xcb_map, "Notify:Close", &xcb_pane_close);
2030 }