/*
 * X11 handling for QEmacs
 *
 * Copyright (c) 2000, 2001, 2002, 2003 Fabrice Bellard.
 * Copyright (c) 2002-2008 Charlie Gordon.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "qe.h"

#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/Xatom.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
#ifdef CONFIG_XFT
#include <X11/extensions/Xrender.h>
#include <X11/Xft/Xft.h>
#endif
#ifdef CONFIG_XV
#include <X11/extensions/Xv.h>
#include <X11/extensions/Xvlib.h>
#endif

//#define CONFIG_DOUBLE_BUFFER

/* NOTE: XFT code is currently broken */

static QEFont *term_open_font(QEditScreen *s, int style, int size);
static void term_close_font(QEditScreen *s, QEFont *font);
#ifdef CONFIG_XV
static void xv_init(QEditScreen *s);
#endif
static void x11_handle_event(void *opaque);

//static struct X11State {
static Display *display;
static int xscreen;
static Window window;
static GC gc, gc_pixmap;
static XWindowAttributes attr;
static int event_mask;
static int screen_width, screen_height;
static int last_window_width, last_window_height, last_window_x, last_window_y;
static XIM xim; /* X11 input method */
static XIC xic; /* X11 input context */
static Pixmap dbuffer;
static int shm_use;
#ifdef CONFIG_XFT
static XftDraw          *renderDraw;
#endif
#ifdef CONFIG_XV
static unsigned int xv_nb_adaptors;
static int xv_nb_formats, xv_port, xv_format, xv_open_count;
static XvAdaptorInfo *xv_ai;
static XvImageFormatValues *xv_fo;
#endif

#ifdef CONFIG_DOUBLE_BUFFER
/* update region to speed up double buffer flush */
#define UPDATE_MAX_REGIONS 3
#define UPDATE_DMIN 64
static int update_nb;
static CSSRect update_rects[UPDATE_MAX_REGIONS];
#endif

static int visual_depth;

static int force_tty;
static const char *display_str;
static const char *geometry_str;
static int font_ptsize;
//} x11_state;

static const char *default_x11_fonts[NB_FONT_FAMILIES] = {
#ifdef CONFIG_XFT
    "mono",
#else
    "fixed,unifont",
#endif
    "times,unifont",
    "helvetica,unifont",
};

#ifdef CONFIG_DOUBLE_BUFFER
static void update_reset(void)
{
    int i;
    for (i = 0; i < UPDATE_MAX_REGIONS; i++)
        css_set_rect(update_rects + i, 0, 0, 0, 0);
    update_nb = 0;
}

static inline int rect_dist(CSSRect *r1, CSSRect *r2)
{
    int dx, dy;

    if (r2->x2 < r1->x1)
        dx = r1->x1 - r2->x2;
    else if (r2->x1 > r1->x2)
        dx = r2->x1 - r1->x2;
    else
        dx = 0;

    if (r2->y2 < r1->y1)
        dy = r1->y1 - r2->y2;
    else if (r2->y1 > r1->y2)
        dy = r2->y1 - r1->y2;
    else
        dy = 0;
    if (dy > dx)
        dx = dy;
    return dx;
}

static void update_rect(int x1, int y1, int x2, int y2)
{
    CSSRect r, *r1, *r2;
    int i, d, dmin;

    css_set_rect(&r, x1, y1, x2, y2);
    if (css_is_null_rect(&r))
        return;

    /* find closest rectangle */
    dmin = INT_MAX;
    r2 = update_rects;
    r1 = NULL;
    for (i = 0; i < update_nb; i++) {
        d = rect_dist(r2, &r);
        if (d < dmin) {
            dmin = d;
            r1 = r2;
        }
        r2++;
    }
    if (dmin < UPDATE_DMIN || update_nb == UPDATE_MAX_REGIONS) {
        css_union_rect(r1, &r);
    } else {
        *r2 = r;
        update_nb++;
    }
}

#else
static inline void update_rect(__unused__ int x1, __unused__ int y1,
                               __unused__ int x2, __unused__ int y2)
{
}
#endif

static int term_probe(void)
{
    char *dpy;

    if (force_tty)
        return 0;

    /* if no env variable DISPLAY, we do not use x11 */
    dpy = getenv("DISPLAY");
    if (dpy == NULL ||
        dpy[0] == '\0')
        return 0;
    return 1;
}

static int term_init(QEditScreen *s, int w, int h)
{
    XSizeHints hint;
    int xsize, ysize;
    XSetWindowAttributes xwa;
    int font_xsize, font_ysize, fd;
    unsigned int fg, bg;
    const char *p;
    QEFont *font;
    QEStyleDef default_style;
    XGCValues gc_val;

    s->private = NULL;
    s->media = CSS_MEDIA_SCREEN;

    if (!display_str)
        display_str = "";

    display = XOpenDisplay(display_str);
    if (display == NULL) {
        fprintf(stderr, "Could not open X11 display - exiting.\n");
        return -1;
    }
    xscreen = DefaultScreen(display);

    bg = BlackPixel(display, xscreen);
    fg = WhitePixel(display, xscreen);
    screen_width = DisplayWidth(display, xscreen);
    screen_height = DisplayHeight(display, xscreen);

    /* At this point, we should be able to ask for metrics */
    if (font_ptsize)
        qe_styles[0].font_size = font_ptsize;
    get_style(NULL, &default_style, 0);
    font = term_open_font(s, default_style.font_style,
                            default_style.font_size);
    if (!font) {
        fprintf(stderr, "Could not open default font\n");
        exit(1);
    }
    font_ysize = font->ascent + font->descent;
    font_xsize = glyph_width(s, font, 'x');
    term_close_font(s, font);

    if (w > 0 && h > 0) {
        xsize = w;
        ysize = h;
    } else {
        xsize = 80;
        ysize = 50;

        if (geometry_str) {
            p = geometry_str;
            xsize = strtol(p, (char **)&p, 0);
            if (*p == 'x')
                p++;
            ysize = strtol(p, (char **)&p, 0);

            if (xsize <= 0 || ysize <=0) {
                fprintf(stderr, "Invalid geometry '%s'\n", geometry_str);
                exit(1);
            }
        }
    }

    xsize *= font_xsize;
    ysize *= font_ysize;
    s->width = xsize;
    s->height = ysize;
    s->charset = &charset_utf8;

    s->clip_x1 = 0;
    s->clip_y1 = 0;
    s->clip_x2 = s->width;
    s->clip_y2 = s->height;
    /* Fill in hint structure */

    hint.x = 0;
    hint.y = 0;
    hint.width = xsize;
    hint.height = ysize;
    hint.flags = PPosition | PSize;

    /* Make the window */
    window = XCreateSimpleWindow(display,
                                 DefaultRootWindow(display),
                                 hint.x, hint.y,
                                 hint.width, hint.height,
                                 4, fg, bg);
    /* Enable backing store */
    xwa.backing_store = Always;
    XChangeWindowAttributes(display, window, CWBackingStore, &xwa);

    XSelectInput(display, window, StructureNotifyMask);

    /* Tell other applications about this window */

    XSetStandardProperties(display, window,
                           "qemacs", "qemacs",
                           None, NULL, 0, &hint);

    /* Map window. */

    XMapWindow(display, window);

    /* Wait for map. */
    while (1) {
        XEvent xev;
        XNextEvent(display, &xev);
        if (xev.type == MapNotify && xev.xmap.event == window)
            break;
    }
    event_mask = KeyPressMask | ButtonPressMask | ButtonReleaseMask |
        ButtonMotionMask | ExposureMask | StructureNotifyMask;
    XSelectInput(display, window, event_mask);

    XGetWindowAttributes(display, window, &attr);

    /* see if we can bypass the X color functions */
    visual_depth = 0;
    if (attr.visual->class == TrueColor) {
        XVisualInfo *vinfo, templ;
        int n;
        templ.visualid = attr.visual->visualid;
        vinfo = XGetVisualInfo(display, VisualIDMask, &templ, &n);
        if (vinfo) {
            visual_depth = vinfo->depth;
        }
    }

    xim = XOpenIM(display, NULL, NULL, NULL);
    xic = XCreateIC(xim, XNInputStyle,  XIMPreeditNothing |
                    XIMStatusNothing,
                    XNClientWindow, window,
                    XNFocusWindow, window,
                    NULL);

    gc = XCreateGC(display, window, 0, NULL);
#ifdef CONFIG_XFT
    renderDraw = XftDrawCreate(display, window, attr.visual,
                               DefaultColormap(display, xscreen));
#endif
    /* double buffer handling */
    gc_pixmap = XCreateGC(display, window, 0, NULL);
    gc_val.graphics_exposures = 0;
    XChangeGC(display, gc_pixmap, GCGraphicsExposures, &gc_val);
    XSetForeground(display, gc, BlackPixel(display, xscreen));
#ifdef CONFIG_DOUBLE_BUFFER
    dbuffer = XCreatePixmap(display, window, xsize, ysize, attr.depth);
    /* erase pixmap */
    XFillRectangle(display, dbuffer, gc, 0, 0, xsize, ysize);
    update_reset();
#else
    dbuffer = window;
#endif

    /* shm extension usable ? */
    p = XDisplayName(display_str);
    strstart(p, "unix:", &p);
    strstart(p, "localhost:", &p);
    shm_use = 0;
    /* Check if display is local and XShm available */
    if ((*p == ':') && XShmQueryExtension(display))
        shm_use = 1;

    /* compute bitmap format */
    switch (visual_depth) {
    case 15:
        s->bitmap_format = QEBITMAP_FORMAT_RGB555;
        break;
    default:
    case 16:
        s->bitmap_format = QEBITMAP_FORMAT_RGB565;
        break;
    case 24:
        s->bitmap_format = QEBITMAP_FORMAT_RGB24;
        break;
    case 32:
        s->bitmap_format = QEBITMAP_FORMAT_RGBA32;
        break;
    }
    s->video_format = s->bitmap_format;

#ifdef CONFIG_XV
    xv_init(s);
#endif
    fd = ConnectionNumber(display);
    set_read_handler(fd, x11_handle_event, s);
    return 0;
}

#ifdef CONFIG_XV
static void xv_init(QEditScreen *s)
{
    int i;
    XvPortID xv_p;

    xv_port = 0; /* zero means no XV port found */
    if (XvQueryAdaptors(display,
                        DefaultRootWindow(display),
                        &xv_nb_adaptors, &xv_ai) != Success)
        return;

    for (i = 0; i < (int)xv_nb_adaptors; i++) {
        if ((xv_ai[i].type & XvInputMask) &&
            (xv_ai[i].type & XvImageMask)) {
            for (xv_p = xv_ai[i].base_id;
                 xv_p < (XvPortID)(xv_ai[i].base_id + xv_ai[i].num_ports);
                 xv_p++) {
                if (!XvGrabPort(display, xv_p, CurrentTime)) {
                    xv_port = xv_p;
                    goto found;
                }
            }
        }
    }
    return;
 found:

    xv_fo = XvListImageFormats(display, xv_port, &xv_nb_formats);
    for (i = 0; i < xv_nb_formats; i++) {
        XvImageFormatValues *fo = &xv_fo[i];
#if 0
        printf("Xvideo image format: 0x%x (%c%c%c%c) %s\n",
               fo->id,
               (fo->id) & 0xff,
               (fo->id >> 8) & 0xff,
               (fo->id >> 16) & 0xff,
               (fo->id >> 24) & 0xff,
               (fo->format == XvPacked) ? "packed" : "planar");
#endif
        /* search YUV420P format */
        if (fo->id == 0x32315659 && fo->format == XvPlanar) {
            xv_format = fo->id;
            break;
        }
    }
    /* if no format found, then release port */
    if (i == xv_nb_formats) {
        XvUngrabPort(display, xv_p, CurrentTime);
        xv_port = 0;
    } else {
        s->video_format = QEBITMAP_FORMAT_YUV420P;
    }
}
#endif

static void term_close(__unused__ QEditScreen *s)
{
#ifdef CONFIG_DOUBLE_BUFFER
    XFreePixmap(display, dbuffer);
#endif
    XCloseDisplay(display);
}

static int term_resize(QEditScreen *s, int w, int h)
{
    if (s->width == w && s->height == h)
        return 0;

    s->width = w;
    s->height = h;
#ifdef CONFIG_DOUBLE_BUFFER
    /* resize double buffer */
    XFreePixmap(display, dbuffer);
    dbuffer = XCreatePixmap(display, window, w, h, attr.depth);
#endif
    return 1;
}

static unsigned long get_x11_color(QEColor color)
{
    unsigned int r, g, b;
    XColor col;

    r = (color >> 16) & 0xff;
    g = (color >> 8) & 0xff;
    b = (color) & 0xff;
    switch (visual_depth) {
    case 15:
        return ((((r) >> 3) << 10) | (((g) >> 3) << 5) | ((b) >> 3));
    case 16:
        return ((((r) >> 3) << 11) | (((g) >> 2) << 5) | ((b) >> 3));
    case 24:
    case 32:
        return (r << 16) | (g << 8) | b;
    default:
        /* NOTE: the color is never freed */
        /* XXX: should do allocation ourself ? */
        col.red = r << 8;
        col.green = g << 8;
        col.blue = b << 8;
        XAllocColor(display, attr.colormap, &col);
        return col.pixel;
    }
}

static void xor_rectangle(__unused__ QEditScreen *s,
                          int x, int y, int w, int h)
{
    int fg;

    fg = WhitePixel(display, xscreen);
    XSetForeground(display, gc, fg);
    XSetFunction(display, gc, GXxor);

    XFillRectangle(display, dbuffer, gc, x, y, w, h);

    XSetFunction(display, gc, GXcopy);
}


/* Print the unicode string 'str' with baseline at position (x,y). The
   higher bits of each char may contain attributes. */
#ifdef CONFIG_XFT

static void term_fill_rectangle(QEditScreen *s,
                                int x1, int y1, int w, int h, QEColor color)
{
    XftColor col;
    int r, g, b, a;

    if (color == QECOLOR_XOR) {
        xor_rectangle(s, x1, y1, w, h);
        return;
    }

    a = (color >> 24) & 0xff;
    r = (color >> 16) & 0xff;
    g = (color >> 8) & 0xff;
    b = (color) & 0xff;
    /* not exact, but faster */
    col.color.red = r << 8;
    col.color.green = g << 8;
    col.color.blue = b << 8;
    col.color.alpha = a << 8;
    col.pixel = get_x11_color(color);
    XftDrawRect(renderDraw, &col, x1, y1, w, h);
}

static QEFont *term_open_font(QEditScreen *s, int style, int size)
{
    const char *family;
    int weight, slant;
    XftFont *renderFont;
    QEFont *font;

    font = qe_mallocz(QEFont);
    if (!font)
        return NULL;

    switch (style & QE_FAMILY_MASK) {
    default:
    case QE_FAMILY_FIXED:
        family = font_family_str;
        break;
    case QE_FAMILY_SANS:
        family = "sans";
        break;
    case QE_FAMILY_SERIF:
        family = "serif";
        break;
    }
    weight = XFT_WEIGHT_MEDIUM;
    if (style & QE_STYLE_BOLD)
        weight = XFT_WEIGHT_BOLD;
    slant = XFT_SLANT_ROMAN;
    if (style & QE_STYLE_ITALIC)
        slant = XFT_SLANT_ITALIC;
    renderFont = XftFontOpen(display, xscreen,
                             XFT_FAMILY, XftTypeString, family,
                             XFT_SIZE, XftTypeInteger, size,
                             XFT_WEIGHT, XftTypeInteger, weight,
                             XFT_SLANT, XftTypeInteger, slant,
                             0);
    if (!renderFont) {
        /* CG: don't know if this can happen, should try fallback? */
        qe_free(&font);
        return NULL;
    }
    font->ascent = renderFont->ascent;
    font->descent = renderFont->descent;
    font->private = renderFont;
    return font;
}

static void term_close_font(QEditScreen *s, QEFont *font)
{
    XftFont *renderFont = font->private;

    XftFontClose(display, renderFont);
    /* Clear structure to force crash if font is still used after
     * close_font.
     */
    memset(font, 0, sizeof(*font));
    qe_free(&font);
}

static int term_glyph_width(QEditScreen *s, QEFont *font, unsigned int cc)
{
    XftFont *renderFont = font->private;
    XGlyphInfo gi;

    XftTextExtents32(display, renderFont, &cc, 1, &gi);
    return gi.xOff;
}

static void term_draw_text(QEditScreen *s, QEFont *font,
                           int x, int y, const unsigned int *str, int len,
                           QEColor color)
{
    XftFont *renderFont = font->private;
    XftColor col;
    int r, g, b, a;

    a = (color >> 24) & 0xff;
    r = (color >> 16) & 0xff;
    g = (color >> 8) & 0xff;
    b = (color) & 0xff;
    /* not exact, but faster */
    col.color.red = r << 8;
    col.color.green = g << 8;
    col.color.blue = b << 8;
    col.color.alpha = a << 8;
    col.pixel = get_x11_color(color);

    XftDrawString32(renderDraw, &col, renderFont, x, y,
                    (XftChar32 *)str, len);
}

#else

static void term_fill_rectangle(QEditScreen *s,
                                int x1, int y1, int w, int h, QEColor color)
{
    unsigned long xcolor;

    update_rect(x1, y1, x1 + w, y1 + h);

    if (color == QECOLOR_XOR) {
        xor_rectangle(s, x1, y1, w, h);
        return;
    }

    xcolor = get_x11_color(color);
    XSetForeground(display, gc, xcolor);
    XFillRectangle(display, dbuffer, gc, x1, y1, w, h);
}

static void get_entry(char *buf, int buf_size, const char **pp)
{
    const char *p, *r;
    int len;

    r = *pp;
    p = strchr(r, '-');
    if (!p) {
        if (buf)
            buf[0] = '\0';
    } else {
        if (buf) {
            len = p - r;
            if (len >= buf_size)
                len = buf_size - 1;
            memcpy(buf, r, len);
            buf[len] = '\0';
        }
        *pp = p + 1;
    }
}


static QEFont *term_open_font(__unused__ QEditScreen *s, int style, int size)
{
    char family[128];
    const char *family_list, *p1;
    XFontStruct *xfont;
    QEFont *font;
    char buf[512];
    int count, found, dist, dist_min, i, size1, font_index, font_fallback;
    char **list;
    const char *p;

    font = qe_malloc(QEFont);
    if (!font)
        return NULL;

    /* get font name */
    font_index = ((style & QE_FAMILY_MASK) >> QE_FAMILY_SHIFT) - 1;
    if ((unsigned)font_index >= NB_FONT_FAMILIES)
        font_index = 0; /* fixed font is default */
    family_list = qe_state.system_fonts[font_index];
    if (family_list[0] == '\0')
        family_list = default_x11_fonts[font_index];

    /* take the nth font number in family list */
    font_fallback = (style & QE_FAMILY_FALLBACK_MASK) >> QE_FAMILY_FALLBACK_SHIFT;
    p = family_list;
    for (i = 0; i < font_fallback; i++) {
        p = strchr(p, ',');
        if (!p) {
            /* no font found */
            qe_free(&font);
            return NULL;
        }
        p++;
    }
    p1 = strchr(p, ',');
    if (!p1)
        pstrcpy(family, sizeof(family), p);
    else
        pstrncpy(family, sizeof(family), p, p1 - p);
#if 0
    for (i = 0; i < 2; i++) {
        char buf1[32];
        if (i == 0)
            snprintf(buf1, sizeof(buf1), "%d", size * 10);
        else
            pstrcpy(buf1, sizeof(buf1), "*");
        snprintf(buf, sizeof(buf),
                 "-*-%s-*-*-*-*-*-%s-*-*-*-*-*-*",
                 family, buf1);
        list = XListFonts(display, buf, 256, &count);
        if (count > 0)
            break;
    }
    if (i == 2)
        goto fail;
#else
    /* note: we do not want the X server to scale the font (usually
       ugly result), so we do not ask a given size */
    snprintf(buf, sizeof(buf),
             "-*-%s-*-*-*-*-*-%s-*-*-*-*-*-*",
             family, "*");
    list = XListFonts(display, buf, 256, &count);
    if (count == 0)
        goto fail;
#endif
    /* iterate thru each font and select closer one */
    found = 0;
    dist_min = INT_MAX;
    for (i = 0; i < count; i++) {
        dist = 0;
        p = list[i] + 1;
        get_entry(NULL, 0, &p);
        get_entry(NULL, 0, &p); /* family */
        get_entry(buf, sizeof(buf), &p); /* weight */
        if (!((strequal(buf, "bold") && (style & QE_STYLE_BOLD)) ||
              (strequal(buf, "medium") && !(style & QE_STYLE_BOLD))))
            dist += 3;
        get_entry(buf, sizeof(buf), &p); /* slant */
        if (!((strequal(buf, "o") && (style & QE_STYLE_ITALIC)) ||
              (strequal(buf, "i") && (style & QE_STYLE_ITALIC)) ||
              (strequal(buf, "r") && !(style & QE_STYLE_ITALIC))))
            dist += 3;
        get_entry(NULL, 0, &p); /* swidth */
        get_entry(NULL, 0, &p); /* adstyle */
        get_entry(NULL, 0, &p); /* pixsize */
        get_entry(buf, sizeof(buf), &p); /* size */
        size1 = atoi(buf) / 10;
        dist += abs(size - size1) * 3;
        get_entry(NULL, 0, &p); /* pixsize */
        get_entry(NULL, 0, &p); /* pixsize */
        get_entry(NULL, 0, &p); /* pixsize */
        get_entry(NULL, 0, &p); /* pixsize */
        /* always favor unicode encoding */
        if (!strequal(p, "iso10646-1"))
            dist += 3;

        if (dist < dist_min) {
            found = i;
            dist_min = dist;
        }
    }

    xfont = XLoadQueryFont(display, list[found]);
    if (!xfont)
        goto fail;
    XFreeFontNames(list);

    font->ascent = xfont->ascent;
    font->descent = xfont->descent;
    font->private = xfont;
    return font;
 fail:
    XFreeFontNames(list);
    qe_free(&font);
    return NULL;
}

static void term_close_font(__unused__ QEditScreen *s, QEFont *font)
{
    XFontStruct *xfont = font->private;

    XFreeFont(display, xfont);
    /* Clear structure to force crash if font is still used after
     * close_font.
     */
    memset(font, 0, sizeof(*font));
    qe_free(&font);
}

/* get a char struct associated to a char. Return NULL if no glyph
   associated. */
static XCharStruct *get_char_struct(QEFont *font, int cc)
{
    XFontStruct *xfont = font->private;
    int b1, b2;
    XCharStruct *cs;

    if (!xfont)
        return NULL;

    if (xfont->min_byte1 == 0 && xfont->max_byte1 == 0) {
        if (cc > (int)xfont->max_char_or_byte2)
            return NULL;
        cc -= xfont->min_char_or_byte2;
        if (cc < 0)
            return NULL;
    } else {
        b1 = (cc >> 8) & 0xff;
        b2 = cc & 0xff;
        if (b1 > (int)xfont->max_byte1)
            return NULL;
        b1 -= xfont->min_byte1;
        if (b1 < 0)
            return NULL;
        if (b2 > (int)xfont->max_char_or_byte2)
            return NULL;
        b2 -= xfont->min_char_or_byte2;
        if (b2 < 0)
            return NULL;
        cc = b1 * (xfont->max_char_or_byte2 -
                   xfont->min_char_or_byte2 + 1) + b2;
    }
    cs = xfont->per_char;
    if (!cs)
        return &xfont->min_bounds; /* all char have same metrics */
    cs += cc;
    /* fast test for non existant char */
    if (cs->width == 0 &&
        (cs->ascent | cs->descent | cs->rbearing | cs->lbearing) == 0) {
        return NULL;
    } else {
        return cs;
    }
}

static XCharStruct *handle_fallback(QEditScreen *s, QEFont **out_font,
                                    QEFont *font, unsigned int cc)
{
    XFontStruct *xfont;
    XCharStruct *cs;
    int fallback_count;
    QEFont *font1;


    /* fallback case */
    for (fallback_count = 1; fallback_count < 5; fallback_count++) {
        font1 = select_font(s, font->style |
                            (fallback_count << QE_FAMILY_FALLBACK_SHIFT),
                            font->size);
        if (!font1)
            break;
        cs = get_char_struct(font1, cc);
        if (cs) {
            *out_font = font1;
            return cs;
        }
        release_font(s, font1);
    }

    /* really no glyph : use default char in current font */
    /* Should have half-width and full-width default char patterns */
    xfont = font->private;
    cs = get_char_struct(font, xfont->default_char);
    *out_font = lock_font(s, font);
    return cs;
}

static void term_text_metrics(QEditScreen *s, QEFont *font,
                              QECharMetrics *metrics,
                              const unsigned int *str, int len)
{
    QEFont *font1;
    XCharStruct *cs;
    int i, x;
    unsigned int cc;

    metrics->font_ascent = font->ascent;
    metrics->font_descent = font->descent;
    x = 0;
    for (i = 0; i < len; i++) {
        cc = str[i];
        cs = get_char_struct(font, cc);
        if (cs) {
            /* most common case */
            x += cs->width;
        } else {
            /* no glyph: use a fallback font */
            cs = handle_fallback(s, &font1, font, cc);
            if (cs) {
                x += cs->width;
                metrics->font_ascent = max(metrics->font_ascent, font1->ascent);
                metrics->font_descent = max(metrics->font_descent, font1->descent);
            }
            release_font(s, font1);
        }
    }
    metrics->width = x;
}

static void term_draw_text(QEditScreen *s, QEFont *font,
                           int x1, int y, const unsigned int *str, int len,
                           QEColor color)
{
    XFontStruct *xfont;
    QEFont *font1, *last_font;
    XCharStruct *cs;
#ifdef __GNUC__
    XChar2b x11_str[len];
#else
    XChar2b x11_str[LINE_MAX_SIZE];
#endif
    XChar2b *q;
    int i, l, x, x_start;
    unsigned int cc;
    unsigned long xcolor;

    xcolor = get_x11_color(color);
    XSetForeground(display, gc, xcolor);
    q = x11_str;
    i = 0;
    x = x1;
    x_start = x;
    last_font = font;
    while (i < len) {
        cc = str[i++];
        cs = get_char_struct(font, cc);
        if (cs) {
            font1 = font; /* most common case */
        } else {
            /* complicated case: fallback */
            cs = handle_fallback(s, &font1, font, cc);
            if (!cs) {
                /* still no char: use default glyph */
                xfont = font->private;
                cc = xfont->default_char;
            }
        }
        /* flush previous chars if font change needed */
        if (font1 != last_font && q > x11_str) {
            xfont = last_font->private;
            l = q - x11_str;
            XSetFont(display, gc, xfont->fid);
            XDrawString16(display, dbuffer, gc, x_start, y, x11_str, l);
            update_rect(x_start, y - last_font->ascent, x, y + last_font->descent);
            x_start = x;
            q = x11_str;
        }
        last_font = font1;
        q->byte1 = (cc >> 8) & 0xff;
        q->byte2 = (cc) & 0xff;
        q++;
        x += cs->width;
    }
    if (q > x11_str) {
        /* flush remaining chars (more common case) */
        xfont = last_font->private;
        l = q - x11_str;
        XSetFont(display, gc, xfont->fid);
        XDrawString16(display, dbuffer, gc, x_start, y, x11_str, l);
        update_rect(x_start, y - last_font->ascent, x, y + last_font->descent);
    }
    /* underline synthesis */
    if (font->style & (QE_STYLE_UNDERLINE | QE_STYLE_LINE_THROUGH)) {
        int dy, h, w;
        h = (font->descent + 2) / 4;
        if (h < 1)
            h = 1;
        w = x - x1;
        if (font->style & QE_STYLE_UNDERLINE) {
            dy = (font->descent + 1) / 3;
            XFillRectangle(display, dbuffer, gc, x1, y + dy, w, h);
        }
        if (font->style & QE_STYLE_LINE_THROUGH) {
            dy = -(font->ascent / 2 - 1);
            XFillRectangle(display, dbuffer, gc, x1, y + dy, w, h);
        }
    }
}
#endif

static void term_set_clip(__unused__ QEditScreen *s,
                          int x, int y, int w, int h)
{
    XRectangle rect;

    rect.x = x;
    rect.y = y;
    rect.width = w;
    rect.height = h;
    XSetClipRectangles(display, gc, 0, 0, &rect, 1, YXSorted);
}

static void term_flush(__unused__ QEditScreen *s)
{
#ifdef CONFIG_DOUBLE_BUFFER
    CSSRect *r;
    int i, w, h;

    r = update_rects;
    for (i = 0; i < update_nb; i++) {
        if (r->x1 < 0)
            r->x1 = 0;
        if (r->x2 > s->width)
            r->x2 = s->width;
        if (r->y1 < 0)
            r->y1 = 0;
        if (r->y2 > s->height)
            r->y2 = s->height;
        w = r->x2 - r->x1;
        h = r->y2 - r->y1;
        if (w > 0 && h > 0) {
            XCopyArea(display, dbuffer, window, gc_pixmap,
                      r->x1, r->y1, w, h, r->x1, r->y1);
#if 0
            XSetForeground(display, gc_pixmap, 0xffff);
            XDrawRectangle(display, window, gc_pixmap, r->x1, r->y1,
                           w - 1, h - 1);
#endif
        }
        r++;
    }
#endif
    XFlush(display);
#ifdef CONFIG_DOUBLE_BUFFER
    update_reset();
#endif
}

static void x11_full_screen(__unused__ QEditScreen *s, int full_screen)
{
    XWindowAttributes attr1;
    Window win;

    XGetWindowAttributes(display, window, &attr1);
    if (full_screen) {
        if ((attr1.width != screen_width || attr1.height != screen_height)) {
            /* store current window position and size */
            XTranslateCoordinates(display, window, attr1.root, 0, 0,
                                  &last_window_x, &last_window_y, &win);
            last_window_width = attr1.width;
            last_window_height = attr1.height;
            XMoveResizeWindow(display, window,
                              0, 0, screen_width, screen_height);
        }
    } else if (!full_screen) {
        if (attr1.width == screen_width && attr1.height == screen_height) {
            XMoveResizeWindow(display, window,
                              last_window_x, last_window_y,
                              last_window_width, last_window_height);
        }
    }
}

static void term_selection_activate(__unused__ QEditScreen *s)
{
    /* own selection from now */
    XSetSelectionOwner(display, XA_PRIMARY, window, CurrentTime);
}

static Bool test_event(__unused__ Display *dpy, XEvent *ev,
                       __unused__ char *arg)
{
    return (ev->type == SelectionNotify);
}

/* request the selection from the GUI and put it in a new yank buffer
   if needed */
static void term_selection_request(__unused__ QEditScreen *s)
{
    QEmacsState *qs = &qe_state;
    Window w;
    Atom prop;
    long nread;
    unsigned char *data;
    Atom actual_type;
    int actual_fmt;
    unsigned long nitems, bytes_after;
    EditBuffer *b;
    XEvent xev;

    w = XGetSelectionOwner(display, XA_PRIMARY);
    if (w == None || w == window)
        return; /* qemacs can use its own selection */

    /* use X11 selection (Will be pasted when receiving
       SelectionNotify event) */
    prop = XInternAtom(display, "VT_SELECTION", False);
    XConvertSelection(display, XA_PRIMARY, XA_STRING,
                      prop, window, CurrentTime);

    /* XXX: add timeout too if the target application is not well
       educated */
    XIfEvent(display, &xev, test_event, NULL);

    w = xev.xselection.requestor;
    prop = xev.xselection.property;

    /* copy GUI selection a new yank buffer */
    b = new_yank_buffer(qs);

    nread = 0;
    for (;;) {
        if ((XGetWindowProperty(display, w, prop,
                                nread/4, 4096, True,
                                AnyPropertyType, &actual_type, &actual_fmt,
                                &nitems, &bytes_after,
                                &data) != Success) ||
            (actual_type != XA_STRING)) {
            XFree(data);
            break;
        }

       eb_write(b, nread, data, nitems);

       nread += nitems;
       XFree(data);

       if (bytes_after == 0)
           break;
   }
}

/* send qemacs selection to requestor */
static void selection_send(XSelectionRequestEvent *rq)
{
    static Atom xa_targets = None;
    QEmacsState *qs = &qe_state;
    unsigned char *buf;
    XEvent ev;
    EditBuffer *b;

    if (xa_targets == None)
        xa_targets = XInternAtom(display, "TARGETS", False);

    ev.xselection.type      = SelectionNotify;
    ev.xselection.property  = None;
    ev.xselection.display   = rq->display;
    ev.xselection.requestor = rq->requestor;
    ev.xselection.selection = rq->selection;
    ev.xselection.target           = rq->target;
    ev.xselection.time      = rq->time;

    if (rq->target == xa_targets) {
        unsigned int target_list[2];

        /* indicate which are supported types */
        target_list[0] = xa_targets;
        target_list[1] = XA_STRING;

        XChangeProperty(display, rq->requestor, rq->property,
                        xa_targets, 8*sizeof(target_list[0]), PropModeReplace,
                        (unsigned char *)target_list,
                        countof(target_list));
    } else if (rq->target == XA_STRING) {
        /* get qemacs yank buffer */

        b = qs->yank_buffers[qs->yank_current];
        if (!b)
            return;
        buf = qe_malloc_array(unsigned char, b->total_size);
        if (!buf)
            return;
        eb_read(b, 0, buf, b->total_size);

        XChangeProperty(display, rq->requestor, rq->property,
                        XA_STRING, 8, PropModeReplace,
                        buf, b->total_size);
        qe_free(&buf);
    }
    ev.xselection.property = rq->property;
    XSendEvent(display, rq->requestor, False, 0, &ev);
}

/* fast test to see if the user pressed a key or a mouse button */
static int x11_is_user_input_pending(__unused__ QEditScreen *s)
{
    XEvent xev;

    if (XCheckMaskEvent(display, KeyPressMask | ButtonPressMask, &xev)) {
        XPutBackEvent(display, &xev);
        return 1;
    } else {
        return 0;
    }
}

typedef struct QExposeRegion QExposeRegion;
struct QExposeRegion {
    int pending;
    /* simplistic single rectangle region */ 
    int x0, y0, x1, y1;
};

static void qe_expose_reset(QEditScreen *s, QExposeRegion *rgn)
{
    rgn->pending = 0;
    rgn->x0 = rgn->y0 = INT_MAX;
    rgn->x1 = rgn->y1 = INT_MIN;
}

static void qe_expose_set(QEditScreen *s, QExposeRegion *rgn,
                          int x, int y, int width, int height)
{
    rgn->pending = 1;
    rgn->x0 = x;
    rgn->y0 = y;
    rgn->x1 = x + width;
    rgn->y1 = y + height;
}

static void qe_expose_add(QEditScreen *s, QExposeRegion *rgn,
                          int x, int y, int width, int height)
{
    rgn->pending++;
    if (rgn->x0 > x)
        rgn->x0 = x;
    if (rgn->y0 > y)
        rgn->y0 = y;
    if (rgn->x1 < x + width)
        rgn->x1 = x + width;
    if (rgn->y1 > y + height)
        rgn->y1 = y + height;
}

static void qe_expose_flush(QEditScreen *s, QExposeRegion *rgn)
{
    if (rgn->pending) {
        QEEvent ev1, *ev = &ev1;

        /* Ignore expose region */
        ev->expose_event.type = QE_EXPOSE_EVENT;
        qe_handle_event(ev);
        qe_expose_reset(s, rgn);
    }
}

/* called when an X event happens. dispatch events to qe_handle_event() */
static void x11_handle_event(void *opaque)
{
    QEditScreen *s = opaque;
    char buf[16];
    XEvent xev;
    KeySym keysym;
    int shift, ctrl, meta, len, key;
    QEEvent ev1, *ev = &ev1;
    QExposeRegion rgn1, *rgn = &rgn1;

    qe_expose_reset(s, rgn);

    while (XPending(display)) {
        XNextEvent(display, &xev);
        switch (xev.type) {
        case ConfigureNotify:
            if (term_resize(s, xev.xconfigure.width, xev.xconfigure.height)) {
                qe_expose_set(s, rgn, 0, 0, s->width, s->height);
            }
            break;

        case Expose:
            {
                XExposeEvent *xe = &xev.xexpose;

                qe_expose_add(s, rgn, xe->x, xe->y, xe->width, xe->height);
            }
            break;

        case ButtonPress:
        case ButtonRelease:
            {
                XButtonEvent *xe = &xev.xbutton;

                if (xev.type == ButtonPress)
                    ev->button_event.type = QE_BUTTON_PRESS_EVENT;
                else
                    ev->button_event.type = QE_BUTTON_RELEASE_EVENT;

                ev->button_event.x = xe->x;
                ev->button_event.y = xe->y;
                switch (xe->button) {
                case Button1:
                    ev->button_event.button = QE_BUTTON_LEFT;
                    break;
                case Button2:
                    ev->button_event.button = QE_BUTTON_MIDDLE;
                    break;
                case Button3:
                    ev->button_event.button = QE_BUTTON_RIGHT;
                    break;
                case Button4:
                    ev->button_event.button = QE_WHEEL_UP;
                    break;
                case Button5:
                    ev->button_event.button = QE_WHEEL_DOWN;
                    break;
                default:
                    continue;
                }
                qe_expose_flush(s, rgn);
                qe_handle_event(ev);
            }
            break;
        case MotionNotify:
            {
                XMotionEvent *xe = &xev.xmotion;
                ev->button_event.type = QE_MOTION_EVENT;
                ev->button_event.x = xe->x;
                ev->button_event.y = xe->y;
                qe_expose_flush(s, rgn);
                qe_handle_event(ev);
            }
            break;
            /* selection handling */
        case SelectionClear:
            {
                /* ask qemacs to stop visual notification of selection */
                ev->type = QE_SELECTION_CLEAR_EVENT;
                qe_expose_flush(s, rgn);
                qe_handle_event(ev);
            }
            break;
        case SelectionRequest:
            qe_expose_flush(s, rgn);
            selection_send(&xev.xselectionrequest);
            break;
        case KeyPress:
#ifdef X_HAVE_UTF8_STRING
            /* only present since XFree 4.0.2 */
            {
                Status status;
                len = Xutf8LookupString(xic, &xev.xkey, buf, sizeof(buf),
                                        &keysym, &status);
            }
#else
            {
                static XComposeStatus status;
                len = XLookupString(&xev.xkey, buf, sizeof(buf),
                                    &keysym, &status);
            }
#endif
            shift = (xev.xkey.state & ShiftMask);
            ctrl = (xev.xkey.state & ControlMask);
            meta = (xev.xkey.state & Mod1Mask);
#if 0
            put_error(NULL, "keysym=%d %s%s%s len=%d buf[0]=%d",
                      (int)keysym,
                      shift ? "shft " : "",
                      ctrl ? "ctrl " : "",
                      meta ? "meta " : "",
                      len, buf[0]);
#endif
            if (meta) {
                switch (keysym) {
                case XK_BackSpace:
                    key = KEY_META(KEY_DEL);
                    goto got_key;
                default:
                    if (len > 0) {
                        key = KEY_META(buf[0] & 0xff);
                        goto got_key;
                    }
                    if (keysym >= ' ' && keysym <= '~') {
                        key = KEY_META(' ') + keysym - ' ';
                        goto got_key;
                    }
                    break;
                }
            } else
            if (ctrl) {
                switch (keysym) {
                case XK_Right:
                    key = KEY_CTRL_RIGHT;
                    goto got_key;
                case XK_Left:
                    key = KEY_CTRL_LEFT;
                    goto got_key;
                case XK_Home:
                    key = KEY_CTRL_HOME;
                    goto got_key;
                case XK_End:
                    key = KEY_CTRL_END;
                    goto got_key;
                default:
                    if (len > 0) {
                        key = buf[0] & 0xff;
                        goto got_key;
                    }
                    break;
                }
            } else {
                switch (keysym) {
                case XK_F1:     key = KEY_F1;     goto got_key;
                case XK_F2:     key = KEY_F2;     goto got_key;
                case XK_F3:     key = KEY_F3;     goto got_key;
                case XK_F4:     key = KEY_F4;     goto got_key;
                case XK_F5:     key = KEY_F5;     goto got_key;
                case XK_F6:     key = KEY_F6;     goto got_key;
                case XK_F7:     key = KEY_F7;     goto got_key;
                case XK_F8:     key = KEY_F8;     goto got_key;
                case XK_F9:     key = KEY_F9;     goto got_key;
                case XK_F10:    key = KEY_F10;    goto got_key;
                case XK_F11:    key = KEY_F11;    goto got_key;
                case XK_F13:    key = KEY_F13;    goto got_key;
                case XK_F14:    key = KEY_F14;    goto got_key;
                case XK_F15:    key = KEY_F15;    goto got_key;
                case XK_F16:    key = KEY_F16;    goto got_key;
                case XK_F17:    key = KEY_F17;    goto got_key;
                case XK_F18:    key = KEY_F18;    goto got_key;
                case XK_F19:    key = KEY_F19;    goto got_key;
                case XK_F20:    key = KEY_F20;    goto got_key;
                case XK_Up:     key = KEY_UP;     goto got_key;
                case XK_Down:   key = KEY_DOWN;   goto got_key;
                case XK_Right:  key = KEY_RIGHT;  goto got_key;
                case XK_Left:   key = KEY_LEFT;   goto got_key;
                case XK_BackSpace: key = KEY_DEL; goto got_key;
                case XK_Insert: key = KEY_INSERT; goto got_key;
                case XK_Delete: key = KEY_DELETE; goto got_key;
                case XK_Home:   key = KEY_HOME;   goto got_key;
                case XK_End:    key = KEY_END;    goto got_key;
                case XK_Prior:  key = KEY_PAGEUP; goto got_key;
                case XK_Next:   key = KEY_PAGEDOWN; goto got_key;
                case XK_ISO_Left_Tab: key = KEY_SHIFT_TAB; goto got_key;
                default:
                    if (len > 0) {
#ifdef X_HAVE_UTF8_STRING
                        {
                            const char *p = buf;
                            buf[len] = '\0';
                            key = utf8_decode(&p);
                        }
#else
                        key = buf[0] & 0xff;
#endif
                    got_key:
                        ev->key_event.type = QE_KEY_EVENT;
                        ev->key_event.key = key;
                        qe_expose_flush(s, rgn);
                        qe_handle_event(ev);
                    }
                    break;
                }
            }
        }
    }
    qe_expose_flush(s, rgn);
}

/* bitmap handling */

enum X11BitmapType {
    BMP_PIXMAP,
    BMP_XIMAGE,
    BMP_XSHMIMAGE,
#ifdef CONFIG_XV
    BMP_XVIMAGE,
    BMP_XVSHMIMAGE,
#endif
};

typedef struct X11Bitmap {
    enum X11BitmapType type;
    union {
        Pixmap pixmap;
        XImage *ximage;
#ifdef CONFIG_XV
        XvImage *xvimage;
#endif
    } u;
    XShmSegmentInfo *shm_info;
    int x_lock, y_lock; /* destination for locking */
    XImage *ximage_lock;
} X11Bitmap;

static int x11_bmp_alloc(QEditScreen *s, QEBitmap *b)
{
    X11Bitmap *xb;

    xb = qe_malloc(X11Bitmap);
    if (!xb)
        return -1;
    b->priv_data = xb;
    /* choose bitmap type to optimize communication with x server and
       performances */
    if (b->flags & QEBITMAP_FLAG_VIDEO) {
#if defined(CONFIG_XV)
        if (xv_port != 0 && xv_open_count == 0) {
            if (shm_use)
                xb->type = BMP_XVSHMIMAGE;
            else
                xb->type = BMP_XVIMAGE;
            b->format = s->video_format;
            xv_open_count++;
        } else
#endif
        {
            if (shm_use)
                xb->type = BMP_XSHMIMAGE;
            else
                xb->type = BMP_XIMAGE;
            b->format = s->bitmap_format;
        }
    } else {
        xb->type = BMP_PIXMAP;
        b->format = s->bitmap_format;
    }

    switch (xb->type) {
    default:
    case BMP_PIXMAP:
        xb->u.pixmap = XCreatePixmap(display, window,
                                     b->width, b->height, attr.depth);
        if (!xb->u.pixmap)
            goto fail;
        break;
    case BMP_XIMAGE:
        {
            XImage *ximage;
            ximage = XCreateImage(display, None, attr.depth, ZPixmap, 0,
                                  NULL, b->width, b->height, 8, 0);
            ximage->data = qe_malloc_array(char, b->height * ximage->bytes_per_line);
            xb->u.ximage = ximage;
        }
        break;
    case BMP_XSHMIMAGE:
        {
            XImage *ximage;
            XShmSegmentInfo *shm_info;

            /* XXX: error testing */
            shm_info = qe_malloc(XShmSegmentInfo);
            ximage = XShmCreateImage(display, None, attr.depth, ZPixmap, NULL,
                                     shm_info, b->width, b->height);
            shm_info->shmid = shmget(IPC_PRIVATE,
                                     b->height * ximage->bytes_per_line,
                                     IPC_CREAT | 0777);
            ximage->data = shmat(shm_info->shmid, 0, 0);
            shm_info->shmaddr = ximage->data;
            shm_info->readOnly = False;

            XShmAttach(display, shm_info);
            XSync(display, False);

            /* the shared memory will be automatically deleted */
            shmctl(shm_info->shmid, IPC_RMID, 0);
            xb->shm_info = shm_info;
            xb->u.ximage = ximage;
        }
        break;
#ifdef CONFIG_XV
    case BMP_XVIMAGE:
        {
            XvImage *xvimage;
            xvimage = XvCreateImage(display, xv_port, xv_format, 0,
                                    b->width, b->height);
            xvimage->data = qe_malloc_array(char, xvimage->data_size);
            xb->u.xvimage = xvimage;
        }
        break;
    case BMP_XVSHMIMAGE:
        {
            XvImage *xvimage;
            XShmSegmentInfo *shm_info;

            shm_info = qe_malloc(XShmSegmentInfo);
            xvimage = XvShmCreateImage(display, xv_port, xv_format, 0,
                                       b->width, b->height, shm_info);
            shm_info->shmid = shmget(IPC_PRIVATE,
                                     xvimage->data_size,
                                     IPC_CREAT | 0777);
            xvimage->data = shmat(shm_info->shmid, 0, 0);
            shm_info->shmaddr = xvimage->data;
            shm_info->readOnly = False;

            XShmAttach(display, shm_info);
            XSync(display, False);

            /* the shared memory will be automatically deleted */
            shmctl(shm_info->shmid, IPC_RMID, 0);
            xb->shm_info = shm_info;
            xb->u.xvimage = xvimage;
        }
        break;
#endif
    }
    return 0;
 fail:
    qe_free(&xb);
    return -1;
}

static void x11_bmp_free(__unused__ QEditScreen *s, QEBitmap *b)
{
    X11Bitmap *xb = b->priv_data;

    switch (xb->type) {
    case BMP_PIXMAP:
        XFreePixmap(display, xb->u.pixmap);
        break;
    case BMP_XIMAGE:
        /* NOTE: also frees the ximage data */
        XDestroyImage(xb->u.ximage);
        break;
    case BMP_XSHMIMAGE:
        XShmDetach(display, xb->shm_info);
        XDestroyImage(xb->u.ximage);
        shmdt(xb->shm_info->shmaddr);
        qe_free(&xb->shm_info);
        break;
#ifdef CONFIG_XV
    case BMP_XVIMAGE:
        qe_free(&xb->u.xvimage->data);
        XFree(xb->u.xvimage);
        xv_open_count--;
        break;
    case BMP_XVSHMIMAGE:
        XShmDetach(display, xb->shm_info);
        XFree(xb->u.xvimage);
        shmdt(xb->shm_info->shmaddr);
        qe_free(&xb->shm_info);
        xv_open_count--;
        break;
#endif
    }
    qe_free(&b->priv_data);
}

static void x11_bmp_draw(__unused__ QEditScreen *s, QEBitmap *b,
                         int dst_x, int dst_y, int dst_w, int dst_h,
                         __unused__ int offset_x, __unused__ int offset_y,
                         __unused__ int flags)
{
    X11Bitmap *xb = b->priv_data;

    /* XXX: handle clipping ? */
    update_rect(dst_x, dst_y, dst_x + dst_w, dst_y + dst_h);

    switch (xb->type) {
    case BMP_PIXMAP:
        XCopyArea(display, xb->u.pixmap, dbuffer, gc,
                  0, 0, b->width, b->height, dst_x, dst_y);
        break;
    case BMP_XIMAGE:
        XPutImage(display, dbuffer, gc,
                  xb->u.ximage, 0, 0, dst_x, dst_y,
                  b->width, b->height);
        break;

    case BMP_XSHMIMAGE:
        XShmPutImage(display, dbuffer, gc,
                     xb->u.ximage, 0, 0, dst_x, dst_y,
                     b->width, b->height, False);
        break;
#ifdef CONFIG_XV
    case BMP_XVIMAGE:
        XvPutImage(display, xv_port, window, gc, xb->u.xvimage,
                   0, 0, b->width, b->height,
                   dst_x, dst_y, dst_w, dst_h);
        break;
    case BMP_XVSHMIMAGE:
        XvShmPutImage(display, xv_port, window, gc, xb->u.xvimage,
                      0, 0, b->width, b->height,
                      dst_x, dst_y, dst_w, dst_h, False);
        break;
#endif
    }
}

static void x11_bmp_lock(__unused__ QEditScreen *s, QEBitmap *b, QEPicture *pict,
                         int x1, int y1, int w1, int h1)
{
    X11Bitmap *xb = b->priv_data;
    int bpp;

    pict->width = w1;
    pict->height = h1;
    pict->format = b->format;
    switch (xb->type) {
    case BMP_PIXMAP:
        {
            XImage *ximage;
            ximage = XCreateImage(display, None, attr.depth, ZPixmap, 0,
                                  NULL, w1, h1, 8, 0);
            ximage->data = qe_malloc_array(char, h1 * ximage->bytes_per_line);
            pict->data[0] = (unsigned char *)ximage->data;
            pict->linesize[0] = ximage->bytes_per_line;
            xb->ximage_lock = ximage;
            xb->x_lock = x1;
            xb->y_lock = y1;
        }
        break;
    case BMP_XIMAGE:
    case BMP_XSHMIMAGE:
        bpp = (xb->u.ximage->bits_per_pixel + 7) >> 3;
        pict->data[0] = (unsigned char *)xb->u.ximage->data +
                        y1 * xb->u.ximage->bytes_per_line + x1 * bpp;
        pict->linesize[0] = xb->u.ximage->bytes_per_line;
        break;
#ifdef CONFIG_XV
    case BMP_XVIMAGE:
    case BMP_XVSHMIMAGE:
        /* XXX: only YUV420P is handled yet */
        {
            XvImage *xvimage = xb->u.xvimage;
            int i, xx, yy, j;
            for (i = 0; i < 3; i++) {
                xx = x1;
                yy = y1;
                j = i;
                if (i >= 1) {
                    xx >>= 1;
                    yy >>= 1;
                    j = 3 - i; /* don't know why they inverted Cb and Cr! */
                }
                pict->data[j] = (unsigned char *)xvimage->data +
                                xvimage->offsets[i] +
                                yy * xvimage->pitches[i] + xx;
                pict->linesize[j] = xvimage->pitches[i];
            }
        }
        break;
#endif
    }
}

static void x11_bmp_unlock(__unused__ QEditScreen *s, QEBitmap *b)
{
    X11Bitmap *xb = b->priv_data;
    int ret;
    switch (xb->type) {
    case BMP_PIXMAP:
        ret = XPutImage(display, xb->u.pixmap, gc_pixmap, xb->ximage_lock,
                  0, 0, xb->x_lock, xb->y_lock,
                  xb->ximage_lock->width, xb->ximage_lock->height);
        /* NOTE: also frees the ximage data */
        XDestroyImage(xb->ximage_lock);
        xb->ximage_lock = NULL;
        break;
    default:
        break;
    }
}

static QEDisplay x11_dpy = {
    "x11",
    term_probe,
    term_init,
    term_close,
    term_flush,
    x11_is_user_input_pending,
    term_fill_rectangle,
    term_open_font,
    term_close_font,
    term_text_metrics,
    term_draw_text,
    term_set_clip,
    term_selection_activate,
    term_selection_request,
    NULL, /* dpy_invalidate */
    NULL, /* dpy_cursor_at */
    x11_bmp_alloc,
    x11_bmp_free,
    x11_bmp_draw,
    x11_bmp_lock,
    x11_bmp_unlock,
    x11_full_screen,
    NULL, /* next */
};

static CmdOptionDef cmd_options[] = {
    { "no-windows", "nw", NULL, CMD_OPT_BOOL, "force tty terminal usage",
      { .int_ptr = &force_tty }},
    { "display", "d", "display", CMD_OPT_STRING | CMD_OPT_ARG, "set X11 display",
      { .string_ptr = &display_str }},
    { "geometry", "g", "WxH", CMD_OPT_STRING | CMD_OPT_ARG, "set X11 display size",
      { .string_ptr = &geometry_str }},
    { "font-size", "fs", "ptsize", CMD_OPT_INT | CMD_OPT_ARG, "set default font size",
      { .int_ptr = &font_ptsize }},
    { NULL, NULL, NULL, 0, NULL, { NULL }},
};

static int x11_init(void)
{
    qe_register_cmd_line_options(cmd_options);
    return qe_register_display(&x11_dpy);
}

qe_module_init(x11_init);
