]> git.neil.brown.name Git - edlib.git/blob - display-x11-xcb.c
display-x11: add cursor drawing.
[edlib.git] / display-x11-xcb.c
1 /*
2  * Copyright Neil Brown ©2021 <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 <string.h>
15 #include <xcb/xcb.h>
16 #include <xcb/xcbext.h>
17 #include <ctype.h>
18 #include <math.h>
19
20 #include <cairo.h>
21 #include <cairo-xcb.h>
22
23 #ifndef __CHECKER__
24 #include <pango/pango.h>
25 #include <pango/pangocairo.h>
26 #else
27 typedef struct PangoFontDescription {} PangoFontDescription;
28 typedef struct PangoLayout {} PangoLayout;
29 typedef struct PangoContext {} PangoContext;
30 typedef struct PangoFontMetrics {} PangoFontMetrics;
31 typedef struct PangoRectangle { int x,y,width,height;} PangoRectangle;
32 typedef enum { PANGO_STYLE_NORMAL, PANGO_STYLE_OBLIQUE, PANGO_STYLE_ITALIC
33 } PangoStyle;
34 typedef enum { PANGO_VARIANT_NORMAL, PANGO_VARIANT_SMALL_CAPS } PangoVariant;
35 typedef enum { PANGO_WEIGHT_NORMAL, PANGO_WEIGHT_BOLD } PangoWeight;
36 PangoFontDescription *pango_font_description_new(void);
37 void pango_font_description_set_family_static(PangoFontDescription*, char*);
38 void pango_font_description_set_family(PangoFontDescription*, char*);
39 void pango_font_description_set_size(PangoFontDescription*, int);
40 void pango_font_description_set_style(PangoFontDescription*, PangoStyle);
41 void pango_font_description_set_variant(PangoFontDescription*, PangoVariant);
42 void pango_font_description_set_weight(PangoFontDescription*, PangoWeight);
43 #define PANGO_SCALE (1024)
44
45 PangoLayout *pango_cairo_create_layout(cairo_t*);
46 void g_object_unref(PangoLayout*);
47 PangoContext *pango_cairo_create_context(cairo_t *);
48 void pango_cairo_show_layout(cairo_t *, PangoLayout *);
49 PangoFontMetrics *pango_context_get_metrics(PangoContext*, PangoFontDescription*, void*);
50 void pango_font_description_free(PangoFontDescription*);
51 int pango_font_metrics_get_approximate_char_width(PangoFontMetrics *);
52 int pango_font_metrics_get_ascent(PangoFontMetrics *);
53 int pango_font_metrics_get_descent(PangoFontMetrics *);
54 void pango_font_metrics_unref(PangoFontMetrics *);
55 PangoContext* pango_layout_get_context(PangoLayout *);
56 int pango_layout_get_baseline(PangoLayout *);
57 void pango_layout_get_extents(PangoLayout *, PangoRectangle *, PangoRectangle *);
58 void pango_layout_get_pixel_extents(PangoLayout *, PangoRectangle *, PangoRectangle *);
59 void pango_layout_set_font_description(PangoLayout *, PangoFontDescription *);
60 void pango_layout_set_text(PangoLayout*, const char *, int);
61 void pango_layout_xy_to_index(PangoLayout*, int, int, int*, int*);
62 void pango_layout_index_to_pos(PangoLayout*, int, PangoRectangle*);
63 #endif
64
65 //#include <xkbcommon/xkbcommon.h>
66 //#include <xkbcommon/xkbcommon-x11.h>
67
68 #undef True
69 #undef False
70 #include "core.h"
71
72 enum my_atoms {
73         a_WM_STATE, a_STATE_ADD, a_STATE_REMOVE, a_STATE_FULLSCREEN,
74         NR_ATOMS
75 };
76 static char *atom_names[NR_ATOMS] = {
77         [a_WM_STATE]            = "_NET_WM_STATE",
78         [a_STATE_ADD]           = "_NET_WM_STATE_ADD",
79         [a_STATE_REMOVE]        = "_NET_WM_STATE_REMOVE",
80         [a_STATE_FULLSCREEN]    = "_NET_WM_STATE_FULLSCREEN",
81 };
82
83 struct xcb_data {
84         xcb_connection_t        *conn safe;
85         char                    *display safe;
86
87         const xcb_setup_t       *setup safe;
88         const xcb_screen_t      *screen safe;
89         xcb_atom_t              atoms[NR_ATOMS];
90
91         long                    last_event;
92         xcb_window_t            win;
93         xcb_visualtype_t        *visual;
94         cairo_t                 *cairo safe;
95         cairo_surface_t         *surface safe;
96         PangoFontDescription    *fd safe;
97         char                    *noclose;
98         int                     charwidth, lineheight;
99
100         bool                    motion_blocked;
101         bool                    in_focus;
102
103 #ifdef USE_XKB
104         struct xkb_context      *xkb;
105         int32_t                 xkb_device_id;
106         struct xkb_state        *xkb_state;
107 #endif
108
109         /* FIXME use hash?? */
110         struct panes {
111                 struct panes    *next;
112                 struct pane     *p safe;
113                 int             w,h;
114                 cairo_t         *ctx safe;
115                 xcb_pixmap_t    draw;
116                 cairo_surface_t *surface safe;
117         }                       *panes;
118 };
119
120 static struct map *xcb_map;
121 DEF_LOOKUP_CMD(xcb_handle, xcb_map);
122
123 static cairo_t *get_pixmap(struct pane *home safe,
124                            struct pane *p safe)
125 {
126         struct xcb_data *xd = home->data;
127         struct panes **pp, *ps;
128         cairo_surface_t *surface;
129         cairo_t *ctx = NULL;
130
131         for (pp = &xd->panes; (ps = *pp) != NULL; pp = &(*pp)->next) {
132                 if (ps->p != p)
133                         continue;
134                 if (ps->w == p->w && ps->h == p->h)
135                         return ps->ctx;
136                 *pp = ps->next;
137                 cairo_destroy(ps->ctx);
138                 cairo_surface_destroy(ps->surface);
139                 xcb_free_pixmap(xd->conn, ps->draw);
140                 free(ps);
141                 break;
142         }
143         alloc(ps, pane);
144         ps->p = p;
145         ps->w = p->w;
146         ps->h = p->h;
147         ps->draw = xcb_generate_id(xd->conn);
148         xcb_create_pixmap(xd->conn, xd->screen->root_depth, ps->draw,
149                           xd->win, p->w, p->h);
150         surface = cairo_xcb_surface_create(
151                 xd->conn, ps->draw, xd->visual, p->w, p->h);
152         if (!surface)
153                 goto free_ps;
154         ctx = cairo_create(surface);
155         if (!ctx)
156                 goto free_surface;
157         ps->ctx = ctx;
158         ps->surface = surface;
159
160         pane_add_notify(home, p, "Notify:Close");
161         ps->next = *pp;
162         *pp = ps;
163         return ps->ctx;
164 free_surface:
165         cairo_surface_destroy(surface);
166 free_ps:
167         xcb_free_pixmap(xd->conn, ps->draw);
168         unalloc(ps, pane);
169         return NULL;
170 }
171
172 static struct panes *find_pixmap(struct xcb_data *xd safe, struct pane *p safe,
173                                  int *xp safe, int *yp safe)
174 {
175         int x = 0, y = 0;
176         struct panes *ret = NULL;
177
178         while (!ret && p->parent != p) {
179                 struct panes *ps;
180                 for (ps = xd->panes; ps ; ps = ps->next)
181                         if (ps->p == p) {
182                                 ret = ps;
183                                 break;
184                         }
185                 if (!ret) {
186                         x += p->x;
187                         y += p->y;
188                         p = p->parent;
189                 }
190         }
191         *xp = x;
192         *yp = y;
193         return ret;
194 }
195
196 struct rgb {
197         double r,g,b;
198 };
199
200 static inline double cvt(int i)
201 {
202         return (float)i / 1000.0;
203 }
204
205 static void parse_attrs(
206         struct pane *home safe, const char *cattrs, int scale,
207         struct rgb *fgp, struct rgb *bgp, bool *underline,
208         PangoFontDescription **fdp)
209 {
210         char *attrs = strdup(cattrs ?: "");
211         char *ap = attrs;
212         char *word;
213         char *fg = NULL, *bg = NULL;
214         bool ul = False;
215         bool inv = False;
216         int size = 10*1000;
217         PangoFontDescription *fd = NULL;
218         PangoStyle style = PANGO_STYLE_NORMAL;
219         PangoVariant variant = PANGO_VARIANT_NORMAL;
220         PangoWeight weight = PANGO_WEIGHT_NORMAL;
221
222         if (fdp) {
223                 fd = pango_font_description_new();
224                 *fdp = fd;
225                 pango_font_description_set_family_static(fd, "mono");
226         }
227
228         while ((word = strsep(&ap, ",")) != NULL) {
229                 if (fd && strncmp(word, "family:", 7) == 0)
230                         pango_font_description_set_family(fd, word+7);
231                 if (strcmp(word, "large") == 0)
232                         size = 14 * 1000;
233                 if (strcmp(word, "small") == 0)
234                         size = 9 * 1000;
235                 if (isdigit(word[0])) {
236                         char *end = NULL;
237                         double s = strtod(word, &end);
238                         if (end && end != word && !*end)
239                                 size = trunc(s * 1000.0);
240                         else
241                                 size = 10*1000;
242                 }
243                 if (strcmp(word, "oblique") == 0)
244                         style = PANGO_STYLE_OBLIQUE;
245                 if (strcmp(word, "italic") == 0)
246                         style = PANGO_STYLE_ITALIC;
247                 if (strcmp(word, "normal") == 0)
248                         style = PANGO_STYLE_NORMAL;
249                 if (strcmp(word, "small-caps") == 0)
250                         variant = PANGO_VARIANT_SMALL_CAPS;
251
252                 if (strcmp(word, "bold") == 0)
253                         weight = PANGO_WEIGHT_BOLD;
254                 if (strcmp(word, "nobold") == 0)
255                         weight = PANGO_WEIGHT_NORMAL;
256
257                 if (strncmp(word, "fg:", 3) == 0)
258                         fg = word + 3;
259                 if (strncmp(word, "bg:", 3) == 0)
260                         bg = word + 3;
261                 if (strcmp(word, "inverse") == 0)
262                         inv = True;
263                 if (strcmp(word, "underline") == 0)
264                         ul = True;
265         }
266
267         if (inv) {
268                 char *t = bg;
269                 bg = fg;
270                 fg = t;
271                 if (!fg)
272                         fg = "white";
273                 if (!bg)
274                         bg = "black";
275         } else if (!fg)
276                 fg = "black";
277
278         if (fg && fgp) {
279                 struct call_return ret = call_ret(all, "colour:map", home,
280                                                   0, NULL, fg);
281                 fgp->r = cvt(ret.i);
282                 fgp->g = cvt(ret.i2);
283                 fgp->b = cvt(ret.x);
284         } else if (fgp)
285                 fgp->g = -1;
286         if (bg && bgp) {
287                 struct call_return ret = call_ret(all, "colour:map", home,
288                                                   0, NULL, bg);
289                 bgp->r = cvt(ret.i);
290                 bgp->g = cvt(ret.i2);
291                 bgp->b = cvt(ret.x);
292         } else if (bgp)
293                 bgp->g = -1;
294         if (fd) {
295                 pango_font_description_set_size(fd, size * scale / PANGO_SCALE);
296                 if (style != PANGO_STYLE_NORMAL)
297                         pango_font_description_set_style(fd, style);
298                 if (variant != PANGO_VARIANT_NORMAL)
299                         pango_font_description_set_variant(fd, variant);
300                 if (weight != PANGO_WEIGHT_NORMAL)
301                         pango_font_description_set_weight(fd, weight);
302         }
303         if (underline)
304                 *underline = ul;
305         free(attrs);
306 }
307
308 DEF_CB(cnt_disp)
309 {
310         struct call_return *cr = container_of(ci->comm, struct call_return, c);
311
312         cr->i += 1;
313         return 1;
314 }
315
316 DEF_CMD(xcb_close_display)
317 {
318         /* If this is only display, then refuse to close this one */
319         struct call_return cr;
320         struct xcb_data *xd = ci->home->data;
321         if (xd->noclose) {
322                 call("Message", ci->focus, 0, NULL, xd->noclose);
323                 return 1;
324         }
325         cr.c = cnt_disp;
326         cr.i = 0;
327         call_comm("editor:notify:all-displays", ci->focus, &cr.c);
328         if (cr.i > 1)
329                 pane_close(ci->home);
330         else
331                 call("Message", ci->focus, 0, NULL,
332                      "Cannot close only window.");
333         return 1;
334 }
335
336 DEF_CMD(xcb_set_noclose)
337 {
338         struct xcb_data *xd = ci->home->data;
339
340         free(xd->noclose);
341         xd->noclose = NULL;
342         if (ci->str)
343                 xd->noclose = strdup(ci->str);
344         return 1;
345 }
346
347 DEF_CMD(xcb_external_viewer)
348 {
349         //struct xcb_data *xd = ci->home->data;
350         //FIXME
351         return 1;
352 }
353
354 DEF_CMD(xcb_fullscreen)
355 {
356         struct xcb_data *xd = ci->home->data;
357         xcb_client_message_event_t msg = {};
358
359         msg.response_type = XCB_CLIENT_MESSAGE;
360         msg.format = 32;
361         msg.window = xd->win;
362         msg.type = xd->atoms[a_WM_STATE];
363         if (ci->num > 0)
364                 msg.data.data32[0] = xd->atoms[a_STATE_ADD];
365         else
366                 msg.data.data32[0] = xd->atoms[a_STATE_REMOVE];
367         msg.data.data32[1] = xd->atoms[a_STATE_FULLSCREEN];
368         msg.data.data32[2] = 0;
369         msg.data.data32[3] = 1; /* source indicator */
370
371         xcb_send_event(xd->conn, 0, xd->screen->root,
372                        XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
373                        XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
374                        (void*)msg.data.data8);
375
376         return 1;
377 }
378
379 DEF_CMD(xcb_close)
380 {
381         struct xcb_data *xd = ci->home->data;
382         xcb_destroy_window(xd->conn, xd->win);
383         xcb_disconnect(xd->conn);
384         /* free stuff */
385         return 1;
386 }
387
388 DEF_CMD(xcb_clear)
389 {
390         struct xcb_data *xd = ci->home->data;
391         const char *attr = ci->str;
392         struct panes *src = NULL;
393         cairo_t *pm;
394         struct rgb bg;
395         int x, y;
396
397         if (attr)
398                 parse_attrs(ci->home, attr, PANGO_SCALE, NULL, &bg, NULL, NULL);
399         else {
400                 src = find_pixmap(xd, ci->focus->parent, &x, &y);
401                 bg.r = bg.g = bg.b = 1.0;
402         }
403
404         pm = get_pixmap(ci->home, ci->focus);
405         if (!pm)
406                 return 1;
407         if (src) {
408                 cairo_set_source_surface(pm, src->surface, -x - ci->focus->x,
409                                          -y - ci->focus->y);
410                 cairo_paint(pm);
411         } else {
412                 cairo_set_source_rgb(pm, bg.r, bg.g, bg.b);
413                 cairo_rectangle(pm, 0.0, 0.0,
414                                 (double)ci->focus->w, (double)ci->focus->h);
415                 cairo_fill(pm);
416         }
417         pane_damaged(ci->home, DAMAGED_POSTORDER);
418         return 1;
419 }
420
421 DEF_CMD(xcb_text_size)
422 {
423         struct xcb_data *xd = ci->home->data;
424         const char *attr = ci->str2 ?: "";
425         const char *str = ci->str ?: "";
426         int scale = ci->num2;
427         PangoLayout *layout;
428         PangoFontDescription *fd;
429         PangoRectangle log;
430         int baseline;
431         int max_bytes;
432
433         if (scale <= 0)
434                 scale = 1000;
435         parse_attrs(ci->home, attr, scale, NULL, NULL, NULL, &fd);
436         /* If we use an empty string, line-height it wrong */
437         layout = pango_cairo_create_layout(xd->cairo);
438         pango_layout_set_text(layout, *str ? str : "M", -1);
439         pango_layout_set_font_description(layout, fd);
440         pango_layout_get_pixel_extents(layout, NULL, &log);
441         baseline = pango_layout_get_baseline(layout) / PANGO_SCALE;
442
443         if (ci->num < 0)
444                 max_bytes = 0;
445         else if (log.width <= ci->num)
446                 max_bytes = strlen(str);
447         else
448                 pango_layout_xy_to_index(layout, 1000*ci->num,
449                                          baseline, &max_bytes, NULL);
450
451         comm_call(ci->comm2, "cb", ci->focus, max_bytes, NULL, NULL,
452                   baseline, NULL, NULL,
453                   str && *str ? log.width : 0,
454                   log.height);
455
456         pango_font_description_free(fd);
457         g_object_unref(layout);
458         return 1;
459 }
460
461 DEF_CMD(xcb_draw_text)
462 {
463         struct xcb_data *xd = ci->home->data;
464         const char *str = ci->str;
465         const char *attr = ci->str2;
466         int scale = 1000;
467         struct panes *ps;
468         cairo_t *ctx;
469         PangoLayout *layout;
470         PangoFontDescription *fd;
471         PangoRectangle log;
472         struct rgb fg, bg;
473         bool ul;
474         int baseline;
475         int xo = 0, yo = 0;
476         int x,y;
477
478         if (!str)
479                 return Enoarg;
480         ps = find_pixmap(xd, ci->focus, &xo, &yo);
481         if (!ps)
482                 return Einval;
483         ctx = ps->ctx;
484
485         pane_damaged(ci->home, DAMAGED_POSTORDER);
486
487         if (ci->num2 > 0)
488                 scale = ci->num2 * 10 / xd->charwidth;
489
490         parse_attrs(ci->home, attr, scale, &fg, &bg, &ul, &fd);
491
492         x = ci->x + xo;
493         y = ci->y + yo;
494         layout = pango_cairo_create_layout(ctx);
495         pango_layout_set_text(layout, str, -1);
496         pango_layout_set_font_description(layout, fd);
497         pango_layout_get_pixel_extents(layout, NULL, &log);
498         baseline = pango_layout_get_baseline(layout) / PANGO_SCALE;
499         if (bg.g >= 0) {
500                 cairo_set_source_rgb(ctx, bg.r, bg.g, bg.b);
501                 cairo_rectangle(ctx, x+log.x, y - baseline + log.y,
502                                 log.width, log.height);
503                 cairo_fill(ctx);
504         }
505         cairo_set_source_rgb(ctx, fg.r, fg.g, fg.b);
506         if (ul) {
507                 /* Draw an underline */
508                 cairo_rectangle(ctx, x+log.x, y+2+log.y,
509                                 log.width, 1);
510                 cairo_fill(ctx);
511         }
512
513         cairo_move_to(ctx, x, y - baseline);
514         pango_cairo_show_layout(ctx, layout);
515         cairo_stroke(ctx);
516
517         if (ci->num >= 0) {
518                 /* draw a cursor - outline box if not in-focus,
519                  * inverse-video if it is.
520                  */
521                 PangoRectangle curs;
522                 bool in_focus = xd->in_focus;
523                 struct pane *f = ci->focus;
524
525                 pango_layout_index_to_pos(layout, ci->num, &curs);
526                 if (curs.width <= 0) {
527                         /* EOL?*/
528                         pango_layout_set_text(layout, "M", 1);
529                         pango_layout_get_extents(layout, NULL, &log);
530                         curs.width = log.width;
531                 }
532                 cairo_rectangle(ctx, x+curs.x/PANGO_SCALE, y-baseline+curs.y/PANGO_SCALE,
533                                 (curs.width - PANGO_SCALE/2) / PANGO_SCALE,
534                                 (curs.height - PANGO_SCALE/2) / PANGO_SCALE);
535                 cairo_set_line_width(ctx, 1.0);
536                 cairo_stroke(ctx);
537
538                 while (in_focus && f->parent->parent != f &&
539                        f->parent != ci->home) {
540                         if (f->parent->focus != f && f->z >= 0)
541                                 in_focus = False;
542                         f = f->parent;
543                 }
544                 if (in_focus) {
545                         if (fg.g >= 0)
546                                 cairo_set_source_rgb(ctx, fg.r, fg.g, fg.b);
547                         cairo_rectangle(ctx, x+curs.x/PANGO_SCALE,
548                                         y-baseline+curs.y/PANGO_SCALE,
549                                         curs.width / PANGO_SCALE,
550                                         curs.height / PANGO_SCALE);
551                         cairo_fill(ctx);
552                         if (ci->num < (int)strlen(str)) {
553                                 const char *cp = str + ci->num;
554                                 get_utf8(&cp, NULL);
555                                 pango_layout_set_text(layout, str + ci->num,
556                                                       cp - (str + ci->num));
557                                 if (bg.g >= 0)
558                                         cairo_set_source_rgb(ctx, bg.r, bg.g, bg.b);
559                                 else
560                                         cairo_set_source_rgb(ctx, 1.0, 1.0, 1.0);
561                                 cairo_move_to(ctx,
562                                               x + curs.x / PANGO_SCALE,
563                                               y - baseline + curs.y / PANGO_SCALE);
564                                 pango_cairo_show_layout(ctx, layout);
565                         }
566                 }
567         }
568         pango_font_description_free(fd);
569         g_object_unref(layout);
570         return 1;
571 }
572
573 DEF_CMD(xcb_draw_image)
574 {
575         //struct xcb_data *xd = ci->home->data;
576         //FIXME
577         return 1;
578 }
579
580 static struct panes *sort_split(struct panes *p)
581 {
582         /* consider 'p' to be a list of panes with
583          * ordered subsets (ordered by p->abs_z).
584          * Remove every other such subset and return them
585          * linked together.
586          * If p is ordered, this means we return NULL.
587          */
588         struct panes *ret, **end = &ret;
589         struct panes *next;
590
591         for (; p && p->next; p = next) {
592                 /* If these are not ordered, attach p->next at
593                  * 'end', and make 'end' point to &p->next.
594                  */
595                 next = p->next;
596                 if (p->p->abs_z <= next->p->abs_z)
597                         continue;
598                 *end = next;
599                 end = &p->next;
600         }
601         *end = NULL;
602         return ret;
603 }
604
605 static struct panes *sort_merge(struct panes *p1, struct panes *p2)
606 {
607         /* merge p1 and p2 and return result */
608         struct panes *ret, **end = &ret;
609         int lastz = -100;
610
611         while (p1 && p2) {
612                 /* if both arg large or smaller than lastz, choose
613                  * least, else choose largest
614                  */
615                 struct panes *lo, *hi, *choice;
616                 if (p1->p->abs_z <= p2->p->abs_z) {
617                         lo = p1; hi = p2;
618                 } else {
619                         lo = p2; hi = p1;
620                 }
621                 if (lo->p->abs_z >= lastz || hi->p->abs_z <= lastz)
622                         choice = lo;
623                 else
624                         choice = hi;
625                 *end = choice;
626                 end = &choice->next;
627                 if (choice == p1)
628                         p1 = p1->next;
629                 else
630                         p2 = p2->next;
631         }
632         if (p1)
633                 *end = p1;
634         else
635                 *end = p2;
636         return ret;
637 }
638
639 DEF_CMD(xcb_refresh_post)
640 {
641         struct xcb_data *xd = ci->home->data;
642         struct panes *ps;
643
644         time_start(TIME_WINDOW);
645         /* First: ensure panes are sorted */
646         while ((ps = sort_split(xd->panes)) != NULL)
647                 xd->panes = sort_merge(xd->panes, ps);
648
649         /* Now copy all panes onto the window */
650         for (ps = xd->panes; ps; ps = ps->next) {
651                 double lox, hix, loy, hiy;
652                 struct xy rel, lo, hi;
653
654                 rel = pane_mapxy(ps->p, ci->home, 0, 0, False);
655                 lo = pane_mapxy(ps->p, ci->home, 0, 0, True);
656                 hi = pane_mapxy(ps->p, ci->home, ps->p->w, ps->p->h, True);
657                 lox = lo.x; loy = lo.y;
658                 hix = hi.x; hiy = hi.y;
659                 cairo_save(xd->cairo);
660                 cairo_set_source_surface(xd->cairo, ps->surface,
661                                          rel.x, rel.y);
662                 cairo_rectangle(xd->cairo, lox, loy, hix, hiy);
663                 cairo_fill(xd->cairo);
664                 cairo_restore(xd->cairo);
665         }
666         time_stop(TIME_WINDOW);
667         xcb_flush(xd->conn);
668         return 1;
669 }
670
671 DEF_CMD(xcb_pane_close)
672 {
673         struct xcb_data *xd = ci->home->data;
674         struct panes **pp, *ps;
675
676         for (pp = &xd->panes; (ps = *pp) != NULL; pp = &(*pp)->next) {
677                 if (ps->p != ci->focus)
678                         continue;
679                 *pp = ps->next;
680                 ps->next = NULL;
681                 cairo_destroy(ps->ctx);
682                 cairo_surface_destroy(ps->surface);
683                 xcb_free_pixmap(xd->conn, ps->draw);
684                 free(ps);
685                 pane_damaged(ci->home, DAMAGED_POSTORDER);
686                 break;
687         }
688         return 1;
689 }
690
691 DEF_CMD(xcb_notify_display)
692 {
693         struct xcb_data *xd = ci->home->data;
694         comm_call(ci->comm2, "callback:display", ci->home, xd->last_event);
695         return 1;
696 }
697
698 static void handle_button(struct pane *home safe,
699                           xcb_button_press_event_t *be safe)
700 {
701         struct xcb_data *xd = home->data;
702         bool press = (be->response_type & 0x7f) == XCB_BUTTON_PRESS;
703         char mod[2+2+2+1];
704         char key[2+2+2+9+1+1];
705
706         mod[0] = 0;
707         if (press) {
708                 xd->motion_blocked = False;
709                 if (be->state & XCB_KEY_BUT_MASK_MOD_1)
710                         strcat(mod, ":A");
711                 if (be->state & XCB_KEY_BUT_MASK_CONTROL)
712                         strcat(mod, ":C");
713                 if (be->state & XCB_KEY_BUT_MASK_SHIFT)
714                         strcat(mod, ":S");
715                 strcpy(key, mod);
716                 strcat(key, ":Press-X");
717         } else
718                 strcpy(key, ":Release-X");
719         key[strlen(key) - 1] = '0' + be->detail;
720         xd->last_event = time(NULL);
721         call("Mouse-event", home, be->detail, NULL, key,
722              press?1:2, NULL, mod,
723              be->event_x, be->event_y);
724 }
725
726 static void handle_motion(struct pane *home safe,
727                           xcb_motion_notify_event_t *mne safe)
728 {
729         struct xcb_data *xd = home->data;
730         xcb_query_pointer_cookie_t c;
731         xcb_query_pointer_reply_t *qpr;
732         int ret;
733         int x = mne->event_x, y = mne->event_y;
734
735         if (xd->motion_blocked)
736                 return;
737         ret = call("Mouse-event", home, 0, NULL, ":Motion",
738                    3, NULL, NULL, x, y);
739         if (ret <= 0)
740                 xd->motion_blocked = True;
741         c = xcb_query_pointer(xd->conn, xd->win);
742         qpr = xcb_query_pointer_reply(xd->conn, c, NULL);
743         free(qpr);
744 }
745
746 static void handle_focus(struct pane *home safe, xcb_focus_in_event_t *fie safe)
747 {
748         struct xcb_data *xd = home->data;
749         bool in = (fie->response_type & 0x7f) == XCB_FOCUS_IN;
750         struct pane *p;
751         struct mark *pt;
752
753         xd->in_focus = in;
754         p = pane_leaf(home);
755         pt = call_ret(mark, "doc:point", p);
756         if (pt)
757                 call("view:changed", p, 0, pt);
758         if (in)
759                 call("pane:refocus", home);
760 }
761
762 static void handle_key(struct pane *home safe, xcb_key_press_event_t *kpe safe)
763 {
764         //struct xcb_data *xd = home->data;
765         bool press = (kpe->response_type & 0x7f) == XCB_KEY_PRESS;
766 #ifdef USE_XKB
767         xcb_key_press_event_t           *kpe;
768         xkb_keysym_t                    keysym;
769         char                            name[64];
770         xkb_state_update_key(xd->xkb_state, kpe->keycode,
771                              XKB_KEY_DOWN);
772         keysym = xkb_state_key_get_one_sym(xd->xkb_state,
773                                            kpe->keycode);
774         xkb_keysym_get_name(keysym, name, sizeof(name));
775         xkb_state_key_get_utf8(xd->xkb_state, kpe->keycode,
776                                name, sizeof(name));
777 #endif
778         if (kpe->detail == 24)
779                 call("event:deactivate", home);
780         else if (kpe->detail == 65 && press)
781                 call("Keystroke", home, 0, NULL, "- ");
782         else LOG("key %d", kpe->detail);
783         //XKeyEvent xke;
784         //KeySym keysym;
785         //char buf[40];
786         //XLookupString(&xke, &buf, sizeof(buf), &keysym, NULL);
787 }
788
789 static void handle_configure(struct pane *home safe,
790                              xcb_configure_notify_event_t *cne safe)
791 {
792         struct xcb_data *xd = home->data;
793
794         pane_resize(home, 0, 0, cne->width, cne->height);
795         cairo_xcb_surface_set_size(xd->surface, cne->width, cne->height);
796 }
797
798 DEF_CMD(xcb_input)
799 {
800         struct xcb_data *xd = ci->home->data;
801         xcb_generic_event_t *ev;
802
803         while ((ev = xcb_poll_for_event(xd->conn)) != NULL) {
804                 switch (ev->response_type & 0x7f) {
805                 case XCB_KEY_PRESS:
806                 case XCB_KEY_RELEASE:
807                         time_start(TIME_KEY);
808                         handle_key(ci->home, safe_cast (void*)ev);
809                         time_stop(TIME_KEY);
810                         break;
811                 case XCB_BUTTON_PRESS:
812                 case XCB_BUTTON_RELEASE:
813                         time_start(TIME_KEY);
814                         handle_button(ci->home, (void*)ev);
815                         time_stop(TIME_KEY);
816                         break;
817                 case XCB_MOTION_NOTIFY:
818                         time_start(TIME_KEY);
819                         handle_motion(ci->home, (void*)ev);
820                         time_stop(TIME_KEY);
821                         break;
822                 case XCB_FOCUS_IN:
823                 case XCB_FOCUS_OUT:
824                         time_start(TIME_WINDOW);
825                         handle_focus(ci->home, (void*)ev);
826                         time_stop(TIME_WINDOW);
827                         break;
828                 case XCB_EXPOSE:
829                         pane_damaged(ci->home, DAMAGED_POSTORDER);
830                         break;
831                 case XCB_CONFIGURE_NOTIFY:
832                         time_start(TIME_WINDOW);
833                         handle_configure(ci->home, (void*)ev);
834                         time_stop(TIME_WINDOW);
835                         break;
836                 default:
837                         LOG("ignored %x", ev->response_type);
838                 }
839                 xcb_flush(xd->conn);
840         }
841         return 1;
842 }
843
844 static struct pane *xcb_display_init(const char *d safe, struct pane *focus safe)
845 {
846         struct xcb_data *xd;
847         struct pane *p;
848         xcb_connection_t *conn;
849         xcb_intern_atom_cookie_t cookies[NR_ATOMS];
850         xcb_screen_iterator_t iter;
851         xcb_depth_iterator_t di;
852         char scale[20];
853         uint32_t valwin[2];
854         int screen = 0;
855         int i;
856         PangoLayout *layout;
857         PangoRectangle log;
858         cairo_t *cairo;
859         cairo_surface_t *surface;
860         PangoFontDescription *fd;
861         // FIXME SCALE from environ?? or pango_cairo_context_set_resolution dpi
862         // 254 * width_in_pixels / width_in_millimeters / 10
863
864         conn = xcb_connect(d, &screen);
865         if (!conn)
866                 return NULL;
867
868         alloc(xd, pane);
869
870         xd->motion_blocked = True;
871         xd->in_focus = True;
872
873         xd->conn = conn;
874         xd->display = strdup(d);
875         xd->setup = safe_cast xcb_get_setup(conn);
876         iter = xcb_setup_roots_iterator(xd->setup);
877         for (i = 0; i < screen; i++)
878                 xcb_screen_next(&iter);
879         xd->screen = safe_cast iter.data;
880
881         di = xcb_screen_allowed_depths_iterator(xd->screen);
882         while (di.data && di.data->depth < 24)
883                 xcb_depth_next(&di);
884         //?? look for class = TrueColor??
885         xd->visual = xcb_depth_visuals(di.data);
886
887         for (i = 0; i < NR_ATOMS; i++) {
888                 char *n = atom_names[i];
889                 if (!n)
890                         continue;
891                 cookies[i] = xcb_intern_atom(conn, 0, strlen(n), n);
892         }
893
894         xd->win = xcb_generate_id(conn);
895         valwin[0] = xd->screen->white_pixel;
896         valwin[1] = (XCB_EVENT_MASK_KEY_PRESS |
897                      XCB_EVENT_MASK_KEY_RELEASE |
898                      XCB_EVENT_MASK_BUTTON_PRESS |
899                      XCB_EVENT_MASK_BUTTON_RELEASE |
900                      // XCB_EVENT_MASK_ENTER_WINDOW |
901                      // XCB_EVENT_MASK_LEAVE_WINDOW |
902                      XCB_EVENT_MASK_FOCUS_CHANGE |
903                      XCB_EVENT_MASK_STRUCTURE_NOTIFY |
904                      XCB_EVENT_MASK_EXPOSURE |
905                      XCB_EVENT_MASK_POINTER_MOTION |
906                      // FIXME XCB_EVENT_MASK_POINTER_MOTION_HINT |
907                      0);
908
909         xcb_create_window(conn, XCB_COPY_FROM_PARENT, xd->win,
910                           xd->screen->root,
911                           0, 0,
912                           100, 100,
913                           0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
914                           xd->screen->root_visual,
915                           XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
916                           valwin);
917         xcb_flush(conn);
918
919         surface = cairo_xcb_surface_create(
920                 conn, xd->win, xd->visual, 100, 100);
921         if (!surface)
922                 goto abort;
923         xd->surface = surface;
924         cairo = cairo_create(xd->surface);
925         if (!cairo)
926                 goto abort;
927         xd->cairo = cairo;
928         fd = pango_font_description_new();
929         if (!fd)
930                 goto abort;
931         xd->fd = fd;
932         pango_font_description_set_family(xd->fd, "mono");
933         pango_font_description_set_size(xd->fd, 12 * PANGO_SCALE);
934
935         layout = pango_cairo_create_layout(xd->cairo);
936         pango_layout_set_font_description(layout, fd);
937         pango_layout_set_text(layout, "M", 1);
938         pango_layout_get_pixel_extents(layout, NULL, &log);
939         g_object_unref(layout);
940         xd->lineheight = log.height;
941         xd->charwidth = log.width;
942
943         valwin[0] = xd->charwidth * 80;
944         valwin[1] = xd->lineheight * 26;
945         xcb_configure_window(conn, xd->win,
946                              XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
947                              valwin);
948         cairo_xcb_surface_set_size(xd->surface, valwin[0], valwin[1]);
949
950         /* Now resolve all those cookies */
951         for (i = 0; i < NR_ATOMS; i++) {
952                 xcb_intern_atom_reply_t *r;
953                 r = xcb_intern_atom_reply(conn, cookies[i], NULL);
954                 if (!r)
955                         goto abort;
956                 xd->atoms[i] = r->atom;
957                 free(r);
958         }
959
960 #ifdef USE_XKB
961         xd->xkb = safe_cast xkb_context_new(XCB_CONTEXT_NO_FLAGS);
962         xd->xkb_device_id = xkb_x11_get_core_keyboard_device_id(conn);
963         xd->xkb_state = xkb_x11_state_new_from_device(xd->xkb_keymap, conn,
964                                                       xd->xkb_device_id);
965 #endif
966         /* FIXME set:
967          * WM_NAME
968          * WM_PROTOCOLS WM_DELETE_WINDOW WM_TAKE_FOCUS _NET_WM_PING  _NET_WM_SYN_REQUEST??
969          * WM_NORMAL_HINTS WM_HINTS
970          * WM_CLIENT_MACHINE
971          */
972         xcb_map_window(conn, xd->win);
973         xcb_flush(conn);
974         p = pane_register(pane_root(focus), 1, &xcb_handle.c, xd);
975         if (!p)
976                 goto abort;
977         pane_resize(p, 0, 0, xd->charwidth*80, xd->lineheight*26);
978         call_comm("event:read", p, &xcb_input, xcb_get_file_descriptor(conn));
979         attr_set_str(&p->attrs, "DISPLAY", d);
980         snprintf(scale, sizeof(scale), "%dx%d", xd->charwidth, xd->lineheight);
981         attr_set_str(&p->attrs, "scale:M", scale);
982         //attr_set_int(&p->attrs, "scale", 2000);
983         return p;
984 abort:
985         cairo_destroy(xd->cairo);
986         cairo_surface_destroy(xd->surface);
987         xcb_disconnect(conn);
988         // FIXME free stuff;
989         unalloc(xd, pane);
990         return NULL;
991 }
992
993 DEF_CMD(display_xcb)
994 {
995         struct pane *p;
996         const char *d = ci->str;
997
998         if (!d)
999                 return Enoarg;
1000         p = xcb_display_init(d, ci->focus);
1001         if (p)
1002                 return comm_call(ci->comm2, "cb", p);
1003         return Efail;
1004 }
1005
1006 DEF_CMD(xcb_new_display)
1007 {
1008         struct pane *p;
1009         char *d = pane_attr_get(ci->focus, "DISPLAY");
1010
1011         if (!d)
1012                 return Enoarg;
1013         p = xcb_display_init(d, ci->focus);
1014         if (p)
1015                 p = call_ret(pane, "editor:activate-display", p);
1016         if (p)
1017                 home_call(ci->focus, "doc:attach-view", p, 1);
1018         return 1;
1019 }
1020
1021 void edlib_init(struct pane *ed safe)
1022 {
1023         call_comm("global-set-command", ed, &display_xcb, 0, NULL,
1024                   "attach-display-x11");
1025         call_comm("global-set-command", ed, &xcb_new_display, 0, NULL,
1026                   "interactive-cmd-x11window");
1027
1028         xcb_map = key_alloc();
1029
1030         key_add(xcb_map, "Display:close", &xcb_close_display);
1031         key_add(xcb_map, "Display:set-noclose", &xcb_set_noclose);
1032         key_add(xcb_map, "Display:external-viewer", &xcb_external_viewer);
1033         key_add(xcb_map, "Display:fullscreen", &xcb_fullscreen);
1034         key_add(xcb_map, "Display:new", &xcb_new_display);
1035
1036         key_add(xcb_map, "Close", &xcb_close);
1037         key_add(xcb_map, "Free", &edlib_do_free);
1038         key_add(xcb_map, "Draw:clear", &xcb_clear);
1039         key_add(xcb_map, "Draw:text-size", &xcb_text_size);
1040         key_add(xcb_map, "Draw:text", &xcb_draw_text);
1041         key_add(xcb_map, "Draw:image", &xcb_draw_image);
1042         key_add(xcb_map, "Refresh:postorder", &xcb_refresh_post);
1043         key_add(xcb_map, "all-displays", &xcb_notify_display);
1044         key_add(xcb_map, "Notify:Close", &xcb_pane_close);
1045 }