/*
- * Copyright Neil Brown ©2021 <neil@brown.name>
+ * Copyright Neil Brown ©2021-2023 <neil@brown.name>
* May be distributed under terms of GPLv2 - see file:COPYING
*
* X11 display driver for edlib, using xcb, cairopango, libxkbcommon etc.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
+#include <stdint.h>
+#include <fcntl.h>
#include <string.h>
#include <xcb/xcb.h>
+#include <stdarg.h>
+#include <sys/wait.h>
#ifndef __CHECKER__
#include <xcb/xkb.h>
#else
#include <xkbcommon/xkbcommon-compose.h>
#include <xkbcommon/xkbcommon-x11.h>
+#include "xcb.h"
+
#undef True
#undef False
+
+#define PANE_DATA_TYPE struct xcb_data
#include "core.h"
enum my_atoms {
+ a_NONE = 0,
a_WM_STATE, a_STATE_FULLSCREEN,
+ a_WM_NAME, a_NET_WM_NAME,
+ a_WM_ICON_NAME, a_NET_WM_ICON_NAME,
+ a_WM_PROTOCOLS, a_WM_DELETE_WINDOW,
+ a_NET_WM_PING,
+ a_NET_WM_ICON,
+ a_WM_CLIENT_MACHINE,
+ a_UTF8_STRING,
NR_ATOMS
};
-static char *atom_names[NR_ATOMS] = {
+static const char *atom_names[NR_ATOMS] = {
+ [a_NONE] = "NONE",
[a_WM_STATE] = "_NET_WM_STATE",
[a_STATE_FULLSCREEN] = "_NET_WM_STATE_FULLSCREEN",
+ [a_WM_NAME] = "WM_NAME",
+ [a_NET_WM_NAME] = "_NET_WM_NAME",
+ [a_WM_ICON_NAME] = "WM_ICON_NAME",
+ [a_NET_WM_ICON_NAME] = "_NET_WM_ICON_NAME",
+ [a_WM_PROTOCOLS] = "WM_PROTOCOLS",
+ [a_WM_DELETE_WINDOW] = "WM_DELETE_WINDOW",
+ [a_NET_WM_PING] = "_NET_WM_PING",
+ [a_NET_WM_ICON] = "_NET_WM_ICON",
+ [a_WM_CLIENT_MACHINE] = "WM_CLIENT_MACHINE",
+ [a_UTF8_STRING] = "UTF8_STRING",
};
struct rgb {
struct xcb_data {
xcb_connection_t *conn safe;
char *display safe;
+ char *disp_auth;
const xcb_setup_t *setup safe;
const xcb_screen_t *screen safe;
cairo_t *cairo safe;
cairo_surface_t *surface safe;
PangoFontDescription *fd safe;
- char *noclose;
int charwidth, lineheight;
+ cairo_region_t *need_update;
bool motion_blocked;
bool in_focus;
struct xkb_compose_table *compose_table;
struct xkb_keymap *xkb_keymap;
+ struct pids {
+ pid_t pid;
+ struct pids *next;
+ } *pids;
+
/* FIXME use hash?? */
struct panes {
struct panes *next;
struct pane *p safe;
- int w,h;
- cairo_t *ctx safe;
+ cairo_rectangle_int_t r;
+ cairo_t *ctx;
struct rgb bg;
xcb_pixmap_t draw;
- cairo_surface_t *surface safe;
- } *panes;
+ cairo_surface_t *surface;
+ cairo_region_t *need_update;
+ } *panes;
};
+#include "core-pane.h"
+
+/* panes->r.x is NEVER_DRAWN if the pane has not been drawn */
+#define NEVER_DRAWN (-60000)
static struct map *xcb_map;
DEF_LOOKUP_CMD(xcb_handle, xcb_map);
{
struct xcb_data *xd = home->data;
struct panes **pp, *ps;
- cairo_surface_t *surface;
- cairo_t *ctx = NULL;
for (pp = &xd->panes; (ps = *pp) != NULL; pp = &(*pp)->next) {
if (ps->p != p)
continue;
- if (ps->w == p->w && ps->h == p->h)
+ if (ps->r.width == p->w && ps->r.height == p->h)
return ps;
*pp = ps->next;
- cairo_destroy(ps->ctx);
- cairo_surface_destroy(ps->surface);
- xcb_free_pixmap(xd->conn, ps->draw);
+ if (ps->r.x != NEVER_DRAWN) {
+ if (!xd->need_update)
+ xd->need_update = cairo_region_create();
+ cairo_region_union_rectangle(xd->need_update, &ps->r);
+ }
+ if (ps->ctx)
+ cairo_destroy(ps->ctx);
+ if (ps->surface)
+ cairo_surface_destroy(ps->surface);
+ if (ps->draw)
+ xcb_free_pixmap(xd->conn, ps->draw);
free(ps);
break;
}
alloc(ps, pane);
ps->p = p;
- ps->w = p->w;
- ps->h = p->h;
- ps->bg.g = -1;
- ps->draw = xcb_generate_id(xd->conn);
- xcb_create_pixmap(xd->conn, xd->screen->root_depth, ps->draw,
- xd->win, p->w, p->h);
- surface = cairo_xcb_surface_create(
- xd->conn, ps->draw, xd->visual, p->w, p->h);
- if (!surface)
- goto free_ps;
- ctx = cairo_create(surface);
- if (!ctx)
- goto free_surface;
- ps->ctx = ctx;
- ps->surface = surface;
+ ps->r.x = ps->r.y = NEVER_DRAWN;
+ ps->r.width = p->w;
+ ps->r.height = p->h;
+ ps->bg.r = ps->bg.g = ps->bg.b = 0;
pane_add_notify(home, p, "Notify:Close");
ps->next = *pp;
*pp = ps;
return ps;
+}
+
+static void instantiate_pixmap(struct xcb_data *xd safe,
+ struct panes *ps safe)
+{
+ ps->draw = xcb_generate_id(xd->conn);
+ xcb_create_pixmap(xd->conn, xd->screen->root_depth, ps->draw,
+ xd->win, ps->r.width, ps->r.height);
+ ps->surface = cairo_xcb_surface_create(
+ xd->conn, ps->draw, xd->visual, ps->r.width, ps->r.height);
+ if (!ps->surface)
+ goto free_ps;
+ ps->ctx = cairo_create(ps->surface);
+ if (!ps->ctx)
+ goto free_surface;
+ cairo_set_source_rgb(ps->ctx, ps->bg.r, ps->bg.g, ps->bg.b);
+ cairo_paint(ps->ctx);
+ return;
+
free_surface:
- cairo_surface_destroy(surface);
+ cairo_surface_destroy(ps->surface);
+ ps->surface = NULL;
free_ps:
xcb_free_pixmap(xd->conn, ps->draw);
- unalloc(ps, pane);
- return NULL;
+ ps->draw = 0;
}
static struct panes *find_pixmap(struct xcb_data *xd safe, struct pane *p safe,
char *fg = NULL, *bg = NULL;
bool ul = False;
bool inv = False;
- int size = 10*1000;
+ int size = 12*1000;
PangoFontDescription *fd = NULL;
PangoStyle style = PANGO_STYLE_NORMAL;
PangoVariant variant = PANGO_VARIANT_NORMAL;
if (fdp) {
fd = pango_font_description_new();
*fdp = fd;
- pango_font_description_set_family_static(fd, "mono");
+ pango_font_description_set_family_static(fd, "monospace");
}
while ((word = strsep(&ap, ",")) != NULL) {
- if (fd && strncmp(word, "family:", 7) == 0)
+ if (fd && strstarts(word, "family:"))
pango_font_description_set_family(fd, word+7);
if (strcmp(word, "large") == 0)
size = 14 * 1000;
if (strcmp(word, "nobold") == 0)
weight = PANGO_WEIGHT_NORMAL;
- if (strncmp(word, "fg:", 3) == 0)
+ if (strstarts(word, "fg:"))
fg = word + 3;
- if (strncmp(word, "bg:", 3) == 0)
+ if (strstarts(word, "bg:"))
bg = word + 3;
if (strcmp(word, "inverse") == 0)
inv = True;
+ if (strcmp(word, "noinverse") == 0)
+ inv = False;
if (strcmp(word, "underline") == 0)
ul = True;
+ if (strcmp(word, "nounderline") == 0)
+ ul = False;
}
if (inv) {
} else if (bgp)
bgp->g = -1;
if (fd) {
- pango_font_description_set_size(fd, size * scale / PANGO_SCALE);
+ pango_font_description_set_size(fd, PANGO_SCALE * size /1000 * scale / 1000);
if (style != PANGO_STYLE_NORMAL)
pango_font_description_set_style(fd, style);
if (variant != PANGO_VARIANT_NORMAL)
return 1;
}
-DEF_CMD(xcb_close_display)
+DEF_CMD_CLOSED(xcb_close_display)
{
/* If this is only display, then refuse to close this one */
struct call_return cr;
- struct xcb_data *xd = ci->home->data;
- if (xd->noclose) {
- call("Message", ci->focus, 0, NULL, xd->noclose);
+ char *nc = pane_attr_get(ci->home, "no-close");
+
+ if (nc) {
+ call("Message", ci->focus, 0, NULL, nc);
return 1;
}
cr.c = cnt_disp;
cr.i = 0;
call_comm("editor:notify:all-displays", ci->focus, &cr.c);
if (cr.i > 1)
- pane_close(ci->home);
+ return Efallthrough;
else
call("Message", ci->focus, 0, NULL,
"Cannot close only window.");
return 1;
}
-DEF_CMD(xcb_set_noclose)
+static void wait_for(struct xcb_data *xd safe)
{
- struct xcb_data *xd = ci->home->data;
-
- free(xd->noclose);
- xd->noclose = NULL;
- if (ci->str)
- xd->noclose = strdup(ci->str);
- return 1;
+ struct pids **pp = &xd->pids;
+
+ while (*pp) {
+ struct pids *p = *pp;
+ if (waitpid(p->pid, NULL, WNOHANG) > 0) {
+ *pp = p->next;
+ free(p);
+ } else
+ pp = &p->next;
+ }
}
DEF_CMD(xcb_external_viewer)
{
- //struct xcb_data *xd = ci->home->data;
- //FIXME
+ struct xcb_data *xd = ci->home->data;
+ const char *path = ci->str;
+ struct pids *p;
+ int pid;
+ int fd;
+
+ if (!path)
+ return Enoarg;
+ switch (pid = fork()) {
+ case -1:
+ return Efail;
+ case 0: /* Child */
+ setenv("DISPLAY", xd->display, 1);
+ if (xd->disp_auth)
+ setenv("XAUTHORITY", xd->disp_auth, 1);
+ fd = open("/dev/null", O_RDWR);
+ if (fd) {
+ dup2(fd, 0);
+ dup2(fd, 1);
+ dup2(fd, 2);
+ if (fd > 2)
+ close(fd);
+ }
+ execlp("xdg-open", "xdg-open", path, NULL);
+ exit(1);
+ default: /* parent */
+ p = malloc(sizeof(*p));
+ p->pid = pid;
+ p->next = xd->pids;
+ xd->pids = p;
+ break;
+ }
+ wait_for(xd);
return 1;
}
return 1;
}
-DEF_CMD(xcb_close)
+static void panes_free(struct xcb_data *xd safe)
+{
+ while (xd->panes) {
+ struct panes *ps = xd->panes;
+ xd->panes = ps->next;
+ if (ps->ctx)
+ cairo_destroy(ps->ctx);
+ if (ps->surface)
+ cairo_surface_destroy(ps->surface);
+ if (ps->draw)
+ xcb_free_pixmap(xd->conn, ps->draw);
+ free(ps);
+ }
+}
+
+static void kbd_free(struct xcb_data *xd safe);
+
+DEF_CMD_CLOSED(xcb_close)
{
struct xcb_data *xd = ci->home->data;
+
xcb_destroy_window(xd->conn, xd->win);
+ kbd_free(xd);
+ panes_free(xd);
+
+ pango_font_description_free(xd->fd);
+ cairo_destroy(xd->cairo);
+ cairo_device_finish(cairo_surface_get_device(xd->surface));
+ cairo_surface_destroy(xd->surface);
+ free(xd->display);
+ free(xd->disp_auth);
xcb_disconnect(xd->conn);
- /* free stuff */
+ if (xd->need_update)
+ cairo_region_destroy(xd->need_update);
return 1;
}
const char *attr = ci->str;
struct panes *src = NULL, *dest;
struct rgb bg;
- int x, y;
+ int x=0, y=0;
+ cairo_rectangle_int_t r;
if (attr) {
parse_attrs(ci->home, attr, PANGO_SCALE, NULL, &bg, NULL, NULL);
bg.r = bg.g = bg.b = 1.0;
else if (src->bg.g >= 0)
bg = src->bg;
+ else if (src->surface == NULL)
+ bg.r = bg.g = bg.b = 1.0;
else
bg.g = -1;
}
if (!dest)
return 1;
if (bg.g >= 0) {
- cairo_set_source_rgb(dest->ctx, bg.r, bg.g, bg.b);
- cairo_rectangle(dest->ctx, 0.0, 0.0,
- (double)ci->focus->w, (double)ci->focus->h);
- cairo_fill(dest->ctx);
+ if (dest->ctx) {
+ cairo_set_source_rgb(dest->ctx, bg.r, bg.g, bg.b);
+ cairo_paint(dest->ctx);
+ }
dest->bg = bg;
} else if (src) {
- cairo_set_source_surface(dest->ctx, src->surface, -x, -y);
- cairo_paint(dest->ctx);
- dest->bg.g = -1;
- } else
- LOG("ERROR neither src or bg");
+ if (!dest->ctx)
+ instantiate_pixmap(xd, dest);
+ if (dest->ctx) {
+ cairo_set_source_surface(dest->ctx, src->surface, -x, -y);
+ cairo_paint(dest->ctx);
+ dest->bg.g = -1;
+ }
+ }
pane_damaged(ci->home, DAMAGED_POSTORDER);
+
+ if (!dest->need_update)
+ dest->need_update = cairo_region_create();
+ r.x = 0;
+ r.y = 0;
+ r.width = ci->focus->w;
+ r.height = ci->focus->h;
+ cairo_region_union_rectangle(dest->need_update, &r);
return 1;
}
if (scale <= 0)
scale = 1000;
+ if (!utf8_valid(str))
+ str = "*INV*";
parse_attrs(ci->home, attr, scale, NULL, NULL, NULL, &fd);
- /* If we use an empty string, line-height it wrong */
+ /* If we use an empty string, line-height is wrong */
layout = pango_cairo_create_layout(xd->cairo);
pango_layout_set_text(layout, *str ? str : "M", -1);
pango_layout_set_font_description(layout, fd);
else if (log.width <= ci->num)
max_bytes = strlen(str);
else
- pango_layout_xy_to_index(layout, 1000*ci->num,
+ pango_layout_xy_to_index(layout, PANGO_SCALE*ci->num,
baseline, &max_bytes, NULL);
comm_call(ci->comm2, "cb", ci->focus, max_bytes, NULL, NULL,
ps = find_pixmap(xd, ci->focus, &xo, &yo);
if (!ps)
return Einval;
+ if (!ps->ctx)
+ instantiate_pixmap(xd, ps);
ps->bg.g = -1;
ctx = ps->ctx;
+ if (!ctx)
+ return Efail;
+
+ if (!utf8_valid(str))
+ str = "*INV*";
pane_damaged(ci->home, DAMAGED_POSTORDER);
pango_layout_set_font_description(layout, fd);
pango_layout_get_pixel_extents(layout, NULL, &log);
baseline = pango_layout_get_baseline(layout) / PANGO_SCALE;
+ cairo_save(ctx);
if (bg.g >= 0) {
cairo_set_source_rgb(ctx, bg.r, bg.g, bg.b);
cairo_rectangle(ctx, x+log.x, y - baseline + log.y,
PangoRectangle curs;
bool in_focus = xd->in_focus;
struct pane *f = ci->focus;
+ double cx, cy, cw, ch;
pango_layout_index_to_pos(layout, ci->num, &curs);
if (curs.width <= 0) {
pango_layout_get_extents(layout, NULL, &log);
curs.width = log.width;
}
- cairo_rectangle(ctx, x+curs.x/PANGO_SCALE, y-baseline+curs.y/PANGO_SCALE,
- (curs.width - PANGO_SCALE/2) / PANGO_SCALE,
- (curs.height - PANGO_SCALE/2) / PANGO_SCALE);
- cairo_set_line_width(ctx, 1.0);
- cairo_stroke(ctx);
while (in_focus && f->parent->parent != f &&
f->parent != ci->home) {
in_focus = False;
f = f->parent;
}
- if (in_focus) {
- if (fg.g >= 0)
- cairo_set_source_rgb(ctx, fg.r, fg.g, fg.b);
- cairo_rectangle(ctx, x+curs.x/PANGO_SCALE,
+ if (!in_focus) {
+ /* Just an fg:rectangle around the fg:text */
+ /* Add half to x,y as stroke is either side of the line */
+ cx = x * PANGO_SCALE + curs.x + PANGO_SCALE/2;
+ cy = (y - baseline) * PANGO_SCALE + curs.y + PANGO_SCALE/2;
+ ch = curs.height - PANGO_SCALE;
+ cw = curs.width - PANGO_SCALE;
+ cairo_rectangle(ctx, cx/PANGO_SCALE, cy/PANGO_SCALE,
+ cw/PANGO_SCALE, ch/PANGO_SCALE);
+ cairo_set_line_width(ctx, 1.0);
+ cairo_stroke(ctx);
+ } else {
+ /* solid fd:block with txt in bg color */
+ cairo_rectangle(ctx,
+ x+curs.x/PANGO_SCALE,
y-baseline+curs.y/PANGO_SCALE,
curs.width / PANGO_SCALE,
curs.height / PANGO_SCALE);
}
}
}
+ cairo_restore(ctx);
pango_font_description_free(fd);
g_object_unref(layout);
return 1;
}
+struct di_info {
+ struct command c;
+ MagickWand *wd safe;
+ int x,y,w,h;
+ int xo, yo;
+ struct panes *ps safe;
+};
+
+DEF_CB(xcb_draw_image_cb)
+{
+ struct di_info *dii = container_of(ci->comm, struct di_info, c);
+ int stride;
+ int fmt[2];
+ unsigned char *buf;
+ cairo_surface_t *surface;
+
+ switch (ci->key[0]) {
+ case 'w': /* width */
+ return MagickGetImageWidth(dii->wd);
+ case 'h': /* height */
+ return MagickGetImageHeight(dii->wd);
+ case 's': /* scale */
+ MagickResizeImage(dii->wd, ci->num, ci->num2, BoxFilter, 1.0);
+ return 1;
+ case 'c': /* crop or cursor */
+ if (ci->key[1] != 'u') {
+ /* crop */
+ dii->x = ci->x;
+ dii->y = ci->y;
+ dii->w = ci->num;
+ dii->h = ci->num2;
+ return 1;
+ } else {
+ /* cursor */
+ cairo_rectangle(dii->ps->ctx,
+ ci->x + dii->xo, ci->y + dii->yo,
+ ci->num, ci->num2);
+ cairo_set_line_width(dii->ps->ctx, 1.0);
+ cairo_set_source_rgb(dii->ps->ctx, 1.0, 0.0, 0.0);
+ cairo_stroke(dii->ps->ctx);
+ return 1;
+ }
+ case 'd': /* draw */
+ if (dii->w <= 0 || dii->h <= 0)
+ return Efail;
+ stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24,
+ dii->w);
+ buf = malloc(dii->h * stride);
+ // Cairo expects 32bit values with A in the high byte, then RGB.
+ // Magick provides 8bit values in the order requests.
+ // So depending on byte order, a different string is needed
+
+ fmt[0] = ('A'<<24) | ('R' << 16) | ('G' << 8) | ('B' << 0);
+ fmt[1] = 0;
+ MagickExportImagePixels(dii->wd, dii->x, dii->y,
+ dii->w, dii->h,
+ (char*)fmt, CharPixel, buf);
+ surface = cairo_image_surface_create_for_data(
+ buf, CAIRO_FORMAT_ARGB32, dii->w, dii->h, stride);
+ cairo_set_source_surface(dii->ps->ctx, surface,
+ ci->num + dii->xo, ci->num2 + dii->yo);
+ cairo_paint(dii->ps->ctx);
+ cairo_surface_destroy(surface);
+ free(buf);
+ return 1;
+ default:
+ return Efail;
+ }
+}
+
DEF_CMD(xcb_draw_image)
{
/* 'str' identifies the image. Options are:
* file:filename - load file from fs
* comm:command - run command collecting bytes
- * 'num' is '1' if image should be stretched to fill pane
- * if 'num is '0', then 'num2' is 'or' of
- * 0,1,2 for left/middle/right in x direction
- * 0,4,8 for top/middle/bottom in y direction
- * only one of these can be used as image will fill pane in other direction.
+ * 'str2' and numbers are handled by Draw:scale-image.
*/
struct xcb_data *xd = ci->home->data;
- bool stretch = ci->num == 1;
- int pos = ci->num2;
- int w = ci->focus->w, h = ci->focus->h;
- int x = 0, y = 0;
- int xo, yo;
- int stride;
+ struct di_info dii;
+ MagickWand *wd = NULL;
struct panes *ps;
- MagickBooleanType status;
- MagickWand *wd;
- int fmt[2];
- unsigned char *buf;
- cairo_surface_t *surface;
if (!ci->str)
return Enoarg;
- ps = find_pixmap(xd, ci->focus, &xo, &yo);
+ ps = find_pixmap(xd, ci->focus, &dii.xo, &dii.yo);
if (!ps)
return Einval;
- ps->bg.g = -1;
- if (strncmp(ci->str, "file:", 5) == 0) {
+ dii.ps = ps;
+ if (!dii.ps->ctx)
+ instantiate_pixmap(xd, dii.ps);
+ dii.ps->bg.g = -1;
+ if (!dii.ps->ctx)
+ return Efail;
+ if (strstarts(ci->str, "file:")) {
+ MagickBooleanType status;
+
wd = NewMagickWand();
status = MagickReadImage(wd, ci->str + 5);
if (status == MagickFalse) {
DestroyMagickWand(wd);
return Efail;
}
- } else if (strncmp(ci->str, "comm:", 5) == 0) {
+ } else if (strstarts(ci->str, "comm:")) {
+ MagickBooleanType status;
struct call_return cr;
+
wd = NewMagickWand();
- cr = call_ret(bytes, ci->str+5, ci->focus, 0, NULL, ci->str2);
+ cr = call_ret(bytes, ci->str+5, ci->focus);
if (!cr.s) {
DestroyMagickWand(wd);
return Efail;
DestroyMagickWand(wd);
return Efail;
}
- } else
+ }
+
+ if (!wd)
return Einval;
MagickAutoOrientImage(wd);
- if (!stretch) {
- int ih = MagickGetImageHeight(wd);
- int iw = MagickGetImageWidth(wd);
+ dii.c = xcb_draw_image_cb;
+ dii.wd = wd;
+ dii.w = dii.h = dii.x = dii.y = dii.xo = dii.yo = 0;
+ call("Draw:scale-image", ci->focus,
+ ci->num, NULL, NULL, ci->num2, NULL, ci->str2,
+ ci->x, ci->y, &dii.c);
+
+ DestroyMagickWand(wd);
+
+ pane_damaged(ci->home, DAMAGED_POSTORDER);
+
+ return 1;
+}
+
+DEF_CMD(xcb_image_size)
+{
+ MagickBooleanType status;
+ MagickWand *wd;
+ int ih, iw;
- if (iw <= 0 || iw <= 0) {
+ if (!ci->str)
+ return Enoarg;
+ if (strstarts(ci->str, "file:")) {
+ wd = NewMagickWand();
+ status = MagickReadImage(wd, ci->str + 5);
+ if (status == MagickFalse) {
DestroyMagickWand(wd);
return Efail;
}
- if (iw * h > ih * w) {
- /* Image is wider than space, use less height */
- ih = ih * w / iw;
- switch(pos & (8+4)) {
- case 4: /* center */
- y = (h - ih) / 2; break;
- case 8: /* bottom */
- y = h - ih; break;
- }
- h = ih;
- } else {
- /* image is too tall, use less width */
- iw = iw * h / ih;
- switch (pos & (1+2)) {
- case 1: /* center */
- x = (w - iw) / 2; break;
- case 2: /* right */
- x = w - iw ; break;
- }
- w = iw;
+ } else if (strstarts(ci->str, "comm:")) {
+ struct call_return cr;
+ wd = NewMagickWand();
+ cr = call_ret(bytes, ci->str+5, ci->focus);
+ if (!cr.s) {
+ DestroyMagickWand(wd);
+ return Efail;
}
- }
- MagickAdaptiveResizeImage(wd, w, h);
- stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w);
- buf = malloc(h * stride);
- // Cairo expects 32bit values with A in the high byte, then RGB.
- // Magick provides 8bit values in the order requests.
- // So depending on byte order, a different string is needed
-
- fmt[0] = ('A'<<24) | ('R' << 16) | ('G' << 8) | ('B' << 0);
- fmt[1] = 0;
- MagickExportImagePixels(wd, 0, 0, w, h, (char*)fmt, CharPixel, buf);
- surface = cairo_image_surface_create_for_data(buf, CAIRO_FORMAT_ARGB32,
- w, h, stride);
- cairo_set_source_surface(ps->ctx, surface, x + xo, y + yo);
- cairo_paint(ps->ctx);
- cairo_surface_destroy(surface);
- free(buf);
- DestroyMagickWand(wd);
+ status = MagickReadImageBlob(wd, cr.s, cr.i);
+ free(cr.s);
+ if (status == MagickFalse) {
+ DestroyMagickWand(wd);
+ return Efail;
+ }
+ } else
+ return Einval;
- pane_damaged(ci->home, DAMAGED_POSTORDER);
+ MagickAutoOrientImage(wd);
+ ih = MagickGetImageHeight(wd);
+ iw = MagickGetImageWidth(wd);
+ DestroyMagickWand(wd);
+ comm_call(ci->comm2, "callback:size", ci->focus,
+ 0, NULL, NULL, 0, NULL, NULL,
+ iw, ih);
return 1;
}
* 'end', and make 'end' point to &p->next.
*/
next = p->next;
- if (p->p->abs_z <= next->p->abs_z)
- continue;
- *end = next;
- end = &p->next;
+ if (p->p->abs_z < next->p->abs_z) {
+ *end = next;
+ end = &p->next;
+ }
}
*end = NULL;
return ret;
{
/* merge p1 and p2 and return result */
struct panes *ret, **end = &ret;
- int lastz = -100;
+ struct panes *prev = NULL;
while (p1 && p2) {
- /* if both arg large or smaller than lastz, choose
- * least, else choose largest
+ /* Make p1 the largest (or be added first.
+ * Then in prev is between them add p2, else p1
*/
- struct panes *lo, *hi, *choice;
- if (p1->p->abs_z <= p2->p->abs_z) {
- lo = p1; hi = p2;
- } else {
- lo = p2; hi = p1;
+ if (p1->p->abs_z < p2->p->abs_z) {
+ struct panes *t = p1;
+ p1 = p2;
+ p2 = t;
}
- if (lo->p->abs_z >= lastz || hi->p->abs_z <= lastz)
- choice = lo;
- else
- choice = hi;
- *end = choice;
- end = &choice->next;
- if (choice == p1)
- p1 = p1->next;
- else
+ if (prev &&
+ p1->p->abs_z > prev->p->abs_z &&
+ prev->p->abs_z >= p2->p->abs_z) {
+ /* p2 is the better choice */
+ prev = p2;
p2 = p2->next;
+ } else {
+ prev = p1;
+ p1 = p1->next;
+ }
+ *end = prev;
+ end = &prev->next;
}
if (p1)
*end = p1;
while ((ps = sort_split(xd->panes)) != NULL)
xd->panes = sort_merge(xd->panes, ps);
- /* Now copy all panes onto the window */
- for (ps = xd->panes; ps; ps = ps->next) {
- double lox, hix, loy, hiy;
+ /* Then merge all update rectanges, checking for movement */
+ if (!xd->need_update)
+ xd->need_update = cairo_region_create();
+ for (ps = xd->panes; ps ; ps = ps->next)
+ {
+ struct xy rel;
+
+ rel = pane_mapxy(ps->p, ci->home, 0, 0, False);
+ if (ps->r.x == NEVER_DRAWN) {
+ ps->r.x = rel.x;
+ ps->r.y = rel.y;
+ cairo_region_union_rectangle(xd->need_update, &ps->r);
+ } else if (rel.x != ps->r.x || rel.y != ps->r.y) {
+ /* Moved, so refresh all.
+ * This rectangle might be too big if it is clipped,
+ * but that doesn't really matter.
+ */
+ cairo_region_union_rectangle(xd->need_update, &ps->r);
+ ps->r.x = rel.x;
+ ps->r.y = rel.y;
+ cairo_region_union_rectangle(xd->need_update, &ps->r);
+ } else if (ps->need_update) {
+ cairo_region_translate(ps->need_update, rel.x, rel.y);
+ cairo_region_union(xd->need_update, ps->need_update);
+ }
+ if (ps->need_update)
+ cairo_region_destroy(ps->need_update);
+ ps->need_update = NULL;
+ }
+ /* Now copy all panes onto the window where an update is needed */
+ for (ps = xd->panes; ps ; ps = ps->next) {
struct xy rel, lo, hi;
+ cairo_region_t *cr;
+ cairo_rectangle_int_t r;
+ int nr, i;
+
+ cr = cairo_region_copy(xd->need_update);
rel = pane_mapxy(ps->p, ci->home, 0, 0, False);
- lo = pane_mapxy(ps->p, ci->home, 0, 0, True);
- hi = pane_mapxy(ps->p, ci->home, ps->p->w, ps->p->h, True);
- lox = lo.x; loy = lo.y;
- hix = hi.x; hiy = hi.y;
+
cairo_save(xd->cairo);
if (ps->bg.g >= 0)
cairo_set_source_rgb(xd->cairo,
else
cairo_set_source_surface(xd->cairo, ps->surface,
rel.x, rel.y);
- cairo_rectangle(xd->cairo, lox, loy, hix-lox, hiy-loy);
- cairo_fill(xd->cairo);
+
+ lo = pane_mapxy(ps->p, ci->home, 0, 0, True);
+ hi = pane_mapxy(ps->p, ci->home, ps->r.width, ps->r.height, True);
+ r.x = lo.x; r.y = lo.y;
+ r.width = hi.x - lo.x; r.height = hi.y - lo.y;
+ cairo_region_intersect_rectangle(cr, &r);
+ cairo_region_subtract_rectangle(xd->need_update, &r);
+ nr = cairo_region_num_rectangles(cr);
+ for (i = 0; i < nr; i++) {
+ cairo_region_get_rectangle(cr, i, &r);
+ cairo_rectangle(xd->cairo, r.x, r.y, r.width, r.height);
+ cairo_fill(xd->cairo);
+ }
cairo_restore(xd->cairo);
}
+
+ cairo_region_destroy(xd->need_update);
+ xd->need_update = NULL;
time_stop(TIME_WINDOW);
xcb_flush(xd->conn);
return 1;
}
+DEF_CMD(xcb_refresh_size)
+{
+ /* FIXME: should I consider resizing the window?
+ * For now, just ensure we redraw everything.
+ */
+ struct xcb_data *xd = ci->home->data;
+ cairo_rectangle_int_t r = {
+ .x = 0,
+ .y = 0,
+ .width = ci->home->w,
+ .height = ci->home->h,
+ };
+
+ if (!xd->need_update)
+ xd->need_update = cairo_region_create();
+ cairo_region_union_rectangle(xd->need_update, &r);
+ /* Ask common code to notify children */
+ return Efallthrough;
+}
+
DEF_CMD(xcb_pane_close)
{
struct xcb_data *xd = ci->home->data;
for (pp = &xd->panes; (ps = *pp) != NULL; pp = &(*pp)->next) {
if (ps->p != ci->focus)
continue;
+
+ if (!xd->need_update)
+ xd->need_update = cairo_region_create();
+ if (ps->r.x != NEVER_DRAWN)
+ cairo_region_union_rectangle(xd->need_update, &ps->r);
+
*pp = ps->next;
ps->next = NULL;
+ if (ps->need_update)
+ cairo_region_destroy(ps->need_update);
cairo_destroy(ps->ctx);
cairo_surface_destroy(ps->surface);
xcb_free_pixmap(xd->conn, ps->draw);
char mod[2+2+2+1];
char key[2+2+2+9+1+1];
+ xcb_set_input_focus(xd->conn, XCB_INPUT_FOCUS_POINTER_ROOT,
+ xd->win, XCB_CURRENT_TIME);
mod[0] = 0;
if (press) {
xd->motion_blocked = False;
3, NULL, NULL, x, y);
if (ret <= 0)
xd->motion_blocked = True;
+
+ /* This doesn't seem to be needed, but the spec says
+ * I should do this when using POINTER_MOTION_HINT
+ */
c = xcb_query_pointer(xd->conn, xd->win);
qpr = xcb_query_pointer_reply(xd->conn, c, NULL);
free(qpr);
struct mark *pt;
xd->in_focus = in;
- p = pane_leaf(home);
+ p = pane_focus(home);
pt = call_ret(mark, "doc:point", p);
if (pt)
call("view:changed", p, 0, pt);
char mods[32];
bool shift=False, ctrl=False, alt=False;
+ xd->last_event = time(NULL);
+
keysym = xkb_state_key_get_one_sym(xd->xkb_state,
keycode);
if (xd->compose_state)
s[1] += '@';
if (s[1] < 'A' || s[1] > 'Z')
shift = False;
- } else if (s[0] == '-' && !s[1]) {
+ } else if (s[0] == '-' && !s[1] && strcmp(key, "space") == 0) {
/* 'nul' becomes "C- " (ctrl-space) */
ctrl = True;
s[1] = ' ';
cairo_xcb_surface_set_size(xd->surface, cne->width, cne->height);
}
+static void handle_expose(struct pane *home safe,
+ xcb_expose_event_t *ee safe)
+{
+ struct xcb_data *xd = home->data;
+ cairo_rectangle_int_t r = {
+ .x = ee->x,
+ .y = ee->y,
+ .width = ee->width,
+ .height = ee->height,
+ };
+
+ if (!xd->need_update)
+ xd->need_update = cairo_region_create();
+ cairo_region_union_rectangle(xd->need_update, &r);
+ if (ee->count == 0)
+ pane_damaged(home, DAMAGED_POSTORDER);
+}
+
+static void handle_client_message(struct pane *home safe,
+ xcb_client_message_event_t *cme safe)
+{
+ struct xcb_data *xd = home->data;
+
+ if (cme->type == xd->atoms[a_WM_PROTOCOLS] &&
+ cme->format == 32 &&
+ cme->window == xd->win &&
+ cme->data.data32[0] == xd->atoms[a_WM_DELETE_WINDOW]) {
+ call("Window:close", pane_focus(home));
+ return;
+ }
+
+ if (cme->type == xd->atoms[a_WM_PROTOCOLS] &&
+ cme->format == 32 &&
+ cme->window == xd->win &&
+ cme->data.data32[0] == xd->atoms[a_NET_WM_PING]) {
+ cme->window = xd->screen->root;
+ xcb_send_event(xd->conn, 0, xd->screen->root,
+ XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
+ XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
+ (void*)cme);
+ return;
+ }
+ LOG("x11 %s got unexpected client message type=%d/%d win=%x data=%d",
+ xd->display,
+ cme->type, cme->format, cme->window, cme->data.data32[0]);
+
+}
+
DEF_CMD(xcb_input)
{
struct xcb_data *xd = ci->home->data;
xcb_generic_event_t *ev;
int ret = 1;
+ wait_for(xd);
if (ci->num < 0)
/* This is a poll - only return 1 on something happening */
ret = Efalse;
time_stop(TIME_WINDOW);
break;
case XCB_EXPOSE:
- pane_damaged(ci->home, DAMAGED_POSTORDER);
+ time_start(TIME_WINDOW);
+ handle_expose(ci->home, (void*)ev);
+ time_stop(TIME_WINDOW);
break;
case XCB_CONFIGURE_NOTIFY:
time_start(TIME_WINDOW);
handle_configure(ci->home, (void*)ev);
time_stop(TIME_WINDOW);
break;
+ case XCB_CLIENT_MESSAGE:
+ time_start(TIME_WINDOW);
+ handle_client_message(ci->home, (void*)ev);
+ time_stop(TIME_WINDOW);
+ break;
case XCB_REPARENT_NOTIFY:
/* Not interested */
break;
case XCB_MAP_NOTIFY:
case XCB_UNMAP_NOTIFY:
+ case XCB_MAPPING_NOTIFY:
/* FIXME what to do?? */
break;
+ case 0:
+ /* Don't know what this means, but I get a lot
+ * of them so I don't want to log that it was
+ * ignored.
+ */
+ break;
default:
if ((ev->response_type & 0x7f) ==
xd->first_xkb_event) {
handle_xkb_event(ci->home, ev);
break;
}
- LOG("ignored %x", ev->response_type);
+ LOG("Ignored X11 event %d", ev->response_type);
}
xcb_flush(xd->conn);
}
+ if (xcb_connection_has_error(xd->conn)) {
+ call("Window:close", ci->home->parent);
+ pane_close(ci->home);
+ }
return ret;
}
-static struct pane *xcb_display_init(const char *d safe, struct pane *focus safe)
+static void set_str_prop(struct xcb_data *xd safe,
+ enum my_atoms a, const char *str safe)
+{
+ xcb_change_property(xd->conn,
+ XCB_PROP_MODE_REPLACE,
+ xd->win, xd->atoms[a], XCB_ATOM_STRING,
+ 8, strlen(str), str);
+}
+
+static void set_utf8_prop(struct xcb_data *xd safe,
+ enum my_atoms a, const char *str safe)
+{
+ xcb_change_property(xd->conn,
+ XCB_PROP_MODE_REPLACE,
+ xd->win, xd->atoms[a],
+ xd->atoms[a_UTF8_STRING],
+ 8, strlen(str), str);
+}
+
+static void set_card32_property(struct xcb_data *xd safe,
+ enum my_atoms a,
+ const uint32_t *data, int cnt)
+{
+ xcb_change_property(xd->conn,
+ XCB_PROP_MODE_REPLACE,
+ xd->win, xd->atoms[a],
+ XCB_ATOM_CARDINAL, 32,
+ cnt, data);
+}
+
+static void set_atom_prop(struct xcb_data *xd safe,
+ enum my_atoms prop, enum my_atoms alist, ...)
+{
+ uint32_t atoms[16];
+ int anum = 0;
+ va_list ap;
+ enum my_atoms a;
+
+ atoms[anum++] = xd->atoms[alist];
+ va_start(ap, alist);
+ while ((a = va_arg(ap, enum my_atoms)) != a_NONE)
+ if (anum < 16)
+ atoms[anum++] = xd->atoms[a];
+ va_end(ap);
+ xcb_change_property(xd->conn,
+ XCB_PROP_MODE_REPLACE,
+ xd->win, xd->atoms[prop],
+ XCB_ATOM_ATOM,
+ 32, anum, atoms);
+}
+
+static void xcb_load_icon(struct pane *p safe,
+ struct xcb_data *xd safe,
+ char *file safe)
+{
+ char *path;
+ int h, w, n;
+ unsigned int *data;
+ MagickBooleanType status;
+ MagickWand *wd;
+ uint32_t fmt[2];
+
+ path = call_ret(str, "xdg-find-edlib-file", p, 0, NULL,
+ file, 0, NULL, "data");
+ if (!path)
+ return;
+
+ wd = NewMagickWand();
+ status = MagickReadImage(wd, path);
+ free(path);
+ if (status == MagickFalse)
+ goto done;
+
+ h = MagickGetImageHeight(wd);
+ w = MagickGetImageWidth(wd);
+ n = 2 + w*h;
+ data = malloc(sizeof(data[0]) * n);
+ if (!data)
+ goto done;
+ data[0] = w;
+ data[1] = h;
+ /* Need host-endian ARGB data */
+ fmt[0] = ('A'<<24) | ('R' << 16) | ('G' << 8) | ('B' << 0);
+ fmt[1] = 0;
+ MagickExportImagePixels(wd, 0, 0, w, h, (char*)fmt,
+ CharPixel, data+2);
+ set_card32_property(xd, a_NET_WM_ICON, data, n);
+ free(data);
+done:
+ DestroyMagickWand(wd);
+ return;
+}
+
+static struct pane *xcb_display_init(const char *d safe,
+ const char *disp_auth,
+ struct pane *focus safe)
{
struct xcb_data *xd;
struct pane *p;
xcb_screen_iterator_t iter;
xcb_depth_iterator_t di;
char scale[20];
+ char hostname[128];
uint32_t valwin[2];
int screen = 0;
int i;
// FIXME SCALE from environ?? or pango_cairo_context_set_resolution dpi
// 254 * width_in_pixels / width_in_millimeters / 10
- conn = xcb_connect(d, &screen);
- if (!conn)
+ conn = safe_cast xcb_connect_auth(d, disp_auth, &screen);
+ if (xcb_connection_has_error(conn))
return NULL;
- alloc(xd, pane);
+ p = call_ret(pane, "attach-window-core", focus);
+ if (!p)
+ return NULL;
+ p = pane_register(p, 1, &xcb_handle.c);
+ if (!p)
+ return NULL;
+ xd = p->data;
xd->motion_blocked = True;
xd->in_focus = True;
xd->conn = conn;
xd->display = strdup(d);
+ if (disp_auth)
+ xd->disp_auth = strdup(disp_auth);
xd->setup = safe_cast xcb_get_setup(conn);
iter = xcb_setup_roots_iterator(xd->setup);
for (i = 0; i < screen; i++)
xd->visual = xcb_depth_visuals(di.data);
for (i = 0; i < NR_ATOMS; i++) {
- char *n = atom_names[i];
+ const char *n = atom_names[i];
if (!n)
continue;
cookies[i] = xcb_intern_atom(conn, 0, strlen(n), n);
XCB_EVENT_MASK_FOCUS_CHANGE |
XCB_EVENT_MASK_STRUCTURE_NOTIFY |
XCB_EVENT_MASK_EXPOSURE |
- XCB_EVENT_MASK_POINTER_MOTION |
- // FIXME XCB_EVENT_MASK_POINTER_MOTION_HINT |
+ XCB_EVENT_MASK_BUTTON_MOTION |
+ XCB_EVENT_MASK_POINTER_MOTION_HINT |
0);
xcb_create_window(conn, XCB_COPY_FROM_PARENT, xd->win,
if (!surface)
goto abort;
xd->surface = surface;
- cairo = cairo_create(xd->surface);
- if (!cairo)
+ cairo = safe_cast cairo_create(xd->surface);
+ if (cairo_status(cairo) != CAIRO_STATUS_SUCCESS)
goto abort;
xd->cairo = cairo;
fd = pango_font_description_new();
if (!fd)
goto abort;
xd->fd = fd;
- pango_font_description_set_family(xd->fd, "mono");
+ pango_font_description_set_family(xd->fd, "monospace");
pango_font_description_set_size(xd->fd, 12 * PANGO_SCALE);
layout = pango_cairo_create_layout(xd->cairo);
}
/* FIXME set:
- * WM_NAME
- * WM_PROTOCOLS WM_DELETE_WINDOW WM_TAKE_FOCUS _NET_WM_PING _NET_WM_SYN_REQUEST??
+ *
+ * WM_PROTOCOLS _NET_WM_SYN_REQUEST??
* WM_NORMAL_HINTS WM_HINTS
* WM_CLIENT_MACHINE
*/
+ set_str_prop(xd, a_WM_NAME, "EdLib");
+ set_utf8_prop(xd, a_NET_WM_NAME, "EdLib");
+ set_str_prop(xd, a_WM_ICON_NAME, "EdLib");
+ set_utf8_prop(xd, a_NET_WM_ICON_NAME, "EdLib");
+ gethostname(hostname, sizeof(hostname));
+ set_str_prop(xd, a_WM_CLIENT_MACHINE, hostname);
+ set_atom_prop(xd, a_WM_PROTOCOLS, a_WM_DELETE_WINDOW, a_NET_WM_PING, 0);
+
+ /* Configure passive grabs - shift, lock, and control only */
+ xcb_grab_button(xd->conn, 0, xd->win,
+ XCB_EVENT_MASK_BUTTON_PRESS |
+ XCB_EVENT_MASK_BUTTON_RELEASE |
+ XCB_EVENT_MASK_BUTTON_MOTION,
+ XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
+ XCB_WINDOW_NONE, XCB_CURSOR_NONE,
+ XCB_BUTTON_INDEX_ANY,
+ XCB_MOD_MASK_SHIFT |
+ XCB_MOD_MASK_LOCK |
+ XCB_MOD_MASK_CONTROL);
+
+ xcb_load_icon(focus, xd, "{COMM}-icon.png");
xcb_map_window(conn, xd->win);
xcb_flush(conn);
- p = pane_register(pane_root(focus), 1, &xcb_handle.c, xd);
- if (!p)
- goto abort;
pane_resize(p, 0, 0, xd->charwidth*80, xd->lineheight*26);
call_comm("event:read", p, &xcb_input, xcb_get_file_descriptor(conn));
call_comm("event:poll", p, &xcb_input);
attr_set_str(&p->attrs, "DISPLAY", d);
+ attr_set_str(&p->attrs, "XAUTHORITY", disp_auth);
snprintf(scale, sizeof(scale), "%dx%d", xd->charwidth, xd->lineheight);
attr_set_str(&p->attrs, "scale:M", scale);
+ xd->last_event = time(NULL);
call("editor:request:all-displays", p);
return p;
abort:
cairo_destroy(xd->cairo);
cairo_surface_destroy(xd->surface);
xcb_disconnect(conn);
- // FIXME free stuff;
- unalloc(xd, pane);
+ free(xd->display);
+ free(xd->disp_auth);
return NULL;
}
-DEF_CMD(display_xcb)
+DEF_CMD(xcb_new_display)
{
struct pane *p;
const char *d = ci->str;
+ const char *disp_auth = ci->str2;
if (!d)
- return Enoarg;
- p = xcb_display_init(d, ci->focus);
- if (p)
- return comm_call(ci->comm2, "cb", p);
- return Efail;
-}
-
-DEF_CMD(xcb_new_display)
-{
- struct pane *p;
- char *d = pane_attr_get(ci->focus, "DISPLAY");
+ d = pane_attr_get(ci->focus, "DISPLAY");
+ if (!disp_auth)
+ disp_auth = pane_attr_get(ci->focus, "XAUTHORITY");
+ if (!disp_auth)
+ disp_auth = getenv("XAUTHORITY");
if (!d)
return Enoarg;
- p = xcb_display_init(d, ci->focus);
- if (p)
- p = call_ret(pane, "editor:activate-display", p);
+ p = xcb_display_init(d, disp_auth, ci->focus);
+ if (strcmp(ci->key, "interactive-cmd-x11window") == 0)
+ p = home_call_ret(pane, p,
+ "Window:activate-display", ci->focus);
if (p)
- home_call(ci->focus, "doc:attach-view", p, 1);
+ comm_call(ci->comm2, "cb", p);
return 1;
}
void edlib_init(struct pane *ed safe)
{
- call_comm("global-set-command", ed, &display_xcb, 0, NULL,
+ call_comm("global-set-command", ed, &xcb_new_display, 0, NULL,
"attach-display-x11");
call_comm("global-set-command", ed, &xcb_new_display, 0, NULL,
"interactive-cmd-x11window");
xcb_map = key_alloc();
- key_add(xcb_map, "Display:close", &xcb_close_display);
- key_add(xcb_map, "Display:set-noclose", &xcb_set_noclose);
- key_add(xcb_map, "Display:external-viewer", &xcb_external_viewer);
- key_add(xcb_map, "Display:fullscreen", &xcb_fullscreen);
- key_add(xcb_map, "Display:new", &xcb_new_display);
+ key_add(xcb_map, "Window:close", &xcb_close_display);
+ key_add(xcb_map, "Window:external-viewer", &xcb_external_viewer);
+ key_add(xcb_map, "Window:fullscreen", &xcb_fullscreen);
+ key_add(xcb_map, "Window:new", &xcb_new_display);
key_add(xcb_map, "Close", &xcb_close);
- key_add(xcb_map, "Free", &edlib_do_free);
key_add(xcb_map, "Draw:clear", &xcb_clear);
key_add(xcb_map, "Draw:text-size", &xcb_text_size);
key_add(xcb_map, "Draw:text", &xcb_draw_text);
key_add(xcb_map, "Draw:image", &xcb_draw_image);
+ key_add(xcb_map, "Draw:image-size", &xcb_image_size);
+ key_add(xcb_map, "Refresh:size", &xcb_refresh_size);
key_add(xcb_map, "Refresh:postorder", &xcb_refresh_post);
key_add(xcb_map, "all-displays", &xcb_notify_display);
key_add(xcb_map, "Notify:Close", &xcb_pane_close);