/* * X11 handling for QEmacs * * Copyright (c) 2000, 2001, 2002, 2003 Fabrice Bellard. * Copyright (c) 2002-2013 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 #include #include #include #include #include #include #include #include #include #ifdef CONFIG_XFT #include #include #endif #ifdef CONFIG_XV #include #include #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 **fontp); #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->priv_data = 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->priv_data = renderFont; return font; } static void term_close_font(QEditScreen *s, QEFont **fontp) { QEFont *font = *fontp; if (font) { XftFont *renderFont = font->priv_data; XftFontClose(display, renderFont); /* Clear structure to force crash if font is still used after * close_font. */ memset(font, 0, sizeof(*font)); qe_free(fontp); } } static int term_glyph_width(QEditScreen *s, QEFont *font, unsigned int cc) { XftFont *renderFont = font->priv_data; 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->priv_data; 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_mallocz(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->priv_data = xfont; return font; fail: XFreeFontNames(list); qe_free(&font); return NULL; } static void term_close_font(__unused__ QEditScreen *s, QEFont **fontp) { if (*fontp) { QEFont *font = *fontp; XFontStruct *xfont = font->priv_data; XFreeFont(display, xfont); /* Clear structure to force crash if font is still used after * close_font. */ memset(font, 0, sizeof(*font)); qe_free(fontp); } } /* 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->priv_data; 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->priv_data; 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->priv_data; cc = xfont->default_char; } } /* flush previous chars if font change needed */ if (font1 != last_font && q > x11_str) { xfont = last_font->priv_data; 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->priv_data; 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, NULL); 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_mallocz(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_mallocz(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_mallocz(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);