2 * Copyright Neil Brown ©2021 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * X11 display driver for edlib, using xcb, cairopango, libxkbcommon etc.
7 * A different connection to the server will be created for each
8 * display. Maybe that can be optimised one day.
19 /* xkb.h has a 'long' in an enum :-( */
21 XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY,
22 XCB_XKB_EVENT_TYPE_MAP_NOTIFY,
23 XCB_XKB_NEW_KEYBOARD_NOTIFY,
24 XCB_XKB_EVENT_TYPE_STATE_NOTIFY,
25 XCB_XKB_MAP_PART_MODIFIER_MAP,
26 XCB_XKB_STATE_PART_MODIFIER_LOCK,
27 XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS,
28 XCB_XKB_STATE_PART_GROUP_BASE,
29 XCB_XKB_MAP_PART_KEY_ACTIONS,
30 XCB_XKB_STATE_PART_GROUP_LATCH,
31 XCB_XKB_MAP_PART_VIRTUAL_MODS,
32 XCB_XKB_STATE_PART_GROUP_LOCK,
33 XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP,
34 XCB_XKB_NKN_DETAIL_KEYCODES,
35 XCB_XKB_MAP_PART_KEY_TYPES,
36 XCB_XKB_MAP_PART_KEY_SYMS,
37 XCB_XKB_STATE_PART_MODIFIER_BASE,
38 XCB_XKB_STATE_PART_MODIFIER_LATCH,
42 typedef uint16_t xcb_xkb_device_spec_t;
43 typedef struct xcb_xkb_select_events_details_t {
44 uint16_t affectNewKeyboard;
45 uint16_t newKeyboardDetails;
47 uint16_t stateDetails;
48 /* and other fields */
49 } xcb_xkb_select_events_details_t;
50 typedef struct xcb_xkb_new_keyboard_notify_event_t {
53 /* and other fields */
54 } xcb_xkb_new_keyboard_notify_event_t;
55 typedef struct xcb_xkb_state_notify_event_t {
63 /* and other fields */
64 } xcb_xkb_state_notify_event_t;
65 typedef struct xcb_xkb_map_notify_event_t {
67 } xcb_xkb_map_notify_event_t;
69 xcb_xkb_select_events_aux_checked(xcb_connection_t *c,
70 xcb_xkb_device_spec_t deviceSpec,
76 const xcb_xkb_select_events_details_t *details);
79 #include <xcb/xcbext.h>
85 #include <cairo-xcb.h>
87 #include <wand/MagickWand.h>
89 // enums confuse sparse...
90 #define MagickBooleanType int
94 #include <pango/pango.h>
95 #include <pango/pangocairo.h>
97 typedef struct PangoFontDescription {} PangoFontDescription;
98 typedef struct PangoLayout {} PangoLayout;
99 typedef struct PangoContext {} PangoContext;
100 typedef struct PangoFontMetrics {} PangoFontMetrics;
101 typedef struct PangoRectangle { int x,y,width,height;} PangoRectangle;
102 typedef enum { PANGO_STYLE_NORMAL, PANGO_STYLE_OBLIQUE, PANGO_STYLE_ITALIC
104 typedef enum { PANGO_VARIANT_NORMAL, PANGO_VARIANT_SMALL_CAPS } PangoVariant;
105 typedef enum { PANGO_WEIGHT_NORMAL, PANGO_WEIGHT_BOLD } PangoWeight;
106 PangoFontDescription *pango_font_description_new(void);
107 void pango_font_description_set_family_static(PangoFontDescription*, char*);
108 void pango_font_description_set_family(PangoFontDescription*, char*);
109 void pango_font_description_set_size(PangoFontDescription*, int);
110 void pango_font_description_set_style(PangoFontDescription*, PangoStyle);
111 void pango_font_description_set_variant(PangoFontDescription*, PangoVariant);
112 void pango_font_description_set_weight(PangoFontDescription*, PangoWeight);
113 #define PANGO_SCALE (1024)
115 PangoLayout *pango_cairo_create_layout(cairo_t*);
116 void g_object_unref(PangoLayout*);
117 PangoContext *pango_cairo_create_context(cairo_t *);
118 void pango_cairo_show_layout(cairo_t *, PangoLayout *);
119 PangoFontMetrics *pango_context_get_metrics(PangoContext*, PangoFontDescription*, void*);
120 void pango_font_description_free(PangoFontDescription*);
121 int pango_font_metrics_get_approximate_char_width(PangoFontMetrics *);
122 int pango_font_metrics_get_ascent(PangoFontMetrics *);
123 int pango_font_metrics_get_descent(PangoFontMetrics *);
124 void pango_font_metrics_unref(PangoFontMetrics *);
125 PangoContext* pango_layout_get_context(PangoLayout *);
126 int pango_layout_get_baseline(PangoLayout *);
127 void pango_layout_get_extents(PangoLayout *, PangoRectangle *, PangoRectangle *);
128 void pango_layout_get_pixel_extents(PangoLayout *, PangoRectangle *, PangoRectangle *);
129 void pango_layout_set_font_description(PangoLayout *, PangoFontDescription *);
130 void pango_layout_set_text(PangoLayout*, const char *, int);
131 void pango_layout_xy_to_index(PangoLayout*, int, int, int*, int*);
132 void pango_layout_index_to_pos(PangoLayout*, int, PangoRectangle*);
135 #include <xkbcommon/xkbcommon.h>
136 #include <xkbcommon/xkbcommon-compose.h>
137 #include <xkbcommon/xkbcommon-x11.h>
144 a_WM_STATE, a_STATE_FULLSCREEN,
147 static char *atom_names[NR_ATOMS] = {
148 [a_WM_STATE] = "_NET_WM_STATE",
149 [a_STATE_FULLSCREEN] = "_NET_WM_STATE_FULLSCREEN",
157 xcb_connection_t *conn safe;
160 const xcb_setup_t *setup safe;
161 const xcb_screen_t *screen safe;
162 xcb_atom_t atoms[NR_ATOMS];
166 xcb_visualtype_t *visual;
168 cairo_surface_t *surface safe;
169 PangoFontDescription *fd safe;
171 int charwidth, lineheight;
176 struct xkb_context *xkb;
177 uint8_t first_xkb_event;
178 int32_t xkb_device_id;
179 struct xkb_state *xkb_state;
180 struct xkb_compose_state *compose_state;
181 struct xkb_compose_table *compose_table;
182 struct xkb_keymap *xkb_keymap;
184 /* FIXME use hash?? */
192 cairo_surface_t *surface safe;
196 static struct map *xcb_map;
197 DEF_LOOKUP_CMD(xcb_handle, xcb_map);
199 static struct panes *get_pixmap(struct pane *home safe,
202 struct xcb_data *xd = home->data;
203 struct panes **pp, *ps;
204 cairo_surface_t *surface;
207 for (pp = &xd->panes; (ps = *pp) != NULL; pp = &(*pp)->next) {
210 if (ps->w == p->w && ps->h == p->h)
213 cairo_destroy(ps->ctx);
214 cairo_surface_destroy(ps->surface);
215 xcb_free_pixmap(xd->conn, ps->draw);
224 ps->draw = xcb_generate_id(xd->conn);
225 xcb_create_pixmap(xd->conn, xd->screen->root_depth, ps->draw,
226 xd->win, p->w, p->h);
227 surface = cairo_xcb_surface_create(
228 xd->conn, ps->draw, xd->visual, p->w, p->h);
231 ctx = cairo_create(surface);
235 ps->surface = surface;
237 pane_add_notify(home, p, "Notify:Close");
242 cairo_surface_destroy(surface);
244 xcb_free_pixmap(xd->conn, ps->draw);
249 static struct panes *find_pixmap(struct xcb_data *xd safe, struct pane *p safe,
250 int *xp safe, int *yp safe)
253 struct panes *ret = NULL;
255 while (!ret && p->parent != p) {
257 for (ps = xd->panes; ps ; ps = ps->next)
273 static inline double cvt(int i)
275 return (float)i / 1000.0;
278 static void parse_attrs(
279 struct pane *home safe, const char *cattrs, int scale,
280 struct rgb *fgp, struct rgb *bgp, bool *underline,
281 PangoFontDescription **fdp)
283 char *attrs = strdup(cattrs ?: "");
286 char *fg = NULL, *bg = NULL;
290 PangoFontDescription *fd = NULL;
291 PangoStyle style = PANGO_STYLE_NORMAL;
292 PangoVariant variant = PANGO_VARIANT_NORMAL;
293 PangoWeight weight = PANGO_WEIGHT_NORMAL;
296 fd = pango_font_description_new();
298 pango_font_description_set_family_static(fd, "mono");
301 while ((word = strsep(&ap, ",")) != NULL) {
302 if (fd && strncmp(word, "family:", 7) == 0)
303 pango_font_description_set_family(fd, word+7);
304 if (strcmp(word, "large") == 0)
306 if (strcmp(word, "small") == 0)
308 if (isdigit(word[0])) {
310 double s = strtod(word, &end);
311 if (end && end != word && !*end)
312 size = trunc(s * 1000.0);
316 if (strcmp(word, "oblique") == 0)
317 style = PANGO_STYLE_OBLIQUE;
318 if (strcmp(word, "italic") == 0)
319 style = PANGO_STYLE_ITALIC;
320 if (strcmp(word, "normal") == 0)
321 style = PANGO_STYLE_NORMAL;
322 if (strcmp(word, "small-caps") == 0)
323 variant = PANGO_VARIANT_SMALL_CAPS;
325 if (strcmp(word, "bold") == 0)
326 weight = PANGO_WEIGHT_BOLD;
327 if (strcmp(word, "nobold") == 0)
328 weight = PANGO_WEIGHT_NORMAL;
330 if (strncmp(word, "fg:", 3) == 0)
332 if (strncmp(word, "bg:", 3) == 0)
334 if (strcmp(word, "inverse") == 0)
336 if (strcmp(word, "underline") == 0)
352 struct call_return ret = call_ret(all, "colour:map", home,
355 fgp->g = cvt(ret.i2);
360 struct call_return ret = call_ret(all, "colour:map", home,
363 bgp->g = cvt(ret.i2);
368 pango_font_description_set_size(fd, size * scale / PANGO_SCALE);
369 if (style != PANGO_STYLE_NORMAL)
370 pango_font_description_set_style(fd, style);
371 if (variant != PANGO_VARIANT_NORMAL)
372 pango_font_description_set_variant(fd, variant);
373 if (weight != PANGO_WEIGHT_NORMAL)
374 pango_font_description_set_weight(fd, weight);
383 struct call_return *cr = container_of(ci->comm, struct call_return, c);
389 DEF_CMD(xcb_close_display)
391 /* If this is only display, then refuse to close this one */
392 struct call_return cr;
393 struct xcb_data *xd = ci->home->data;
395 call("Message", ci->focus, 0, NULL, xd->noclose);
400 call_comm("editor:notify:all-displays", ci->focus, &cr.c);
402 pane_close(ci->home);
404 call("Message", ci->focus, 0, NULL,
405 "Cannot close only window.");
409 DEF_CMD(xcb_set_noclose)
411 struct xcb_data *xd = ci->home->data;
416 xd->noclose = strdup(ci->str);
420 DEF_CMD(xcb_external_viewer)
422 //struct xcb_data *xd = ci->home->data;
427 DEF_CMD(xcb_fullscreen)
429 struct xcb_data *xd = ci->home->data;
430 xcb_client_message_event_t msg = {};
432 msg.response_type = XCB_CLIENT_MESSAGE;
434 msg.window = xd->win;
435 msg.type = xd->atoms[a_WM_STATE];
437 msg.data.data32[0] = 1; /* ADD */
439 msg.data.data32[0] = 0; /* REMOVE */
440 msg.data.data32[1] = xd->atoms[a_STATE_FULLSCREEN];
441 msg.data.data32[2] = 0;
442 msg.data.data32[3] = 1; /* source indicator */
444 xcb_send_event(xd->conn, 0, xd->screen->root,
445 XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
446 XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
454 struct xcb_data *xd = ci->home->data;
455 xcb_destroy_window(xd->conn, xd->win);
456 xcb_disconnect(xd->conn);
463 struct xcb_data *xd = ci->home->data;
464 const char *attr = ci->str;
465 struct panes *src = NULL, *dest;
470 parse_attrs(ci->home, attr, PANGO_SCALE, NULL, &bg, NULL, NULL);
472 bg.r = bg.g = bg.b = 1.0;
474 src = find_pixmap(xd, ci->focus->parent, &x, &y);
478 bg.r = bg.g = bg.b = 1.0;
479 else if (src->bg.g >= 0)
485 dest = get_pixmap(ci->home, ci->focus);
489 cairo_set_source_rgb(dest->ctx, bg.r, bg.g, bg.b);
490 cairo_rectangle(dest->ctx, 0.0, 0.0,
491 (double)ci->focus->w, (double)ci->focus->h);
492 cairo_fill(dest->ctx);
495 cairo_set_source_surface(dest->ctx, src->surface, -x, -y);
496 cairo_paint(dest->ctx);
499 LOG("ERROR neither src or bg");
500 pane_damaged(ci->home, DAMAGED_POSTORDER);
504 DEF_CMD(xcb_text_size)
506 struct xcb_data *xd = ci->home->data;
507 const char *attr = ci->str2 ?: "";
508 const char *str = ci->str ?: "";
509 int scale = ci->num2;
511 PangoFontDescription *fd;
518 parse_attrs(ci->home, attr, scale, NULL, NULL, NULL, &fd);
519 /* If we use an empty string, line-height it wrong */
520 layout = pango_cairo_create_layout(xd->cairo);
521 pango_layout_set_text(layout, *str ? str : "M", -1);
522 pango_layout_set_font_description(layout, fd);
523 pango_layout_get_pixel_extents(layout, NULL, &log);
524 baseline = pango_layout_get_baseline(layout) / PANGO_SCALE;
528 else if (log.width <= ci->num)
529 max_bytes = strlen(str);
531 pango_layout_xy_to_index(layout, 1000*ci->num,
532 baseline, &max_bytes, NULL);
534 comm_call(ci->comm2, "cb", ci->focus, max_bytes, NULL, NULL,
535 baseline, NULL, NULL,
536 str && *str ? log.width : 0,
539 pango_font_description_free(fd);
540 g_object_unref(layout);
544 DEF_CMD(xcb_draw_text)
546 struct xcb_data *xd = ci->home->data;
547 const char *str = ci->str;
548 const char *attr = ci->str2;
553 PangoFontDescription *fd;
563 ps = find_pixmap(xd, ci->focus, &xo, &yo);
569 pane_damaged(ci->home, DAMAGED_POSTORDER);
572 scale = ci->num2 * 10 / xd->charwidth;
574 parse_attrs(ci->home, attr, scale, &fg, &bg, &ul, &fd);
578 layout = pango_cairo_create_layout(ctx);
579 pango_layout_set_text(layout, str, -1);
580 pango_layout_set_font_description(layout, fd);
581 pango_layout_get_pixel_extents(layout, NULL, &log);
582 baseline = pango_layout_get_baseline(layout) / PANGO_SCALE;
584 cairo_set_source_rgb(ctx, bg.r, bg.g, bg.b);
585 cairo_rectangle(ctx, x+log.x, y - baseline + log.y,
586 log.width, log.height);
589 cairo_set_source_rgb(ctx, fg.r, fg.g, fg.b);
591 /* Draw an underline */
592 cairo_rectangle(ctx, x+log.x, y+2+log.y,
597 cairo_move_to(ctx, x, y - baseline);
598 pango_cairo_show_layout(ctx, layout);
602 /* draw a cursor - outline box if not in-focus,
603 * inverse-video if it is.
606 bool in_focus = xd->in_focus;
607 struct pane *f = ci->focus;
609 pango_layout_index_to_pos(layout, ci->num, &curs);
610 if (curs.width <= 0) {
612 pango_layout_set_text(layout, "M", 1);
613 pango_layout_get_extents(layout, NULL, &log);
614 curs.width = log.width;
616 cairo_rectangle(ctx, x+curs.x/PANGO_SCALE, y-baseline+curs.y/PANGO_SCALE,
617 (curs.width - PANGO_SCALE/2) / PANGO_SCALE,
618 (curs.height - PANGO_SCALE/2) / PANGO_SCALE);
619 cairo_set_line_width(ctx, 1.0);
622 while (in_focus && f->parent->parent != f &&
623 f->parent != ci->home) {
624 if (f->parent->focus != f && f->z >= 0)
630 cairo_set_source_rgb(ctx, fg.r, fg.g, fg.b);
631 cairo_rectangle(ctx, x+curs.x/PANGO_SCALE,
632 y-baseline+curs.y/PANGO_SCALE,
633 curs.width / PANGO_SCALE,
634 curs.height / PANGO_SCALE);
636 if (ci->num < (int)strlen(str)) {
637 const char *cp = str + ci->num;
639 pango_layout_set_text(layout, str + ci->num,
640 cp - (str + ci->num));
642 cairo_set_source_rgb(ctx, bg.r, bg.g, bg.b);
644 cairo_set_source_rgb(ctx, 1.0, 1.0, 1.0);
646 x + curs.x / PANGO_SCALE,
647 y - baseline + curs.y / PANGO_SCALE);
648 pango_cairo_show_layout(ctx, layout);
652 pango_font_description_free(fd);
653 g_object_unref(layout);
657 DEF_CMD(xcb_draw_image)
659 /* 'str' identifies the image. Options are:
660 * file:filename - load file from fs
661 * comm:command - run command collecting bytes
662 * 'num' is '1' if image should be stretched to fill pane
663 * if 'num is '0', then 'num2' is 'or' of
664 * 0,1,2 for left/middle/right in x direction
665 * 0,4,8 for top/middle/bottom in y direction
666 * only one of these can be used as image will fill pane in other direction.
668 struct xcb_data *xd = ci->home->data;
669 bool stretch = ci->num == 1;
671 int w = ci->focus->w, h = ci->focus->h;
676 MagickBooleanType status;
680 cairo_surface_t *surface;
684 ps = find_pixmap(xd, ci->focus, &xo, &yo);
688 if (strncmp(ci->str, "file:", 5) == 0) {
689 wd = NewMagickWand();
690 status = MagickReadImage(wd, ci->str + 5);
691 if (status == MagickFalse) {
692 DestroyMagickWand(wd);
695 } else if (strncmp(ci->str, "comm:", 5) == 0) {
696 struct call_return cr;
697 wd = NewMagickWand();
698 cr = call_ret(bytes, ci->str+5, ci->focus, 0, NULL, ci->str2);
700 DestroyMagickWand(wd);
703 status = MagickReadImageBlob(wd, cr.s, cr.i);
705 if (status == MagickFalse) {
706 DestroyMagickWand(wd);
712 MagickAutoOrientImage(wd);
714 int ih = MagickGetImageHeight(wd);
715 int iw = MagickGetImageWidth(wd);
717 if (iw <= 0 || iw <= 0) {
718 DestroyMagickWand(wd);
721 if (iw * h > ih * w) {
722 /* Image is wider than space, use less height */
724 switch(pos & (8+4)) {
726 y = (h - ih) / 2; break;
732 /* image is too tall, use less width */
734 switch (pos & (1+2)) {
736 x = (w - iw) / 2; break;
743 MagickAdaptiveResizeImage(wd, w, h);
744 stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w);
745 buf = malloc(h * stride);
746 // Cairo expects 32bit values with A in the high byte, then RGB.
747 // Magick provides 8bit values in the order requests.
748 // So depending on byte order, a different string is needed
750 fmt[0] = ('A'<<24) | ('R' << 16) | ('G' << 8) | ('B' << 0);
752 MagickExportImagePixels(wd, 0, 0, w, h, (char*)fmt, CharPixel, buf);
753 surface = cairo_image_surface_create_for_data(buf, CAIRO_FORMAT_ARGB32,
755 cairo_set_source_surface(ps->ctx, surface, x + xo, y + yo);
756 cairo_paint(ps->ctx);
757 cairo_surface_destroy(surface);
759 DestroyMagickWand(wd);
761 pane_damaged(ci->home, DAMAGED_POSTORDER);
766 static struct panes *sort_split(struct panes *p)
768 /* consider 'p' to be a list of panes with
769 * ordered subsets (ordered by p->abs_z).
770 * Remove every other such subset and return them
772 * If p is ordered, this means we return NULL.
774 struct panes *ret, **end = &ret;
777 for (; p && p->next; p = next) {
778 /* If these are not ordered, attach p->next at
779 * 'end', and make 'end' point to &p->next.
782 if (p->p->abs_z <= next->p->abs_z)
791 static struct panes *sort_merge(struct panes *p1, struct panes *p2)
793 /* merge p1 and p2 and return result */
794 struct panes *ret, **end = &ret;
798 /* if both arg large or smaller than lastz, choose
799 * least, else choose largest
801 struct panes *lo, *hi, *choice;
802 if (p1->p->abs_z <= p2->p->abs_z) {
807 if (lo->p->abs_z >= lastz || hi->p->abs_z <= lastz)
825 DEF_CMD(xcb_refresh_post)
827 struct xcb_data *xd = ci->home->data;
830 time_start(TIME_WINDOW);
831 /* First: ensure panes are sorted */
832 while ((ps = sort_split(xd->panes)) != NULL)
833 xd->panes = sort_merge(xd->panes, ps);
835 /* Now copy all panes onto the window */
836 for (ps = xd->panes; ps; ps = ps->next) {
837 double lox, hix, loy, hiy;
838 struct xy rel, lo, hi;
840 rel = pane_mapxy(ps->p, ci->home, 0, 0, False);
841 lo = pane_mapxy(ps->p, ci->home, 0, 0, True);
842 hi = pane_mapxy(ps->p, ci->home, ps->p->w, ps->p->h, True);
843 lox = lo.x; loy = lo.y;
844 hix = hi.x; hiy = hi.y;
845 cairo_save(xd->cairo);
846 cairo_set_source_surface(xd->cairo, ps->surface,
848 cairo_rectangle(xd->cairo, lox, loy, hix-lox, hiy-loy);
849 cairo_fill(xd->cairo);
850 cairo_restore(xd->cairo);
852 time_stop(TIME_WINDOW);
857 DEF_CMD(xcb_pane_close)
859 struct xcb_data *xd = ci->home->data;
860 struct panes **pp, *ps;
862 for (pp = &xd->panes; (ps = *pp) != NULL; pp = &(*pp)->next) {
863 if (ps->p != ci->focus)
867 cairo_destroy(ps->ctx);
868 cairo_surface_destroy(ps->surface);
869 xcb_free_pixmap(xd->conn, ps->draw);
871 pane_damaged(ci->home, DAMAGED_POSTORDER);
877 DEF_CMD(xcb_notify_display)
879 struct xcb_data *xd = ci->home->data;
880 comm_call(ci->comm2, "callback:display", ci->home, xd->last_event);
884 static void handle_button(struct pane *home safe,
885 xcb_button_press_event_t *be safe)
887 struct xcb_data *xd = home->data;
888 bool press = (be->response_type & 0x7f) == XCB_BUTTON_PRESS;
890 char key[2+2+2+9+1+1];
894 xd->motion_blocked = False;
895 if (be->state & XCB_KEY_BUT_MASK_MOD_1)
897 if (be->state & XCB_KEY_BUT_MASK_CONTROL)
899 if (be->state & XCB_KEY_BUT_MASK_SHIFT)
902 strcat(key, ":Press-X");
904 strcpy(key, ":Release-X");
905 key[strlen(key) - 1] = '0' + be->detail;
906 xd->last_event = time(NULL);
907 call("Mouse-event", home, be->detail, NULL, key,
908 press?1:2, NULL, mod,
909 be->event_x, be->event_y);
912 static void handle_motion(struct pane *home safe,
913 xcb_motion_notify_event_t *mne safe)
915 struct xcb_data *xd = home->data;
916 xcb_query_pointer_cookie_t c;
917 xcb_query_pointer_reply_t *qpr;
919 int x = mne->event_x, y = mne->event_y;
921 if (xd->motion_blocked)
923 ret = call("Mouse-event", home, 0, NULL, ":Motion",
924 3, NULL, NULL, x, y);
926 xd->motion_blocked = True;
927 c = xcb_query_pointer(xd->conn, xd->win);
928 qpr = xcb_query_pointer_reply(xd->conn, c, NULL);
932 static void handle_focus(struct pane *home safe, xcb_focus_in_event_t *fie safe)
934 struct xcb_data *xd = home->data;
935 bool in = (fie->response_type & 0x7f) == XCB_FOCUS_IN;
941 pt = call_ret(mark, "doc:point", p);
943 call("view:changed", p, 0, pt);
945 call("pane:refocus", home);
948 static bool select_xkb_events_for_device(xcb_connection_t *conn,
951 xcb_generic_error_t *error;
952 xcb_void_cookie_t cookie;
956 (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
957 XCB_XKB_EVENT_TYPE_MAP_NOTIFY |
958 XCB_XKB_EVENT_TYPE_STATE_NOTIFY),
960 required_nkn_details =
961 (XCB_XKB_NKN_DETAIL_KEYCODES),
964 (XCB_XKB_MAP_PART_KEY_TYPES |
965 XCB_XKB_MAP_PART_KEY_SYMS |
966 XCB_XKB_MAP_PART_MODIFIER_MAP |
967 XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
968 XCB_XKB_MAP_PART_KEY_ACTIONS |
969 XCB_XKB_MAP_PART_VIRTUAL_MODS |
970 XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP),
972 required_state_details =
973 (XCB_XKB_STATE_PART_MODIFIER_BASE |
974 XCB_XKB_STATE_PART_MODIFIER_LATCH |
975 XCB_XKB_STATE_PART_MODIFIER_LOCK |
976 XCB_XKB_STATE_PART_GROUP_BASE |
977 XCB_XKB_STATE_PART_GROUP_LATCH |
978 XCB_XKB_STATE_PART_GROUP_LOCK),
981 static const xcb_xkb_select_events_details_t details = {
982 .affectNewKeyboard = required_nkn_details,
983 .newKeyboardDetails = required_nkn_details,
984 .affectState = required_state_details,
985 .stateDetails = required_state_details,
988 cookie = xcb_xkb_select_events_aux_checked(
991 required_events, /* affectWhich */
994 required_map_parts, /* affectMap */
995 required_map_parts, /* map */
996 &details); /* details */
998 error = xcb_request_check(conn, cookie);
1007 static bool update_keymap(struct xcb_data *xd safe)
1009 struct xkb_keymap *new_keymap;
1010 struct xkb_state *new_state;
1012 new_keymap = xkb_x11_keymap_new_from_device(xd->xkb, xd->conn,
1014 XKB_KEYMAP_COMPILE_NO_FLAGS);
1018 new_state = xkb_x11_state_new_from_device(new_keymap, xd->conn,
1021 xkb_keymap_unref(new_keymap);
1025 xkb_state_unref(xd->xkb_state);
1026 xkb_keymap_unref(xd->xkb_keymap);
1027 xd->xkb_keymap = new_keymap;
1028 xd->xkb_state = new_state;
1032 static bool kbd_setup(struct xcb_data *xd safe)
1037 ret = xkb_x11_setup_xkb_extension(xd->conn,
1038 XKB_X11_MIN_MAJOR_XKB_VERSION,
1039 XKB_X11_MIN_MINOR_XKB_VERSION,
1040 XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
1041 NULL, NULL, &xd->first_xkb_event,
1046 xd->xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
1049 xd->xkb_device_id = xkb_x11_get_core_keyboard_device_id(xd->conn);
1050 if (xd->xkb_device_id == -1)
1053 if (!update_keymap(xd))
1056 if (!select_xkb_events_for_device(xd->conn, xd->xkb_device_id))
1059 locale = setlocale(LC_CTYPE, NULL);
1061 xkb_compose_table_new_from_locale(xd->xkb, locale,
1062 XKB_COMPOSE_COMPILE_NO_FLAGS);
1063 if (xd->compose_table)
1065 xkb_compose_state_new(xd->compose_table,
1066 XKB_COMPOSE_STATE_NO_FLAGS);
1070 static void kbd_free(struct xcb_data *xd safe)
1072 if (xd->compose_table)
1073 xkb_compose_table_unref(xd->compose_table);
1075 xkb_keymap_unref(xd->xkb_keymap);
1077 xkb_context_unref(xd->xkb);
1081 char *from safe, *to safe;
1083 { "Return", ":Enter"},
1085 { "ISO_Left_Tab",":Tab"},
1086 { "Escape", ":ESC"},
1087 { "Linefeed", ":LF"},
1091 { "Right", ":Right"},
1094 { "BackSpace", ":Backspace"},
1095 { "Delete", ":Del"},
1096 { "Insert", ":Ins"},
1097 { "Next", ":Prior"},
1098 { "Prior", ":Next"},
1113 static void handle_key_press(struct pane *home safe,
1114 xcb_key_press_event_t *kpe safe)
1116 struct xcb_data *xd = home->data;
1117 xkb_keycode_t keycode = kpe->detail;
1118 xcb_keysym_t keysym;
1120 const xkb_keysym_t *syms;
1122 enum xkb_compose_status status;
1123 xkb_mod_index_t mod;
1127 bool shift=False, ctrl=False, alt=False;
1129 keysym = xkb_state_key_get_one_sym(xd->xkb_state,
1131 if (xd->compose_state)
1132 xkb_compose_state_feed(xd->compose_state, keysym);
1133 nsyms = xkb_state_key_get_syms(xd->xkb_state, keycode,
1137 status = XKB_COMPOSE_NOTHING;
1138 if (xd->compose_state)
1139 status = xkb_compose_state_get_status(xd->compose_state);
1140 if (status == XKB_COMPOSE_COMPOSING ||
1141 status == XKB_COMPOSE_CANCELLED)
1144 for (mod = 0; mod < xkb_keymap_num_mods(xd->xkb_keymap); mod++) {
1146 if (xkb_state_mod_index_is_active(
1148 XKB_STATE_MODS_EFFECTIVE) <= 0)
1150 /* This does tells me "shift" is consumed for :C:S-l ...
1151 if (xkb_state_mod_index_is_consumed2(
1152 xd->xkb_state, keycode, mod,
1153 XKB_CONSUMED_MODE_XKB))
1156 n = xkb_keymap_mod_get_name(xd->xkb_keymap, mod);
1157 if (n && strcmp(n, "Shift") == 0)
1159 if (n && strcmp(n, "Control") == 0)
1161 if (n && strcmp(n, "Mod1") == 0)
1165 if (status == XKB_COMPOSE_COMPOSED) {
1166 sym = xkb_compose_state_get_one_sym(xd->compose_state);
1170 xkb_compose_state_get_utf8(xd->compose_state,
1175 /* Mod1 can still apply to a composed char */
1176 } else if (nsyms == 1) {
1178 sym = xkb_state_key_get_one_sym(xd->xkb_state, keycode);
1181 xkb_state_key_get_utf8(xd->xkb_state, keycode,
1183 xkb_keysym_get_name(syms[0], key, sizeof(key));
1184 for (i = 0; i < ARRAY_SIZE(key_map); i++) {
1185 if (strcmp(key, key_map[i].from) == 0) {
1186 strcpy(s, key_map[i].to);
1190 if (s[0] == '-' && s[1] >= ' ' && s[1] < 0x7f)
1191 /* Shift is included */
1193 if (s[0] == '-' && s[1] && (unsigned char)(s[1]) < ' ') {
1196 if (s[1] < 'A' || s[1] > 'Z')
1198 } else if (s[0] == '-' && !s[1]) {
1199 /* 'nul' becomes "C- " (ctrl-space) */
1206 if (xd->compose_state &&
1207 (status == XKB_COMPOSE_CANCELLED ||
1208 status == XKB_COMPOSE_COMPOSED))
1209 xkb_compose_state_reset(xd->compose_state);
1220 call("Keystroke", home, 0, NULL, mods);
1224 static void handle_xkb_event(struct pane *home safe,
1225 xcb_generic_event_t *ev safe)
1227 struct xcb_data *xd = home->data;
1230 xcb_xkb_new_keyboard_notify_event_t *nkne;
1231 xcb_xkb_state_notify_event_t *sne;
1232 xcb_xkb_map_notify_event_t *mne;
1233 case XCB_XKB_NEW_KEYBOARD_NOTIFY:
1235 if (nkne->deviceID == xd->xkb_device_id &&
1236 nkne->changed & XCB_XKB_NKN_DETAIL_KEYCODES)
1239 case XCB_XKB_MAP_NOTIFY:
1241 if (mne->deviceID == xd->xkb_device_id)
1244 case XCB_XKB_STATE_NOTIFY:
1246 if (sne->deviceID == xd->xkb_device_id)
1247 xkb_state_update_mask(xd->xkb_state,
1258 static void handle_configure(struct pane *home safe,
1259 xcb_configure_notify_event_t *cne safe)
1261 struct xcb_data *xd = home->data;
1263 pane_resize(home, 0, 0, cne->width, cne->height);
1264 cairo_xcb_surface_set_size(xd->surface, cne->width, cne->height);
1269 struct xcb_data *xd = ci->home->data;
1270 xcb_generic_event_t *ev;
1272 while ((ev = xcb_poll_for_event(xd->conn)) != NULL) {
1273 switch (ev->response_type & 0x7f) {
1275 time_start(TIME_KEY);
1276 handle_key_press(ci->home, safe_cast (void*)ev);
1277 time_stop(TIME_KEY);
1279 case XCB_KEY_RELEASE:
1280 /* Ignore for now */
1282 case XCB_BUTTON_PRESS:
1283 case XCB_BUTTON_RELEASE:
1284 time_start(TIME_KEY);
1285 handle_button(ci->home, (void*)ev);
1286 time_stop(TIME_KEY);
1288 case XCB_MOTION_NOTIFY:
1289 time_start(TIME_KEY);
1290 handle_motion(ci->home, (void*)ev);
1291 time_stop(TIME_KEY);
1295 time_start(TIME_WINDOW);
1296 handle_focus(ci->home, (void*)ev);
1297 time_stop(TIME_WINDOW);
1300 pane_damaged(ci->home, DAMAGED_POSTORDER);
1302 case XCB_CONFIGURE_NOTIFY:
1303 time_start(TIME_WINDOW);
1304 handle_configure(ci->home, (void*)ev);
1305 time_stop(TIME_WINDOW);
1307 case XCB_REPARENT_NOTIFY:
1308 /* Not interested */
1310 case XCB_MAP_NOTIFY:
1311 case XCB_UNMAP_NOTIFY:
1312 /* FIXME what to do?? */
1315 if ((ev->response_type & 0x7f) ==
1316 xd->first_xkb_event) {
1317 handle_xkb_event(ci->home, ev);
1320 LOG("ignored %x", ev->response_type);
1322 xcb_flush(xd->conn);
1327 static struct pane *xcb_display_init(const char *d safe, struct pane *focus safe)
1329 struct xcb_data *xd;
1331 xcb_connection_t *conn;
1332 xcb_intern_atom_cookie_t cookies[NR_ATOMS];
1333 xcb_screen_iterator_t iter;
1334 xcb_depth_iterator_t di;
1339 PangoLayout *layout;
1342 cairo_surface_t *surface;
1343 PangoFontDescription *fd;
1344 // FIXME SCALE from environ?? or pango_cairo_context_set_resolution dpi
1345 // 254 * width_in_pixels / width_in_millimeters / 10
1347 conn = xcb_connect(d, &screen);
1353 xd->motion_blocked = True;
1354 xd->in_focus = True;
1357 xd->display = strdup(d);
1358 xd->setup = safe_cast xcb_get_setup(conn);
1359 iter = xcb_setup_roots_iterator(xd->setup);
1360 for (i = 0; i < screen; i++)
1361 xcb_screen_next(&iter);
1362 xd->screen = safe_cast iter.data;
1364 di = xcb_screen_allowed_depths_iterator(xd->screen);
1365 while (di.data && di.data->depth < 24)
1366 xcb_depth_next(&di);
1367 //?? look for class = TrueColor??
1368 xd->visual = xcb_depth_visuals(di.data);
1370 for (i = 0; i < NR_ATOMS; i++) {
1371 char *n = atom_names[i];
1374 cookies[i] = xcb_intern_atom(conn, 0, strlen(n), n);
1377 xd->win = xcb_generate_id(conn);
1378 valwin[0] = xd->screen->white_pixel;
1379 valwin[1] = (XCB_EVENT_MASK_KEY_PRESS |
1380 XCB_EVENT_MASK_KEY_RELEASE |
1381 XCB_EVENT_MASK_BUTTON_PRESS |
1382 XCB_EVENT_MASK_BUTTON_RELEASE |
1383 // XCB_EVENT_MASK_ENTER_WINDOW |
1384 // XCB_EVENT_MASK_LEAVE_WINDOW |
1385 XCB_EVENT_MASK_FOCUS_CHANGE |
1386 XCB_EVENT_MASK_STRUCTURE_NOTIFY |
1387 XCB_EVENT_MASK_EXPOSURE |
1388 XCB_EVENT_MASK_POINTER_MOTION |
1389 // FIXME XCB_EVENT_MASK_POINTER_MOTION_HINT |
1392 xcb_create_window(conn, XCB_COPY_FROM_PARENT, xd->win,
1396 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
1397 xd->screen->root_visual,
1398 XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
1403 surface = cairo_xcb_surface_create(
1404 conn, xd->win, xd->visual, 100, 100);
1407 xd->surface = surface;
1408 cairo = cairo_create(xd->surface);
1412 fd = pango_font_description_new();
1416 pango_font_description_set_family(xd->fd, "mono");
1417 pango_font_description_set_size(xd->fd, 12 * PANGO_SCALE);
1419 layout = pango_cairo_create_layout(xd->cairo);
1420 pango_layout_set_font_description(layout, fd);
1421 pango_layout_set_text(layout, "M", 1);
1422 pango_layout_get_pixel_extents(layout, NULL, &log);
1423 g_object_unref(layout);
1424 xd->lineheight = log.height;
1425 xd->charwidth = log.width;
1427 valwin[0] = xd->charwidth * 80;
1428 valwin[1] = xd->lineheight * 26;
1429 xcb_configure_window(conn, xd->win,
1430 XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
1432 cairo_xcb_surface_set_size(xd->surface, valwin[0], valwin[1]);
1434 /* Now resolve all those cookies */
1435 for (i = 0; i < NR_ATOMS; i++) {
1436 xcb_intern_atom_reply_t *r;
1437 r = xcb_intern_atom_reply(conn, cookies[i], NULL);
1440 xd->atoms[i] = r->atom;
1446 * WM_PROTOCOLS WM_DELETE_WINDOW WM_TAKE_FOCUS _NET_WM_PING _NET_WM_SYN_REQUEST??
1447 * WM_NORMAL_HINTS WM_HINTS
1450 xcb_map_window(conn, xd->win);
1452 p = pane_register(pane_root(focus), 1, &xcb_handle.c, xd);
1455 pane_resize(p, 0, 0, xd->charwidth*80, xd->lineheight*26);
1456 call_comm("event:read", p, &xcb_input, xcb_get_file_descriptor(conn));
1457 attr_set_str(&p->attrs, "DISPLAY", d);
1458 snprintf(scale, sizeof(scale), "%dx%d", xd->charwidth, xd->lineheight);
1459 attr_set_str(&p->attrs, "scale:M", scale);
1460 call("editor:request:all-displays", p);
1464 cairo_destroy(xd->cairo);
1465 cairo_surface_destroy(xd->surface);
1466 xcb_disconnect(conn);
1467 // FIXME free stuff;
1472 DEF_CMD(display_xcb)
1475 const char *d = ci->str;
1479 p = xcb_display_init(d, ci->focus);
1481 return comm_call(ci->comm2, "cb", p);
1485 DEF_CMD(xcb_new_display)
1488 char *d = pane_attr_get(ci->focus, "DISPLAY");
1492 p = xcb_display_init(d, ci->focus);
1494 p = call_ret(pane, "editor:activate-display", p);
1496 home_call(ci->focus, "doc:attach-view", p, 1);
1500 void edlib_init(struct pane *ed safe)
1502 call_comm("global-set-command", ed, &display_xcb, 0, NULL,
1503 "attach-display-x11");
1504 call_comm("global-set-command", ed, &xcb_new_display, 0, NULL,
1505 "interactive-cmd-x11window");
1507 xcb_map = key_alloc();
1509 key_add(xcb_map, "Display:close", &xcb_close_display);
1510 key_add(xcb_map, "Display:set-noclose", &xcb_set_noclose);
1511 key_add(xcb_map, "Display:external-viewer", &xcb_external_viewer);
1512 key_add(xcb_map, "Display:fullscreen", &xcb_fullscreen);
1513 key_add(xcb_map, "Display:new", &xcb_new_display);
1515 key_add(xcb_map, "Close", &xcb_close);
1516 key_add(xcb_map, "Free", &edlib_do_free);
1517 key_add(xcb_map, "Draw:clear", &xcb_clear);
1518 key_add(xcb_map, "Draw:text-size", &xcb_text_size);
1519 key_add(xcb_map, "Draw:text", &xcb_draw_text);
1520 key_add(xcb_map, "Draw:image", &xcb_draw_image);
1521 key_add(xcb_map, "Refresh:postorder", &xcb_refresh_post);
1522 key_add(xcb_map, "all-displays", &xcb_notify_display);
1523 key_add(xcb_map, "Notify:Close", &xcb_pane_close);