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",
153 xcb_connection_t *conn safe;
156 const xcb_setup_t *setup safe;
157 const xcb_screen_t *screen safe;
158 xcb_atom_t atoms[NR_ATOMS];
162 xcb_visualtype_t *visual;
164 cairo_surface_t *surface safe;
165 PangoFontDescription *fd safe;
167 int charwidth, lineheight;
172 struct xkb_context *xkb;
173 uint8_t first_xkb_event;
174 int32_t xkb_device_id;
175 struct xkb_state *xkb_state;
176 struct xkb_compose_state *compose_state;
177 struct xkb_compose_table *compose_table;
178 struct xkb_keymap *xkb_keymap;
180 /* FIXME use hash?? */
187 cairo_surface_t *surface safe;
191 static struct map *xcb_map;
192 DEF_LOOKUP_CMD(xcb_handle, xcb_map);
194 static cairo_t *get_pixmap(struct pane *home safe,
197 struct xcb_data *xd = home->data;
198 struct panes **pp, *ps;
199 cairo_surface_t *surface;
202 for (pp = &xd->panes; (ps = *pp) != NULL; pp = &(*pp)->next) {
205 if (ps->w == p->w && ps->h == p->h)
208 cairo_destroy(ps->ctx);
209 cairo_surface_destroy(ps->surface);
210 xcb_free_pixmap(xd->conn, ps->draw);
218 ps->draw = xcb_generate_id(xd->conn);
219 xcb_create_pixmap(xd->conn, xd->screen->root_depth, ps->draw,
220 xd->win, p->w, p->h);
221 surface = cairo_xcb_surface_create(
222 xd->conn, ps->draw, xd->visual, p->w, p->h);
225 ctx = cairo_create(surface);
229 ps->surface = surface;
231 pane_add_notify(home, p, "Notify:Close");
236 cairo_surface_destroy(surface);
238 xcb_free_pixmap(xd->conn, ps->draw);
243 static struct panes *find_pixmap(struct xcb_data *xd safe, struct pane *p safe,
244 int *xp safe, int *yp safe)
247 struct panes *ret = NULL;
249 while (!ret && p->parent != p) {
251 for (ps = xd->panes; ps ; ps = ps->next)
271 static inline double cvt(int i)
273 return (float)i / 1000.0;
276 static void parse_attrs(
277 struct pane *home safe, const char *cattrs, int scale,
278 struct rgb *fgp, struct rgb *bgp, bool *underline,
279 PangoFontDescription **fdp)
281 char *attrs = strdup(cattrs ?: "");
284 char *fg = NULL, *bg = NULL;
288 PangoFontDescription *fd = NULL;
289 PangoStyle style = PANGO_STYLE_NORMAL;
290 PangoVariant variant = PANGO_VARIANT_NORMAL;
291 PangoWeight weight = PANGO_WEIGHT_NORMAL;
294 fd = pango_font_description_new();
296 pango_font_description_set_family_static(fd, "mono");
299 while ((word = strsep(&ap, ",")) != NULL) {
300 if (fd && strncmp(word, "family:", 7) == 0)
301 pango_font_description_set_family(fd, word+7);
302 if (strcmp(word, "large") == 0)
304 if (strcmp(word, "small") == 0)
306 if (isdigit(word[0])) {
308 double s = strtod(word, &end);
309 if (end && end != word && !*end)
310 size = trunc(s * 1000.0);
314 if (strcmp(word, "oblique") == 0)
315 style = PANGO_STYLE_OBLIQUE;
316 if (strcmp(word, "italic") == 0)
317 style = PANGO_STYLE_ITALIC;
318 if (strcmp(word, "normal") == 0)
319 style = PANGO_STYLE_NORMAL;
320 if (strcmp(word, "small-caps") == 0)
321 variant = PANGO_VARIANT_SMALL_CAPS;
323 if (strcmp(word, "bold") == 0)
324 weight = PANGO_WEIGHT_BOLD;
325 if (strcmp(word, "nobold") == 0)
326 weight = PANGO_WEIGHT_NORMAL;
328 if (strncmp(word, "fg:", 3) == 0)
330 if (strncmp(word, "bg:", 3) == 0)
332 if (strcmp(word, "inverse") == 0)
334 if (strcmp(word, "underline") == 0)
350 struct call_return ret = call_ret(all, "colour:map", home,
353 fgp->g = cvt(ret.i2);
358 struct call_return ret = call_ret(all, "colour:map", home,
361 bgp->g = cvt(ret.i2);
366 pango_font_description_set_size(fd, size * scale / PANGO_SCALE);
367 if (style != PANGO_STYLE_NORMAL)
368 pango_font_description_set_style(fd, style);
369 if (variant != PANGO_VARIANT_NORMAL)
370 pango_font_description_set_variant(fd, variant);
371 if (weight != PANGO_WEIGHT_NORMAL)
372 pango_font_description_set_weight(fd, weight);
381 struct call_return *cr = container_of(ci->comm, struct call_return, c);
387 DEF_CMD(xcb_close_display)
389 /* If this is only display, then refuse to close this one */
390 struct call_return cr;
391 struct xcb_data *xd = ci->home->data;
393 call("Message", ci->focus, 0, NULL, xd->noclose);
398 call_comm("editor:notify:all-displays", ci->focus, &cr.c);
400 pane_close(ci->home);
402 call("Message", ci->focus, 0, NULL,
403 "Cannot close only window.");
407 DEF_CMD(xcb_set_noclose)
409 struct xcb_data *xd = ci->home->data;
414 xd->noclose = strdup(ci->str);
418 DEF_CMD(xcb_external_viewer)
420 //struct xcb_data *xd = ci->home->data;
425 DEF_CMD(xcb_fullscreen)
427 struct xcb_data *xd = ci->home->data;
428 xcb_client_message_event_t msg = {};
430 msg.response_type = XCB_CLIENT_MESSAGE;
432 msg.window = xd->win;
433 msg.type = xd->atoms[a_WM_STATE];
435 msg.data.data32[0] = 1; /* ADD */
437 msg.data.data32[0] = 0; /* REMOVE */
438 msg.data.data32[1] = xd->atoms[a_STATE_FULLSCREEN];
439 msg.data.data32[2] = 0;
440 msg.data.data32[3] = 1; /* source indicator */
442 xcb_send_event(xd->conn, 0, xd->screen->root,
443 XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
444 XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
452 struct xcb_data *xd = ci->home->data;
453 xcb_destroy_window(xd->conn, xd->win);
454 xcb_disconnect(xd->conn);
461 struct xcb_data *xd = ci->home->data;
462 const char *attr = ci->str;
463 struct panes *src = NULL;
469 parse_attrs(ci->home, attr, PANGO_SCALE, NULL, &bg, NULL, NULL);
471 src = find_pixmap(xd, ci->focus->parent, &x, &y);
474 bg.r = bg.g = bg.b = 1.0;
477 pm = get_pixmap(ci->home, ci->focus);
481 cairo_set_source_surface(pm, src->surface, -x, -y);
484 cairo_set_source_rgb(pm, bg.r, bg.g, bg.b);
485 cairo_rectangle(pm, 0.0, 0.0,
486 (double)ci->focus->w, (double)ci->focus->h);
489 pane_damaged(ci->home, DAMAGED_POSTORDER);
493 DEF_CMD(xcb_text_size)
495 struct xcb_data *xd = ci->home->data;
496 const char *attr = ci->str2 ?: "";
497 const char *str = ci->str ?: "";
498 int scale = ci->num2;
500 PangoFontDescription *fd;
507 parse_attrs(ci->home, attr, scale, NULL, NULL, NULL, &fd);
508 /* If we use an empty string, line-height it wrong */
509 layout = pango_cairo_create_layout(xd->cairo);
510 pango_layout_set_text(layout, *str ? str : "M", -1);
511 pango_layout_set_font_description(layout, fd);
512 pango_layout_get_pixel_extents(layout, NULL, &log);
513 baseline = pango_layout_get_baseline(layout) / PANGO_SCALE;
517 else if (log.width <= ci->num)
518 max_bytes = strlen(str);
520 pango_layout_xy_to_index(layout, 1000*ci->num,
521 baseline, &max_bytes, NULL);
523 comm_call(ci->comm2, "cb", ci->focus, max_bytes, NULL, NULL,
524 baseline, NULL, NULL,
525 str && *str ? log.width : 0,
528 pango_font_description_free(fd);
529 g_object_unref(layout);
533 DEF_CMD(xcb_draw_text)
535 struct xcb_data *xd = ci->home->data;
536 const char *str = ci->str;
537 const char *attr = ci->str2;
542 PangoFontDescription *fd;
552 ps = find_pixmap(xd, ci->focus, &xo, &yo);
557 pane_damaged(ci->home, DAMAGED_POSTORDER);
560 scale = ci->num2 * 10 / xd->charwidth;
562 parse_attrs(ci->home, attr, scale, &fg, &bg, &ul, &fd);
566 layout = pango_cairo_create_layout(ctx);
567 pango_layout_set_text(layout, str, -1);
568 pango_layout_set_font_description(layout, fd);
569 pango_layout_get_pixel_extents(layout, NULL, &log);
570 baseline = pango_layout_get_baseline(layout) / PANGO_SCALE;
572 cairo_set_source_rgb(ctx, bg.r, bg.g, bg.b);
573 cairo_rectangle(ctx, x+log.x, y - baseline + log.y,
574 log.width, log.height);
577 cairo_set_source_rgb(ctx, fg.r, fg.g, fg.b);
579 /* Draw an underline */
580 cairo_rectangle(ctx, x+log.x, y+2+log.y,
585 cairo_move_to(ctx, x, y - baseline);
586 pango_cairo_show_layout(ctx, layout);
590 /* draw a cursor - outline box if not in-focus,
591 * inverse-video if it is.
594 bool in_focus = xd->in_focus;
595 struct pane *f = ci->focus;
597 pango_layout_index_to_pos(layout, ci->num, &curs);
598 if (curs.width <= 0) {
600 pango_layout_set_text(layout, "M", 1);
601 pango_layout_get_extents(layout, NULL, &log);
602 curs.width = log.width;
604 cairo_rectangle(ctx, x+curs.x/PANGO_SCALE, y-baseline+curs.y/PANGO_SCALE,
605 (curs.width - PANGO_SCALE/2) / PANGO_SCALE,
606 (curs.height - PANGO_SCALE/2) / PANGO_SCALE);
607 cairo_set_line_width(ctx, 1.0);
610 while (in_focus && f->parent->parent != f &&
611 f->parent != ci->home) {
612 if (f->parent->focus != f && f->z >= 0)
618 cairo_set_source_rgb(ctx, fg.r, fg.g, fg.b);
619 cairo_rectangle(ctx, x+curs.x/PANGO_SCALE,
620 y-baseline+curs.y/PANGO_SCALE,
621 curs.width / PANGO_SCALE,
622 curs.height / PANGO_SCALE);
624 if (ci->num < (int)strlen(str)) {
625 const char *cp = str + ci->num;
627 pango_layout_set_text(layout, str + ci->num,
628 cp - (str + ci->num));
630 cairo_set_source_rgb(ctx, bg.r, bg.g, bg.b);
632 cairo_set_source_rgb(ctx, 1.0, 1.0, 1.0);
634 x + curs.x / PANGO_SCALE,
635 y - baseline + curs.y / PANGO_SCALE);
636 pango_cairo_show_layout(ctx, layout);
640 pango_font_description_free(fd);
641 g_object_unref(layout);
645 DEF_CMD(xcb_draw_image)
647 /* 'str' identifies the image. Options are:
648 * file:filename - load file from fs
649 * comm:command - run command collecting bytes
650 * 'num' is '1' if image should be stretched to fill pane
651 * if 'num is '0', then 'num2' is 'or' of
652 * 0,1,2 for left/middle/right in x direction
653 * 0,4,8 for top/middle/bottom in y direction
654 * only one of these can be used as image will fill pane in other direction.
656 struct xcb_data *xd = ci->home->data;
657 bool stretch = ci->num == 1;
659 int w = ci->focus->w, h = ci->focus->h;
664 MagickBooleanType status;
668 cairo_surface_t *surface;
672 ps = find_pixmap(xd, ci->focus, &xo, &yo);
675 if (strncmp(ci->str, "file:", 5) == 0) {
676 wd = NewMagickWand();
677 status = MagickReadImage(wd, ci->str + 5);
678 if (status == MagickFalse) {
679 DestroyMagickWand(wd);
682 } else if (strncmp(ci->str, "comm:", 5) == 0) {
683 struct call_return cr;
684 wd = NewMagickWand();
685 cr = call_ret(bytes, ci->str+5, ci->focus, 0, NULL, ci->str2);
687 DestroyMagickWand(wd);
690 status = MagickReadImageBlob(wd, cr.s, cr.i);
692 if (status == MagickFalse) {
693 DestroyMagickWand(wd);
699 MagickAutoOrientImage(wd);
701 int ih = MagickGetImageHeight(wd);
702 int iw = MagickGetImageWidth(wd);
704 if (iw <= 0 || iw <= 0) {
705 DestroyMagickWand(wd);
708 if (iw * h > ih * w) {
709 /* Image is wider than space, use less height */
711 switch(pos & (8+4)) {
713 y = (h - ih) / 2; break;
719 /* image is too tall, use less width */
721 switch (pos & (1+2)) {
723 x = (w - iw) / 2; break;
730 MagickAdaptiveResizeImage(wd, w, h);
731 stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w);
732 buf = malloc(h * stride);
733 // Cairo expects 32bit values with A in the high byte, then RGB.
734 // Magick provides 8bit values in the order requests.
735 // So depending on byte order, a different string is needed
737 fmt[0] = ('A'<<24) | ('R' << 16) | ('G' << 8) | ('B' << 0);
739 MagickExportImagePixels(wd, 0, 0, w, h, (char*)fmt, CharPixel, buf);
740 surface = cairo_image_surface_create_for_data(buf, CAIRO_FORMAT_ARGB32,
742 cairo_set_source_surface(ps->ctx, surface, x + xo, y + yo);
743 cairo_paint(ps->ctx);
744 cairo_surface_destroy(surface);
746 DestroyMagickWand(wd);
748 pane_damaged(ci->home, DAMAGED_POSTORDER);
753 static struct panes *sort_split(struct panes *p)
755 /* consider 'p' to be a list of panes with
756 * ordered subsets (ordered by p->abs_z).
757 * Remove every other such subset and return them
759 * If p is ordered, this means we return NULL.
761 struct panes *ret, **end = &ret;
764 for (; p && p->next; p = next) {
765 /* If these are not ordered, attach p->next at
766 * 'end', and make 'end' point to &p->next.
769 if (p->p->abs_z <= next->p->abs_z)
778 static struct panes *sort_merge(struct panes *p1, struct panes *p2)
780 /* merge p1 and p2 and return result */
781 struct panes *ret, **end = &ret;
785 /* if both arg large or smaller than lastz, choose
786 * least, else choose largest
788 struct panes *lo, *hi, *choice;
789 if (p1->p->abs_z <= p2->p->abs_z) {
794 if (lo->p->abs_z >= lastz || hi->p->abs_z <= lastz)
812 DEF_CMD(xcb_refresh_post)
814 struct xcb_data *xd = ci->home->data;
817 time_start(TIME_WINDOW);
818 /* First: ensure panes are sorted */
819 while ((ps = sort_split(xd->panes)) != NULL)
820 xd->panes = sort_merge(xd->panes, ps);
822 /* Now copy all panes onto the window */
823 for (ps = xd->panes; ps; ps = ps->next) {
824 double lox, hix, loy, hiy;
825 struct xy rel, lo, hi;
827 rel = pane_mapxy(ps->p, ci->home, 0, 0, False);
828 lo = pane_mapxy(ps->p, ci->home, 0, 0, True);
829 hi = pane_mapxy(ps->p, ci->home, ps->p->w, ps->p->h, True);
830 lox = lo.x; loy = lo.y;
831 hix = hi.x; hiy = hi.y;
832 cairo_save(xd->cairo);
833 cairo_set_source_surface(xd->cairo, ps->surface,
835 cairo_rectangle(xd->cairo, lox, loy, hix, hiy);
836 cairo_fill(xd->cairo);
837 cairo_restore(xd->cairo);
839 time_stop(TIME_WINDOW);
844 DEF_CMD(xcb_pane_close)
846 struct xcb_data *xd = ci->home->data;
847 struct panes **pp, *ps;
849 for (pp = &xd->panes; (ps = *pp) != NULL; pp = &(*pp)->next) {
850 if (ps->p != ci->focus)
854 cairo_destroy(ps->ctx);
855 cairo_surface_destroy(ps->surface);
856 xcb_free_pixmap(xd->conn, ps->draw);
858 pane_damaged(ci->home, DAMAGED_POSTORDER);
864 DEF_CMD(xcb_notify_display)
866 struct xcb_data *xd = ci->home->data;
867 comm_call(ci->comm2, "callback:display", ci->home, xd->last_event);
871 static void handle_button(struct pane *home safe,
872 xcb_button_press_event_t *be safe)
874 struct xcb_data *xd = home->data;
875 bool press = (be->response_type & 0x7f) == XCB_BUTTON_PRESS;
877 char key[2+2+2+9+1+1];
881 xd->motion_blocked = False;
882 if (be->state & XCB_KEY_BUT_MASK_MOD_1)
884 if (be->state & XCB_KEY_BUT_MASK_CONTROL)
886 if (be->state & XCB_KEY_BUT_MASK_SHIFT)
889 strcat(key, ":Press-X");
891 strcpy(key, ":Release-X");
892 key[strlen(key) - 1] = '0' + be->detail;
893 xd->last_event = time(NULL);
894 call("Mouse-event", home, be->detail, NULL, key,
895 press?1:2, NULL, mod,
896 be->event_x, be->event_y);
899 static void handle_motion(struct pane *home safe,
900 xcb_motion_notify_event_t *mne safe)
902 struct xcb_data *xd = home->data;
903 xcb_query_pointer_cookie_t c;
904 xcb_query_pointer_reply_t *qpr;
906 int x = mne->event_x, y = mne->event_y;
908 if (xd->motion_blocked)
910 ret = call("Mouse-event", home, 0, NULL, ":Motion",
911 3, NULL, NULL, x, y);
913 xd->motion_blocked = True;
914 c = xcb_query_pointer(xd->conn, xd->win);
915 qpr = xcb_query_pointer_reply(xd->conn, c, NULL);
919 static void handle_focus(struct pane *home safe, xcb_focus_in_event_t *fie safe)
921 struct xcb_data *xd = home->data;
922 bool in = (fie->response_type & 0x7f) == XCB_FOCUS_IN;
928 pt = call_ret(mark, "doc:point", p);
930 call("view:changed", p, 0, pt);
932 call("pane:refocus", home);
935 static bool select_xkb_events_for_device(xcb_connection_t *conn,
938 xcb_generic_error_t *error;
939 xcb_void_cookie_t cookie;
943 (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
944 XCB_XKB_EVENT_TYPE_MAP_NOTIFY |
945 XCB_XKB_EVENT_TYPE_STATE_NOTIFY),
947 required_nkn_details =
948 (XCB_XKB_NKN_DETAIL_KEYCODES),
951 (XCB_XKB_MAP_PART_KEY_TYPES |
952 XCB_XKB_MAP_PART_KEY_SYMS |
953 XCB_XKB_MAP_PART_MODIFIER_MAP |
954 XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
955 XCB_XKB_MAP_PART_KEY_ACTIONS |
956 XCB_XKB_MAP_PART_VIRTUAL_MODS |
957 XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP),
959 required_state_details =
960 (XCB_XKB_STATE_PART_MODIFIER_BASE |
961 XCB_XKB_STATE_PART_MODIFIER_LATCH |
962 XCB_XKB_STATE_PART_MODIFIER_LOCK |
963 XCB_XKB_STATE_PART_GROUP_BASE |
964 XCB_XKB_STATE_PART_GROUP_LATCH |
965 XCB_XKB_STATE_PART_GROUP_LOCK),
968 static const xcb_xkb_select_events_details_t details = {
969 .affectNewKeyboard = required_nkn_details,
970 .newKeyboardDetails = required_nkn_details,
971 .affectState = required_state_details,
972 .stateDetails = required_state_details,
975 cookie = xcb_xkb_select_events_aux_checked(
978 required_events, /* affectWhich */
981 required_map_parts, /* affectMap */
982 required_map_parts, /* map */
983 &details); /* details */
985 error = xcb_request_check(conn, cookie);
994 static bool update_keymap(struct xcb_data *xd safe)
996 struct xkb_keymap *new_keymap;
997 struct xkb_state *new_state;
999 new_keymap = xkb_x11_keymap_new_from_device(xd->xkb, xd->conn,
1001 XKB_KEYMAP_COMPILE_NO_FLAGS);
1005 new_state = xkb_x11_state_new_from_device(new_keymap, xd->conn,
1008 xkb_keymap_unref(new_keymap);
1012 xkb_state_unref(xd->xkb_state);
1013 xkb_keymap_unref(xd->xkb_keymap);
1014 xd->xkb_keymap = new_keymap;
1015 xd->xkb_state = new_state;
1019 static bool kbd_setup(struct xcb_data *xd safe)
1024 ret = xkb_x11_setup_xkb_extension(xd->conn,
1025 XKB_X11_MIN_MAJOR_XKB_VERSION,
1026 XKB_X11_MIN_MINOR_XKB_VERSION,
1027 XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
1028 NULL, NULL, &xd->first_xkb_event,
1033 xd->xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
1036 xd->xkb_device_id = xkb_x11_get_core_keyboard_device_id(xd->conn);
1037 if (xd->xkb_device_id == -1)
1040 if (!update_keymap(xd))
1043 if (!select_xkb_events_for_device(xd->conn, xd->xkb_device_id))
1046 locale = setlocale(LC_CTYPE, NULL);
1048 xkb_compose_table_new_from_locale(xd->xkb, locale,
1049 XKB_COMPOSE_COMPILE_NO_FLAGS);
1050 if (xd->compose_table)
1052 xkb_compose_state_new(xd->compose_table,
1053 XKB_COMPOSE_STATE_NO_FLAGS);
1057 static void kbd_free(struct xcb_data *xd safe)
1059 if (xd->compose_table)
1060 xkb_compose_table_unref(xd->compose_table);
1062 xkb_keymap_unref(xd->xkb_keymap);
1064 xkb_context_unref(xd->xkb);
1068 char *from safe, *to safe;
1070 { "Return", ":Enter"},
1072 { "ISO_Left_Tab",":Tab"},
1073 { "Escape", ":ESC"},
1074 { "Linefeed", ":LF"},
1078 { "Right", ":Right"},
1081 { "BackSpace", ":Backspace"},
1082 { "Delete", ":Del"},
1083 { "Insert", ":Ins"},
1084 { "Next", ":Prior"},
1085 { "Prior", ":Next"},
1100 static void handle_key_press(struct pane *home safe,
1101 xcb_key_press_event_t *kpe safe)
1103 struct xcb_data *xd = home->data;
1104 xkb_keycode_t keycode = kpe->detail;
1105 xcb_keysym_t keysym;
1107 const xkb_keysym_t *syms;
1109 enum xkb_compose_status status;
1110 xkb_mod_index_t mod;
1114 bool shift=False, ctrl=False, alt=False;
1116 keysym = xkb_state_key_get_one_sym(xd->xkb_state,
1118 if (xd->compose_state)
1119 xkb_compose_state_feed(xd->compose_state, keysym);
1120 nsyms = xkb_state_key_get_syms(xd->xkb_state, keycode,
1124 status = XKB_COMPOSE_NOTHING;
1125 if (xd->compose_state)
1126 status = xkb_compose_state_get_status(xd->compose_state);
1127 if (status == XKB_COMPOSE_COMPOSING ||
1128 status == XKB_COMPOSE_CANCELLED)
1131 for (mod = 0; mod < xkb_keymap_num_mods(xd->xkb_keymap); mod++) {
1133 if (xkb_state_mod_index_is_active(
1135 XKB_STATE_MODS_EFFECTIVE) <= 0)
1137 /* This does tells me "shift" is consumed for :C:S-l ...
1138 if (xkb_state_mod_index_is_consumed2(
1139 xd->xkb_state, keycode, mod,
1140 XKB_CONSUMED_MODE_XKB))
1143 n = xkb_keymap_mod_get_name(xd->xkb_keymap, mod);
1144 if (n && strcmp(n, "Shift") == 0)
1146 if (n && strcmp(n, "Control") == 0)
1148 if (n && strcmp(n, "Mod1") == 0)
1152 if (status == XKB_COMPOSE_COMPOSED) {
1153 sym = xkb_compose_state_get_one_sym(xd->compose_state);
1157 xkb_compose_state_get_utf8(xd->compose_state,
1162 /* Mod1 can still apply to a composed char */
1163 } else if (nsyms == 1) {
1165 sym = xkb_state_key_get_one_sym(xd->xkb_state, keycode);
1168 xkb_state_key_get_utf8(xd->xkb_state, keycode,
1170 xkb_keysym_get_name(syms[0], key, sizeof(key));
1171 for (i = 0; i < ARRAY_SIZE(key_map); i++) {
1172 if (strcmp(key, key_map[i].from) == 0) {
1173 strcpy(s, key_map[i].to);
1177 if (s[0] == '-' && s[1] >= ' ' && s[1] < 0x7f)
1178 /* Shift is included */
1180 if (s[0] == '-' && s[1] && (unsigned char)(s[1]) < ' ') {
1186 if (xd->compose_state &&
1187 (status == XKB_COMPOSE_CANCELLED ||
1188 status == XKB_COMPOSE_COMPOSED))
1189 xkb_compose_state_reset(xd->compose_state);
1200 call("Keystroke", home, 0, NULL, mods);
1204 static void handle_xkb_event(struct pane *home safe,
1205 xcb_generic_event_t *ev safe)
1207 struct xcb_data *xd = home->data;
1210 xcb_xkb_new_keyboard_notify_event_t *nkne;
1211 xcb_xkb_state_notify_event_t *sne;
1212 xcb_xkb_map_notify_event_t *mne;
1213 case XCB_XKB_NEW_KEYBOARD_NOTIFY:
1215 if (nkne->deviceID == xd->xkb_device_id &&
1216 nkne->changed & XCB_XKB_NKN_DETAIL_KEYCODES)
1219 case XCB_XKB_MAP_NOTIFY:
1221 if (mne->deviceID == xd->xkb_device_id)
1224 case XCB_XKB_STATE_NOTIFY:
1226 if (sne->deviceID == xd->xkb_device_id)
1227 xkb_state_update_mask(xd->xkb_state,
1238 static void handle_configure(struct pane *home safe,
1239 xcb_configure_notify_event_t *cne safe)
1241 struct xcb_data *xd = home->data;
1243 pane_resize(home, 0, 0, cne->width, cne->height);
1244 cairo_xcb_surface_set_size(xd->surface, cne->width, cne->height);
1249 struct xcb_data *xd = ci->home->data;
1250 xcb_generic_event_t *ev;
1252 while ((ev = xcb_poll_for_event(xd->conn)) != NULL) {
1253 switch (ev->response_type & 0x7f) {
1255 time_start(TIME_KEY);
1256 handle_key_press(ci->home, safe_cast (void*)ev);
1257 time_stop(TIME_KEY);
1259 case XCB_KEY_RELEASE:
1260 /* Ignore for now */
1262 case XCB_BUTTON_PRESS:
1263 case XCB_BUTTON_RELEASE:
1264 time_start(TIME_KEY);
1265 handle_button(ci->home, (void*)ev);
1266 time_stop(TIME_KEY);
1268 case XCB_MOTION_NOTIFY:
1269 time_start(TIME_KEY);
1270 handle_motion(ci->home, (void*)ev);
1271 time_stop(TIME_KEY);
1275 time_start(TIME_WINDOW);
1276 handle_focus(ci->home, (void*)ev);
1277 time_stop(TIME_WINDOW);
1280 pane_damaged(ci->home, DAMAGED_POSTORDER);
1282 case XCB_CONFIGURE_NOTIFY:
1283 time_start(TIME_WINDOW);
1284 handle_configure(ci->home, (void*)ev);
1285 time_stop(TIME_WINDOW);
1287 case XCB_REPARENT_NOTIFY:
1288 /* Not interested */
1290 case XCB_MAP_NOTIFY:
1291 case XCB_UNMAP_NOTIFY:
1292 /* FIXME what to do?? */
1295 if ((ev->response_type & 0x7f) ==
1296 xd->first_xkb_event) {
1297 handle_xkb_event(ci->home, ev);
1300 LOG("ignored %x", ev->response_type);
1302 xcb_flush(xd->conn);
1307 static struct pane *xcb_display_init(const char *d safe, struct pane *focus safe)
1309 struct xcb_data *xd;
1311 xcb_connection_t *conn;
1312 xcb_intern_atom_cookie_t cookies[NR_ATOMS];
1313 xcb_screen_iterator_t iter;
1314 xcb_depth_iterator_t di;
1319 PangoLayout *layout;
1322 cairo_surface_t *surface;
1323 PangoFontDescription *fd;
1324 // FIXME SCALE from environ?? or pango_cairo_context_set_resolution dpi
1325 // 254 * width_in_pixels / width_in_millimeters / 10
1327 conn = xcb_connect(d, &screen);
1333 xd->motion_blocked = True;
1334 xd->in_focus = True;
1337 xd->display = strdup(d);
1338 xd->setup = safe_cast xcb_get_setup(conn);
1339 iter = xcb_setup_roots_iterator(xd->setup);
1340 for (i = 0; i < screen; i++)
1341 xcb_screen_next(&iter);
1342 xd->screen = safe_cast iter.data;
1344 di = xcb_screen_allowed_depths_iterator(xd->screen);
1345 while (di.data && di.data->depth < 24)
1346 xcb_depth_next(&di);
1347 //?? look for class = TrueColor??
1348 xd->visual = xcb_depth_visuals(di.data);
1350 for (i = 0; i < NR_ATOMS; i++) {
1351 char *n = atom_names[i];
1354 cookies[i] = xcb_intern_atom(conn, 0, strlen(n), n);
1357 xd->win = xcb_generate_id(conn);
1358 valwin[0] = xd->screen->white_pixel;
1359 valwin[1] = (XCB_EVENT_MASK_KEY_PRESS |
1360 XCB_EVENT_MASK_KEY_RELEASE |
1361 XCB_EVENT_MASK_BUTTON_PRESS |
1362 XCB_EVENT_MASK_BUTTON_RELEASE |
1363 // XCB_EVENT_MASK_ENTER_WINDOW |
1364 // XCB_EVENT_MASK_LEAVE_WINDOW |
1365 XCB_EVENT_MASK_FOCUS_CHANGE |
1366 XCB_EVENT_MASK_STRUCTURE_NOTIFY |
1367 XCB_EVENT_MASK_EXPOSURE |
1368 XCB_EVENT_MASK_POINTER_MOTION |
1369 // FIXME XCB_EVENT_MASK_POINTER_MOTION_HINT |
1372 xcb_create_window(conn, XCB_COPY_FROM_PARENT, xd->win,
1376 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
1377 xd->screen->root_visual,
1378 XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
1383 surface = cairo_xcb_surface_create(
1384 conn, xd->win, xd->visual, 100, 100);
1387 xd->surface = surface;
1388 cairo = cairo_create(xd->surface);
1392 fd = pango_font_description_new();
1396 pango_font_description_set_family(xd->fd, "mono");
1397 pango_font_description_set_size(xd->fd, 12 * PANGO_SCALE);
1399 layout = pango_cairo_create_layout(xd->cairo);
1400 pango_layout_set_font_description(layout, fd);
1401 pango_layout_set_text(layout, "M", 1);
1402 pango_layout_get_pixel_extents(layout, NULL, &log);
1403 g_object_unref(layout);
1404 xd->lineheight = log.height;
1405 xd->charwidth = log.width;
1407 valwin[0] = xd->charwidth * 80;
1408 valwin[1] = xd->lineheight * 26;
1409 xcb_configure_window(conn, xd->win,
1410 XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
1412 cairo_xcb_surface_set_size(xd->surface, valwin[0], valwin[1]);
1414 /* Now resolve all those cookies */
1415 for (i = 0; i < NR_ATOMS; i++) {
1416 xcb_intern_atom_reply_t *r;
1417 r = xcb_intern_atom_reply(conn, cookies[i], NULL);
1420 xd->atoms[i] = r->atom;
1426 * WM_PROTOCOLS WM_DELETE_WINDOW WM_TAKE_FOCUS _NET_WM_PING _NET_WM_SYN_REQUEST??
1427 * WM_NORMAL_HINTS WM_HINTS
1430 xcb_map_window(conn, xd->win);
1432 p = pane_register(pane_root(focus), 1, &xcb_handle.c, xd);
1435 pane_resize(p, 0, 0, xd->charwidth*80, xd->lineheight*26);
1436 call_comm("event:read", p, &xcb_input, xcb_get_file_descriptor(conn));
1437 attr_set_str(&p->attrs, "DISPLAY", d);
1438 snprintf(scale, sizeof(scale), "%dx%d", xd->charwidth, xd->lineheight);
1439 attr_set_str(&p->attrs, "scale:M", scale);
1440 call("editor:request:all-displays", p);
1444 cairo_destroy(xd->cairo);
1445 cairo_surface_destroy(xd->surface);
1446 xcb_disconnect(conn);
1447 // FIXME free stuff;
1452 DEF_CMD(display_xcb)
1455 const char *d = ci->str;
1459 p = xcb_display_init(d, ci->focus);
1461 return comm_call(ci->comm2, "cb", p);
1465 DEF_CMD(xcb_new_display)
1468 char *d = pane_attr_get(ci->focus, "DISPLAY");
1472 p = xcb_display_init(d, ci->focus);
1474 p = call_ret(pane, "editor:activate-display", p);
1476 home_call(ci->focus, "doc:attach-view", p, 1);
1480 void edlib_init(struct pane *ed safe)
1482 call_comm("global-set-command", ed, &display_xcb, 0, NULL,
1483 "attach-display-x11");
1484 call_comm("global-set-command", ed, &xcb_new_display, 0, NULL,
1485 "interactive-cmd-x11window");
1487 xcb_map = key_alloc();
1489 key_add(xcb_map, "Display:close", &xcb_close_display);
1490 key_add(xcb_map, "Display:set-noclose", &xcb_set_noclose);
1491 key_add(xcb_map, "Display:external-viewer", &xcb_external_viewer);
1492 key_add(xcb_map, "Display:fullscreen", &xcb_fullscreen);
1493 key_add(xcb_map, "Display:new", &xcb_new_display);
1495 key_add(xcb_map, "Close", &xcb_close);
1496 key_add(xcb_map, "Free", &edlib_do_free);
1497 key_add(xcb_map, "Draw:clear", &xcb_clear);
1498 key_add(xcb_map, "Draw:text-size", &xcb_text_size);
1499 key_add(xcb_map, "Draw:text", &xcb_draw_text);
1500 key_add(xcb_map, "Draw:image", &xcb_draw_image);
1501 key_add(xcb_map, "Refresh:postorder", &xcb_refresh_post);
1502 key_add(xcb_map, "all-displays", &xcb_notify_display);
1503 key_add(xcb_map, "Notify:Close", &xcb_pane_close);