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