diff options
author | Gary Wong <gtw@gnu.org> | 2009-08-27 23:08:09 -0600 |
---|---|---|
committer | Gary Wong <gtw@gnu.org> | 2009-08-27 23:08:09 -0600 |
commit | 2b6c97bcb50a8cb9442f42e8c72152b4765b385f (patch) | |
tree | ec8dd54bf2a4d9cadd050ef84a36cc40861e2414 | |
parent | efce818c23cd62bf352a0bb9f5f6d397288bed88 (diff) |
Add support for RENDER extension.
-rw-r--r-- | ChangeLog | 11 | ||||
-rw-r--r-- | Makefile.am | 8 | ||||
-rw-r--r-- | configure.ac | 71 | ||||
-rw-r--r-- | decorate-core.c | 23 | ||||
-rw-r--r-- | decorate-render.c | 697 | ||||
-rw-r--r-- | decorate-render.h | 8 | ||||
-rw-r--r-- | gwm.c | 149 | ||||
-rw-r--r-- | gwm.h | 8 |
8 files changed, 932 insertions, 43 deletions
@@ -1,5 +1,16 @@ 2009-08-27 Gary Wong <gtw@gnu.org> + * decorate-render.c, decorate-render.h: New files. Implement + window decoration with RENDER extension (text only, so far). + * gwm.c (setup_display, shutdown_display): Set up and clean up + RENDER extension when available. + (show_error): Show RENDER errors. + + * decorate-core.c (core_update_window): Issue SetClipRectangles + request. + +2009-08-27 Gary Wong <gtw@gnu.org> + * gwm.c (sync_with_callback): Perform the sequence comparison with unsigned arithmetic, so that overflow semantics are well-defined. diff --git a/Makefile.am b/Makefile.am index d3be78f..b67b80d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,7 @@ bin_PROGRAMS = gwm -AM_CFLAGS = @XCB_CFLAGS@ -gwm_LDADD = @XCB_LIBS@ +AM_CFLAGS = @PKG_CFLAGS@ +gwm_LDADD = @PKG_LIBS@ gwm_SOURCES = \ actions.c actions.h \ button.c button.h \ @@ -13,3 +13,7 @@ gwm_SOURCES = \ root.c root.h \ utf8.c utf8.h \ window-table.c window-table.h + +if RENDER +gwm_SOURCES += decorate-render.c decorate-render.h +endif diff --git a/configure.ac b/configure.ac index 389fc34..b37d62d 100644 --- a/configure.ac +++ b/configure.ac @@ -8,18 +8,47 @@ if test -f config.options; then . ./config.options fi -AC_DEFINE(_GNU_SOURCE,1,Enable GNU extensions on systems that have them.) +gwm_packages="" + +# GWM_MANDATORY_PACKAGE(PKG-NAME) +AC_DEFUN([GWM_MANDATORY_PACKAGE],[ + AC_MSG_CHECKING([for $1]) + PKG_CHECK_EXISTS([$1], + [AC_MSG_RESULT(yes) + gwm_packages="$gwm_packages $1"], + [AC_MSG_ERROR(no)]) +]) + +# GWM_OPTIONAL_PACKAGE(PKG-NAME, [LABEL], [DEPENDENCIES]) +AC_DEFUN([GWM_OPTIONAL_PACKAGE],[ + gwm_label=m4_ifval([$2], [$2], [$1]) + gwm_name=`echo $ECHO_N $gwm_label | tr 'a-z' 'A-Z' | tr -c 'a-zA-Z0-9' _` + gwm_have_deps=yes + for gwm_i in [$3]; do + if eval test "x\$gwm_found_$gwm_i" != "xyes"; then gwm_have_deps=no; fi + done + if test $gwm_have_deps = yes; then + AC_MSG_CHECKING([for $gwm_label]) + if eval test "x\$with_$gwm_label" = xno; then + AC_MSG_RESULT(disabled) + else + PKG_CHECK_EXISTS([$1], + [AC_MSG_RESULT(yes) + gwm_packages="$gwm_packages $1" + eval "gwm_found_$gwm_label=yes" + AC_DEFINE_UNQUOTED(USE_$gwm_name, 1)], + [AC_MSG_RESULT(no)]) + fi + fi +]) + +AC_DEFINE(_GNU_SOURCE, 1, [Enable GNU extensions on systems that have them.]) # Checks for programs: AC_PROG_CC PKG_PROG_PKG_CONFIG # Checks for libraries: -# pkg-config packages which must be present: -packages="xcb" -# pkg-config xcb- packages which can optionally be used: -optional_xcb_packages="composite damage render shape xfixes" - AC_ARG_WITH(composite,[ --with-composite use the X Composite extension.]) AC_ARG_WITH(damage, [ --with-damage use the X DAMAGE extension.]) AC_ARG_WITH(render, [ --with-render use the X RENDER extension.]) @@ -32,19 +61,16 @@ AH_TEMPLATE(USE_RENDER,[Use the X RENDER extension.]) AH_TEMPLATE(USE_SHAPE,[Use the X SHAPE extension.]) AH_TEMPLATE(USE_XFIXES,[Use the XFIXES extension.]) -for i in $optional_xcb_packages; do - AC_MSG_CHECKING([for $i]) - if eval test "x\$with_$i" = xno; then - AC_MSG_RESULT(disabled) - else - iname=`echo $i | tr 'a-z' 'A-Z'` - PKG_CHECK_EXISTS( xcb-$i, - [AC_MSG_RESULT(yes) - packages="$packages xcb-$i" - AC_DEFINE_UNQUOTED(USE_$iname, 1)], - [AC_MSG_RESULT(no)]) - fi -done +GWM_MANDATORY_PACKAGE(xcb) +GWM_OPTIONAL_PACKAGE(fontconfig) +GWM_OPTIONAL_PACKAGE(freetype2) +GWM_OPTIONAL_PACKAGE(xcb-composite, composite) +GWM_OPTIONAL_PACKAGE(xcb-damage, damage) +GWM_OPTIONAL_PACKAGE(xcb-render, render, fontconfig freetype2) +GWM_OPTIONAL_PACKAGE(xcb-shape, shape) +GWM_OPTIONAL_PACKAGE(xcb-xfixes, xfixes) + +AM_CONDITIONAL(RENDER, [test x$gwm_found_render = xyes]) AC_SEARCH_LIBS(iconv, iconv) @@ -54,9 +80,10 @@ AC_CHECK_HEADERS(iconv.h mcheck.h poll.h) # Checks for functions: AC_CHECK_FUNCS(iconv mtrace ppoll) -PKG_CHECK_MODULES(XCB, $packages) +PKG_CFLAGS=`$PKG_CONFIG --cflags $gwm_packages` +PKG_LIBS=`$PKG_CONFIG --libs $gwm_packages` -AC_SUBST(XCB_CFLAGS) -AC_SUBST(XCB_LIBS) +AC_SUBST(PKG_CFLAGS) +AC_SUBST(PKG_LIBS) AC_OUTPUT diff --git a/decorate-core.c b/decorate-core.c index 6976537..77b7436 100644 --- a/decorate-core.c +++ b/decorate-core.c @@ -33,6 +33,11 @@ #include "decorate-core.h" +#define FONT_SIZE 12 +#define STRING2(x) #x +#define STRING(x) STRING2(x) +#define FONT_SIZE_STRING STRING( FONT_SIZE ) + static xcb_font_t font; static xcb_gcontext_t *gcs; @@ -124,18 +129,20 @@ static xcb_void_cookie_t core_text( xcb_drawable_t drawable, int screen, extern void core_update_window( struct gwm_window *window ) { - /* FIXME SetClipRectangles? */ if( !window->cleared ) xcb_clear_area( c, FALSE, window->w, window->update.x, window->update.y, window->update.width, window->update.height ); + xcb_set_clip_rectangles( c, XCB_CLIP_ORDERING_YX_BANDED, + gcs[ window->screen ], 0, 0, 1, &window->update ); + if( window->type == WINDOW_FRAME ) { char *name = window->u.frame.child->u.managed.name; - /* FIXME Define constants for position. */ core_text( window->w, window->screen, window == focus_frame ? - COL_TITLE_ACTIVE : COL_TITLE_INACTIVE, 18, 12, - name ? name : "(Untitled)" ); + COL_TITLE_ACTIVE : COL_TITLE_INACTIVE, + FRAME_BUTTON_SIZE + FRAME_BORDER_WIDTH * 3, + FONT_SIZE, name ? name : "(Untitled)" ); } else if( window->type == WINDOW_FEEDBACK ) { char *p, text[ 32 ]; int width; @@ -316,8 +323,8 @@ extern void decorate_core_init( void ) { int i; uint32_t n; - const char *font_name = "-*-lucida-bold-r-normal-sans-12-" - "*-*-*-p-*-iso10646-1", /* FIXME allow font selection */ + const char *font_name = "-*-lucida-bold-r-normal-sans-" FONT_SIZE_STRING + "-*-*-*-p-*-iso10646-1", /* FIXME allow font selection */ *cursor_font_name = "cursor"; xcb_char2b_t c2; union callback_param p; @@ -335,7 +342,7 @@ extern void decorate_core_init( void ) { 16, /* CURSOR_B */ 14 /* CURSOR_BR */ }; - + font = xcb_generate_id( c ); xcb_open_font( c, font, strlen( font_name ), font_name ); @@ -384,5 +391,7 @@ extern void decorate_core_init( void ) { extern void decorate_core_done( void ) { +#if DEBUG free( gcs ); +#endif } diff --git a/decorate-render.c b/decorate-render.c new file mode 100644 index 0000000..c7baef8 --- /dev/null +++ b/decorate-render.c @@ -0,0 +1,697 @@ +/* + * decorate-render.c + * + * Part of gwm, the Gratuitous Window Manager, + * by Gary Wong, <gtw@gnu.org>. + * + * Copyright (C) 2009 Gary Wong + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of version 3 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Id$ + */ + +#include <config.h> + +#include <assert.h> +#include <fontconfig/fontconfig.h> +#include <ft2build.h> +#include FT_FREETYPE_H +#include FT_CACHE_H +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <xcb/render.h> +#include <xcb/xcb.h> + +#include "gwm.h" + +#include "decorate-render.h" + +/* FIXME make this configurable */ +#define FONT_FAMILY "sans" +#define FONT_WEIGHT FC_WEIGHT_BOLD +#define FONT_PIXEL_SIZE 12 +#define FONT_LOAD_TARGET FT_LOAD_TARGET_LIGHT + +static FT_Library ftl; +static FT_Face ftf; + +static const uint16_t decoration_cols[ NUM_COLS ][ 3 ] = { + { 0x2222, 0x3333, 0xEEEE }, /* COL_FRAME_ACTIVE */ + { 0xAAAA, 0xAAAA, 0xAAAA }, /* COL_FRAME_INACTIVE */ + { 0x0000, 0x0000, 0x0000 }, /* COL_BORDER */ + { 0xFFFF, 0x0000, 0x0000 }, /* COL_BUTTON_ACTIVE */ + { 0xCCCC, 0xCCCC, 0xCCCC }, /* COL_BUTTON_INACTIVE */ + { 0xFFFF, 0xFFFF, 0xFFFF }, /* COL_TITLE_ACTIVE */ + { 0x3333, 0x3333, 0x3333 }, /* COL_TITLE_INACTIVE */ + { 0xFFFF, 0xFFFF, 0xFFFF }, /* COL_FEEDBACK_BACK */ + { 0x0000, 0x0000, 0x0000 } /* COL_FEEDBACK_FORE */ +}; + +#define METRIC_CACHE_SIZE_BITS 14 +#define METRIC_CACHE_SIZE ( 1 << METRIC_CACHE_SIZE_BITS ) +#define METRIC_CACHE_ASSOC_BITS 2 +#define METRIC_CACHE_ASSOC ( 1 << METRIC_CACHE_ASSOC_BITS ) + +#define GLYPH_CACHE_SIZE_BITS 11 +#define GLYPH_CACHE_SIZE ( 1 << GLYPH_CACHE_SIZE_BITS ) +#define GLYPH_CACHE_ASSOC_BITS 3 +#define GLYPH_CACHE_ASSOC ( 1 << GLYPH_CACHE_ASSOC_BITS ) + +#if GLYPH_CACHE_SIZE_BITS + GLYPH_CACHE_ASSOC_BITS > 16 +#error "Glyph cache is too big for 16-bit indices." +#endif + +static struct metric_cache_line { + uint32_t c; /* character code */ + unsigned int time; + int x_off, y_off; +} metric_cache[ METRIC_CACHE_SIZE ][ METRIC_CACHE_ASSOC ]; + +static struct glyph_cache_line { + uint32_t c; /* character code */ + unsigned int time; +} glyph_cache[ GLYPH_CACHE_SIZE ][ GLYPH_CACHE_ASSOC ]; + +static unsigned int current_time; + +static void query_metrics( uint32_t c, int *x_off, int *y_off ) { + + int row = c & ( METRIC_CACHE_SIZE - 1 ); + int i, max; + + for( i = 0; i < METRIC_CACHE_ASSOC; i++ ) + if( metric_cache[ row ][ i ].c == c ) { + /* Cache hit. */ + metric_cache[ row ][ i ].time = current_time; + *x_off = metric_cache[ row ][ i ].x_off; + *y_off = metric_cache[ row ][ i ].y_off; + return; + } + + /* Cache miss. */ + if( FT_Load_Char( ftf, c, FONT_LOAD_TARGET ) ) { + /* Couldn't load metrics. Don't bother evicting anything. */ + *x_off = *y_off = 0; + return; + } + + /* Search for a line to evict. */ + for( i = 0, max = 0; i < METRIC_CACHE_ASSOC; i++ ) + if( current_time - metric_cache[ row ][ i ].time > + current_time - metric_cache[ row ][ max ].time ) + max = i; + + metric_cache[ row ][ max ].c = c; + metric_cache[ row ][ max ].time = current_time; + *x_off = metric_cache[ row ][ max ].x_off = + ( ftf->glyph->advance.x + 0x20 ) >> 6; + *y_off = metric_cache[ row ][ max ].y_off = + ( ftf->glyph->advance.y + 0x20 ) >> 6; +} + +static int query_glyph( uint32_t c ) { + + int row = c & ( GLYPH_CACHE_SIZE - 1 ); + int i; + + for( i = 0; i < GLYPH_CACHE_ASSOC; i++ ) + if( glyph_cache[ row ][ i ].c == c ) { + /* Cache hit. */ + glyph_cache[ row ][ i ].time = current_time; + return ( row << GLYPH_CACHE_ASSOC_BITS ) | i; + } + + return -1; +} + +static int replace_glyph( uint32_t c ) { + + int row = c & ( GLYPH_CACHE_SIZE - 1 ); + int i, max; + + /* Search for a line to evict. */ + for( i = 0, max = 0; i < METRIC_CACHE_ASSOC; i++ ) { + assert( glyph_cache[ row ][ i ].c != c ); + if( current_time - glyph_cache[ row ][ i ].time > + current_time - glyph_cache[ row ][ max ].time ) + max = i; + } + + if( glyph_cache[ row ][ max ].time == current_time ) + /* Cache line set is full, and nothing is old enough to evict. */ + return -1; + + glyph_cache[ row ][ max ].c = c; + glyph_cache[ row ][ max ].time = current_time; + + return ( row << GLYPH_CACHE_ASSOC_BITS ) | max; +} + +static xcb_render_glyphset_t glyphset; +static struct picture { + xcb_render_picture_t pic; + enum decoration_col col; +} *pictures; /* indexed by screen */ + +static xcb_void_cookie_t render_text( xcb_drawable_t drawable, int screen, + int col, int x, int y, + const char *text, + const xcb_rectangle_t *clip ) { + + xcb_render_picture_t src, dest; + xcb_rectangle_t rect; + xcb_render_color_t rc; + int i, len; + const unsigned char *p; + uint32_t *buf; + + if( !text || !*text ) { + xcb_void_cookie_t r = { 0 }; + + return r; + } + + len = 0; + p = (const unsigned char *) text; + for(;;) + if( !p[ 0 ] ) + /* End of string. */ + break; + else if( !( p[ 0 ] & 0x80 ) ) { + /* Legal single byte character. */ + len++; + p++; + } else if( *p >= 0xC2 && *p <= 0xDF && + p[ 1 ] >= 0x80 && p[ 1 ] <= 0xBF ) { + /* Legal two byte character. */ + len++; + p += 2; + } else if( *p >= 0xE0 && *p <= 0xEF && + p[ 1 ] >= 0x80 && p[ 1 ] <= 0xBF && + p[ 2 ] >= 0x80 && p[ 2 ] <= 0xBF && + ( *p > 0xE0 || p[ 1 ] > 0x80 ) ) { + /* Legal three byte character. */ + len++; + p += 3; + } else if( *p >= 0xF0 && *p <= 0xF4 && + p[ 1 ] >= 0x80 && p[ 1 ] <= 0xBF && + p[ 2 ] >= 0x80 && p[ 2 ] <= 0xBF && + p[ 3 ] >= 0x80 && p[ 3 ] <= 0xBF && + ( *p > 0xF0 || p[ 1 ] > 0x80 ) ) { + /* Legal four byte character. */ + len++; + p += 4; + } else + /* Illegal character: ignore this byte and continue. */ + p++; + + if( !len ) { + xcb_void_cookie_t r = { 0 }; + + return r; + } + + buf = alloca( len * sizeof *buf ); + + for( p = (const unsigned char *) text, i = 0; i < len; i++ ) { + retry: + assert( *p ); + + if( !( p[ 0 ] & 0x80 ) ) + /* One byte character. */ + buf[ i ] = *p++; + else if( *p >= 0xC2 && *p <= 0xDF && + p[ 1 ] >= 0x80 && p[ 1 ] <= 0xBF ) { + /* Two byte character. */ + buf[ i ] = ( ( p[ 0 ] & 0x1F ) << 6 ) | ( p[ 1 ] & 0x3F ); + p += 2; + } else if( *p >= 0xE0 && *p <= 0xEF && + p[ 1 ] >= 0x80 && p[ 1 ] <= 0xBF && + p[ 2 ] >= 0x80 && p[ 2 ] <= 0xBF && + ( *p > 0xE0 || p[ 1 ] > 0x80 ) ) { + /* Three byte character. */ + buf[ i ] = ( ( p[ 0 ] & 0x0F ) << 12 ) | + ( ( p[ 1 ] & 0x3F ) << 6 ) | ( p[ 2 ] & 0x3F ); + p += 3; + } else if( *p >= 0xF0 && *p <= 0xF4 && + p[ 1 ] >= 0x80 && p[ 1 ] <= 0xBF && + p[ 2 ] >= 0x80 && p[ 2 ] <= 0xBF && + p[ 3 ] >= 0x80 && p[ 3 ] <= 0xBF && + ( *p > 0xF0 || p[ 1 ] > 0x80 ) ) { + /* Four byte character. */ + buf[ i ] = ( ( p[ 0 ] & 0x07 ) << 18 ) | + ( ( p[ 1 ] & 0x3F ) << 12 ) | ( ( p[ 2 ] & 0x3F ) << 6 ) | + ( p[ 3 ] & 0x3F ); + p += 4; + } else { + p++; + goto retry; + } + } + + src = pictures[ screen ].pic; + + dest = xcb_generate_id( c ); + xcb_render_create_picture( c, dest, drawable, + gwm_screens[ screen ].root_pictformat, 0, NULL ); + + if( clip ) + xcb_render_set_picture_clip_rectangles( c, dest, 0, 0, 1, clip ); + + if( pictures[ screen ].col != col ) { + rc.red = decoration_cols[ col ][ 0 ]; + rc.green = decoration_cols[ col ][ 1 ]; + rc.blue = decoration_cols[ col ][ 2 ]; + rc.alpha = 0xFFFF; + rect.x = 0; + rect.y = 0; + rect.width = 1; + rect.height = 1; + xcb_render_fill_rectangles( c, XCB_RENDER_PICT_OP_SRC, src, rc, 1, + &rect ); + + pictures[ screen ].col = col; + } + + i = 0; + while( i < len ) { + uint32_t glyphids[ 0x80 ]; + xcb_render_glyphinfo_t glyphs[ 0x80 ]; + int num_glyphs; /* number of glyphs in AddGlyphs request */ + /* Buffer for "data" parameter of AddGlyphs request. We want + this to be big enough to hold at least one glyph, but no + bigger than the biggest request the server will accept (once + the other parameters are included). All servers must + accept requests of 16,384 bytes or smaller (see X Window + System Protocol, section 8), so this is safe. */ + uint8_t data[ 0x2000 ], *p; + /* Buffer for "glyphcmds" parameter of CompositeGlyphs16 + request. We always send exactly one command, which has an + 8-byte header and at most a 254 glyph string. */ + uint8_t param[ 8 + ( 0xFE << 1 ) ], *out; + int num_chars; /* number of characters for CompositeGlyphs16 */ + + current_time++; + + p = data; + num_glyphs = 0; + + memset( param, 0, 4 ); + *( (uint16_t *) ( param + 4 ) ) = x; + *( (uint16_t *) ( param + 6 ) ) = y; + out = param + 8; + num_chars = 0; + + while( i < len && num_chars < 0xFF ) { + int dx, dy, index; + + if( ( index = query_glyph( buf[ i ] ) ) < 0 ) { + /* Cache miss. */ + int bitmap_size; + FT_GlyphSlot slot; + + if( ( index = replace_glyph( buf[ i ] ) ) < 0 ) + /* Cache set full: spill the partial string to make + room for later glyphs. */ + break; + + if( FT_Load_Char( ftf, buf[ i ], FT_LOAD_RENDER | + FONT_LOAD_TARGET ) || + ( bitmap_size = ( ( ftf->glyph->bitmap.width + 3 ) & ~3 ) * + ftf->glyph->bitmap.rows ) > sizeof data ) { + /* We couldn't load the character, or it was so + huge that it won't fit in an empty AddGlyph + data buffer. We'll have to send the server + an empty glyph and carry on. */ + slot = NULL; + bitmap_size = 0; + } else + slot = ftf->glyph; + + if( ( p - data ) + bitmap_size > sizeof data || + num_glyphs == sizeof glyphids / sizeof *glyphids ) { + /* Can't fit this glyph into the existing request: + transmit what we have... */ + xcb_render_add_glyphs( c, glyphset, num_glyphs, glyphids, + glyphs, p - data, data ); + + /* ...and start building another AddGlyph request. */ + p = data; + num_glyphs = 0; + } + + glyphids[ num_glyphs ] = index; + + if( slot ) { + int y, width_pad; + + glyphs[ num_glyphs ].width = slot->bitmap.width; + glyphs[ num_glyphs ].height = slot->bitmap.rows; + glyphs[ num_glyphs ].x = -slot->bitmap_left; + glyphs[ num_glyphs ].y = slot->bitmap_top; + glyphs[ num_glyphs ].x_off = + ( slot->advance.x + 0x20 ) >> 6; + glyphs[ num_glyphs ].y_off = + ( slot->advance.y + 0x20 ) >> 6; + + width_pad = ( slot->bitmap.width + 3 ) & ~3; + + for( y = 0; y < slot->bitmap.rows; y++ ) { + memcpy( p, slot->bitmap.buffer + y * + slot->bitmap.width, slot->bitmap.width ); + p += width_pad; + } + } else { + glyphs[ num_glyphs ].width = 0; + glyphs[ num_glyphs ].height = 0; + glyphs[ num_glyphs ].x = 0; + glyphs[ num_glyphs ].y = 0; + glyphs[ num_glyphs ].x_off = 0; + glyphs[ num_glyphs ].y_off = 0; + } + + num_glyphs++; + } + + *( (uint16_t *) out ) = index; + out += 2; + + query_metrics( buf[ i ], &dx, &dy ); + x += dx; + y += dy; + + num_chars++; + i++; + } + + if( num_glyphs ) { + xcb_render_add_glyphs( c, glyphset, num_glyphs, glyphids, glyphs, + p - data, data ); + + p = data; + num_glyphs = 0; + } + + param[ 0 ] = num_chars; + + xcb_render_composite_glyphs_16( c, XCB_RENDER_PICT_OP_OVER, src, dest, + XCB_NONE, glyphset, 0, 0, out - param, + param ); + } + + return xcb_render_free_picture( c, dest ); +} + +extern void render_update_window( struct gwm_window *window ) { + + if( !window->cleared ) + xcb_clear_area( c, FALSE, window->w, window->update.x, window->update.y, + window->update.width, window->update.height ); + + if( window->type == WINDOW_FRAME ) { + char *name = window->u.frame.child->u.managed.name; + + render_text( window->w, window->screen, window == focus_frame ? + COL_TITLE_ACTIVE : COL_TITLE_INACTIVE, + FRAME_BUTTON_SIZE + FRAME_BORDER_WIDTH * 3, + FONT_PIXEL_SIZE, name ? name : "(Untitled)", + &window->update ); + } else if( window->type == WINDOW_FEEDBACK ) { + char *p, text[ 32 ]; + int width; + + sprintf( text, "%dx%d", window->u.feedback.fb_width, + window->u.feedback.fb_height ); + + for( width = 0, p = text; *p; p++ ) { + int dx, dy; + + query_metrics( *p, &dx, &dy ); + width += dx; + } + + render_text( window->w, window->screen, COL_FEEDBACK_FORE, + ( FEEDBACK_WIDTH - width ) >> 1, 16, text, + &window->update ); + } +} + +static uint16_t luma( int col ) { + + return ( decoration_cols[ col ][ 0 ] * 0x4C8BU + + decoration_cols[ col ][ 1 ] * 0x9646U + + decoration_cols[ col ][ 2 ] * 0x1D2FU ) >> 16; +} + +static void handle_alloc_color( unsigned int sequence, void *reply, + xcb_generic_error_t *error, + union callback_param p ) { + + xcb_alloc_color_reply_t *r = reply; + int screen = p.l >> 16, col = p.l & 0xFFFF; + + if( error ) { + if( error->error_code != XCB_ALLOC ) + show_error( error ); + + /* No standard RGB map available, and we couldn't allocate a shared + colour. Fall back to black or white. */ + gwm_screens[ screen ].pixels[ col ] = luma( col ) >= 0x8000 ? + screens[ screen ]->white_pixel : screens[ screen ]->black_pixel; + + free( error ); + } + + if( reply ) { + gwm_screens[ screen ].pixels[ col ] = r->pixel; + + free( reply ); + } +} + +static void handle_get_default_map( unsigned int sequence, void *reply, + xcb_generic_error_t *error, + union callback_param p ) { + + int col, got_cols = FALSE; + + if( error ) { + show_error( error ); + + free( error ); + } + + if( reply ) { + xcb_get_property_reply_t *prop = reply; + struct std_cmap { + /* RGB_COLOR_MAP property -- see ICCCM 2.0, section 6.4. */ + xcb_colormap_t colormap; + uint32_t red_max, red_mult, green_max, green_mult, blue_max, + blue_mult, base_pixel; + xcb_visualid_t visual_id; + uint32_t kill_id; + } *std_cmaps = xcb_get_property_value( prop ); + int num_cmaps = ( xcb_get_property_value_length( prop ) << 2 ) / + sizeof *std_cmaps; + int i; + + if( prop->format == 32 ) + for( i = 0; i < num_cmaps; i++ ) + if( std_cmaps[ i ].visual_id == + gwm_screens[ p.l ].root_visual->visual_id ) { + + for( col = 0; col < NUM_COLS; col++ ) { + int r0, g0, b0, r, g, b; + + if( gwm_screens[ p.l ].root_visual->_class == + XCB_VISUAL_CLASS_STATIC_GRAY || + gwm_screens[ p.l ].root_visual->_class == + XCB_VISUAL_CLASS_GRAY_SCALE ) { + r0 = luma( col ); + g0 = b0 = 0; + } else { + r0 = decoration_cols[ col ][ 0 ]; + g0 = decoration_cols[ col ][ 1 ]; + b0 = decoration_cols[ col ][ 2 ]; + } + + r = ( r0 * std_cmaps[ i ].red_max + 0x8000 ) >> 16; + g = ( g0 * std_cmaps[ i ].green_max + 0x8000 ) >> 16; + b = ( b0 * std_cmaps[ i ].blue_max + 0x8000 ) >> 16; + + gwm_screens[ p.l ].pixels[ col ] = + std_cmaps[ i ].base_pixel + + r * std_cmaps[ i ].red_mult + + g * std_cmaps[ i ].green_mult + + b * std_cmaps[ i ].blue_mult; + } + + got_cols = TRUE; + break; + } + + free( reply ); + } + + if( !got_cols ) + /* No useful default maps found; try allocating directly. */ + for( col = 0; col < NUM_COLS; col++ ) { + int r, g, b; + union callback_param cp; + + if( gwm_screens[ p.l ].root_visual->_class == + XCB_VISUAL_CLASS_STATIC_GRAY || + gwm_screens[ p.l ].root_visual->_class == + XCB_VISUAL_CLASS_GRAY_SCALE ) + r = g = b = luma( col ); + else { + r = decoration_cols[ col ][ 0 ]; + g = decoration_cols[ col ][ 1 ]; + b = decoration_cols[ col ][ 2 ]; + } + + cp.l = ( p.l << 16 ) | col; + /* Ideally, we would like a guarantee that this operation will + complete before the pixel values are ever looked up. In + practice, it will, and it is too messy to insert a + sync_with_callback() before every single pixel reference. */ + handle_async_reply( xcb_alloc_color( + c, screens[ p.l ]->default_colormap, + r, g, b ).sequence, handle_alloc_color, + cp ); + } +} + +static void decorate_compat_init( void ) { + + int i; + const char *cursor_font_name = "cursor"; + xcb_font_t cursor_font; + static const int cursor_glyphs[ NUM_CURSORS ] = { + 68, /* CURSOR_ARROW */ + 88, /* CURSOR_DESTROY */ + 134, /* CURSOR_TL */ + 138, /* CURSOR_T */ + 136, /* CURSOR_TR */ + 70, /* CURSOR_L */ + 52, /* CURSOR_C */ + 96, /* CURSOR_R */ + 12, /* CURSOR_BL */ + 16, /* CURSOR_B */ + 14 /* CURSOR_BR */ + }; + + cursor_font = xcb_generate_id( c ); + xcb_open_font( c, cursor_font, strlen( cursor_font_name ), + cursor_font_name ); + for( i = 0; i < NUM_CURSORS; i++ ) { + cursors[ i ] = xcb_generate_id( c ); + xcb_create_glyph_cursor( c, cursors[ i ], cursor_font, cursor_font, + cursor_glyphs[ i ], cursor_glyphs[ i ] | 1, + 0, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF ); + } + xcb_close_font( c, cursor_font ); + + /* Retrieve any RGB_DEFAULT_MAP properties on the roots. */ + for( i = 0; i < num_screens; i++ ) { + union callback_param cp; + + cp.l = i; + handle_async_reply( xcb_get_property( c, FALSE, screens[ i ]->root, + RGB_DEFAULT_MAP, RGB_COLOR_MAP, + 0, 0x10000 ).sequence, + handle_get_default_map, cp ); + } +} + +extern void decorate_render_init( void ) { + + FcPattern *pat, *font; + FcResult r; + FcValue v; + FT_Error err; + char *filename; + int i, index; + xcb_pixmap_t pixmap; + uint32_t n; + + FcInit(); + + pat = FcPatternBuild( NULL, FC_FAMILY, FcTypeString, FONT_FAMILY, + FC_WEIGHT, FcTypeInteger, FONT_WEIGHT, + FC_PIXEL_SIZE, FcTypeDouble, (double) FONT_PIXEL_SIZE, + NULL ); + + FcConfigSubstitute( NULL, pat, FcMatchPattern ); + + FcDefaultSubstitute( pat ); + + font = FcFontMatch( NULL, pat, &r ); + + FcPatternGet( font, FC_FILE, 0, &v ); + filename = (char *) v.u.s; + FcPatternGet( font, FC_INDEX, 0, &v ); + index = v.u.i; + + if( ( err = FT_Init_FreeType( &ftl ) ) ) + fatal( "typeface initialisation error %d", err ); + + if( ( err = FT_New_Face( ftl, filename, index, &ftf ) ) ) + fatal( "%s: could not load font (%d)", filename, err ); + + FcPatternDestroy( pat ); + FcPatternDestroy( font ); + + FcFini(); + + if( ( err = FT_Set_Char_Size( ftf, 0, FONT_PIXEL_SIZE << 6, 0, 0 ) ) ) + fatal( "%s: could not scale font (%d)", filename, err ); + + if( ( err = FT_Select_Charmap( ftf, FT_ENCODING_UNICODE ) ) ) + fatal( "%s: could not select character map (%d)", filename, err ); + + glyphset = xcb_generate_id( c ); + xcb_render_create_glyph_set( c, glyphset, fmt_a8 ); + + pixmap = xcb_generate_id( c ); + + pictures = xmalloc( num_screens * sizeof *pictures ); + + n = XCB_RENDER_REPEAT_NORMAL; + for( i = 0; i < num_screens; i++ ) { + xcb_create_pixmap( c, screens[ i ]->root_depth, pixmap, + screens[ i ]->root, 1, 1 ); + + pictures[ i ].pic = xcb_generate_id( c ); + xcb_render_create_picture( c, pictures[ i ].pic, pixmap, + gwm_screens[ i ].root_pictformat, + XCB_RENDER_CP_REPEAT, &n ); + pictures[ i ].col = -1; + + xcb_free_pixmap( c, pixmap ); + } + + decorate_compat_init(); /* FIXME this is for pixel values (backgrounds) + and cursors */ +} + +extern void decorate_render_done( void ) { + +#if DEBUG + free( pictures ); + FT_Done_Face( ftf ); + FT_Done_FreeType( ftl ); +#endif +} diff --git a/decorate-render.h b/decorate-render.h new file mode 100644 index 0000000..e607544 --- /dev/null +++ b/decorate-render.h @@ -0,0 +1,8 @@ +#ifndef DECORATE_RENDER_H +#define DECORATE_RENDER_H + +extern void render_update_window( struct gwm_window *window ); +extern void decorate_render_init( void ); +extern void decorate_render_done( void ); + +#endif @@ -47,7 +47,6 @@ #if USE_DAMAGE #include <xcb/damage.h> #endif -#include <xcb/xproto.h> #if USE_RENDER #include <xcb/render.h> #endif @@ -64,6 +63,9 @@ #include "button.h" #include "decorate-core.h" +#if USE_RENDER +#include "decorate-render.h" +#endif #include "frame.h" #include "keyboard.h" #include "managed.h" @@ -127,6 +129,11 @@ int num_screens; xcb_screen_t **screens; struct gwm_screen *gwm_screens; +#if USE_RENDER +xcb_render_pictformat_t fmt_rgba8, fmt_a8; +xcb_render_directformat_t dfmt_rgba8, dfmt_a8; +#endif + xcb_cursor_t cursors[ NUM_CURSORS ]; struct gwm_window *fake_window, *focus_frame; @@ -417,6 +424,20 @@ extern void show_error( xcb_generic_error_t *error ) { bad_value_str = ""; break; } + +#if USE_RENDER + if( have_extension[ EXT_RENDER ] && + error->error_code >= extension_error[ EXT_RENDER ] && + error->error_code <= extension_error[ EXT_RENDER ] + + XCB_RENDER_GLYPH ) { + static const char *render_strings[ XCB_RENDER_GLYPH + 1 ] = { + "PictFormat", "Picture", "PictOp", "GlyphSet", "Glyph" + }; + + type = render_strings[ error->error_code - + extension_error[ EXT_RENDER ] ]; + } +#endif warning( "unexpected error (type %s, sequence %d; opcode %d %d%s)", type, error->sequence, verror->major_opcode, verror->minor_opcode, @@ -777,7 +798,8 @@ extern xcb_generic_event_t *wait_for_event( void ) { #endif check_async_callbacks(); /* must poll for events first, since - polling replies won't dequeue them */ + polling replies won't dequeue them */ + if( ev ) return ev; @@ -1741,7 +1763,7 @@ static void setup_display( void ) { xcb_client_message_event_t msg; xcb_void_cookie_t *cookies; xcb_query_tree_cookie_t *tree_cookies; - + table_init( &windows ); table_init( &update_windows ); @@ -1825,13 +1847,6 @@ static void setup_display( void ) { } } - decorate_core_init(); - update_window = core_update_window; - - /* FIXME For each extension we have, query if its version is recent - enough if neccesary. (SHAPE is OK -- version 1.0 is enough, and - it's the only extension we bother with so far.) */ - for( i = 0; i < NUM_ATOMS; i++ ) { xcb_intern_atom_reply_t *r = xcb_intern_atom_reply( c, atom_cookies[ i ], NULL ); @@ -1865,6 +1880,111 @@ static void setup_display( void ) { free( r ); } +#if USE_RENDER + if( have_extension[ EXT_RENDER ] ) { + xcb_render_query_version_cookie_t render_cookie; + xcb_render_query_pict_formats_cookie_t formats_cookie; + xcb_render_query_version_reply_t *render_reply; + xcb_render_query_pict_formats_reply_t *formats_reply; + xcb_render_pictforminfo_t *formats; + int i; + xcb_render_pictscreen_iterator_t siter; + + render_cookie = xcb_render_query_version( c, XCB_RENDER_MAJOR_VERSION, + XCB_RENDER_MINOR_VERSION ); + + formats_cookie = xcb_render_query_pict_formats( c ); + + xcb_flush( c ); /* BLOCK */ + + render_reply = xcb_render_query_version_reply( c, render_cookie, NULL ); + + if( !render_reply ) { + have_extension[ EXT_RENDER ] = FALSE; + goto no_render; + } + + if( !render_reply->major_version && render_reply->minor_version < 1 ) { + /* We need at least version 0.1, for FillRectangles support. It's + too inconvenient to initialise pictures to a constant colour + without it (which seems to have been an oversight in the + original specification of the protocol). */ + have_extension[ EXT_RENDER ] = FALSE; + + free( render_reply ); + + goto no_render; + } + + free( render_reply ); + + formats_reply = xcb_render_query_pict_formats_reply( c, formats_cookie, + NULL ); + + formats = xcb_render_query_pict_formats_formats( formats_reply ); + + for( i = 0; i < formats_reply->num_formats; i++ ) + if( formats[ i ].type == XCB_RENDER_PICT_TYPE_DIRECT && + formats[ i ].direct.alpha_mask == 0xFF ) { + if( !formats[ i ].direct.red_mask && + !formats[ i ].direct.green_mask && + !formats[ i ].direct.blue_mask ) { + fmt_a8 = formats[ i ].id; + memcpy( &dfmt_a8, &formats[ i ].direct, sizeof dfmt_a8 ); + } else if( formats[ i ].direct.red_mask == 0xFF && + formats[ i ].direct.green_mask == 0xFF && + formats[ i ].direct.blue_mask == 0xFF ) { + fmt_rgba8 = formats[ i ].id; + memcpy( &dfmt_rgba8, &formats[ i ].direct, + sizeof dfmt_rgba8 ); + } + } + + if( !fmt_a8 || !fmt_rgba8 ) + /* These two pictformats are guaranteed by the RENDER protocol + (section 7). We can't cope without them. */ + have_extension[ EXT_RENDER ] = FALSE; + + for( i = 0, siter = xcb_render_query_pict_formats_screens_iterator( + formats_reply ); siter.rem; + i++, xcb_render_pictscreen_next( &siter ) ) { + xcb_render_pictdepth_iterator_t diter; + + for( diter = xcb_render_pictscreen_depths_iterator( siter.data ); + diter.rem; xcb_render_pictdepth_next( &diter ) ) { + xcb_render_pictvisual_iterator_t viter; + + for( viter = xcb_render_pictdepth_visuals_iterator( + diter.data ); viter.rem; + xcb_render_pictvisual_next( &viter ) ) + if( viter.data->visual == + gwm_screens[ i ].root_visual->visual_id ) { + gwm_screens[ i ].root_pictformat = viter.data->format; + goto next_screen; + } + } + next_screen: + ; + } + + free( formats_reply ); + + no_render: + ; + } +#endif + +#if USE_RENDER + if( have_extension[ EXT_RENDER ] ) { + decorate_render_init(); + update_window = render_update_window; + } else +#endif + { + decorate_core_init(); + update_window = core_update_window; + } + /* Listen for a selection timestamp. */ for(;;) { /* BLOCK */ @@ -2501,13 +2621,18 @@ retry: xcb_set_input_focus( c, XCB_INPUT_FOCUS_NONE, XCB_INPUT_FOCUS_POINTER_ROOT, XCB_CURRENT_TIME ); - decorate_core_done(); - n = 0; for( i = 0; i < num_screens; i++ ) xcb_change_window_attributes( c, screens[ i ]->root, XCB_CW_EVENT_MASK, &n ); +#if USE_RENDER + if( have_extension[ EXT_RENDER ] ) + decorate_render_done(); + else +#endif + decorate_core_done(); + /* A pointless request to generate a reply, so we'll know when we've processed everything the server will send us. */ cookie = xcb_intern_atom( c, TRUE, 4, "ATOM" ); @@ -122,6 +122,11 @@ extern int have_extension[ EXTENSIONS_SIZE ]; extern uint8_t extension_event[ EXTENSIONS_SIZE ], extension_error[ EXTENSIONS_SIZE ]; +#if USE_RENDER && defined( XCB_RENDER_MAJOR_VERSION ) +extern xcb_render_pictformat_t fmt_rgba8, fmt_a8; +extern xcb_render_directformat_t dfmt_rgba8, dfmt_a8; +#endif + enum decoration_col { COL_FRAME_ACTIVE, COL_FRAME_INACTIVE, @@ -144,6 +149,9 @@ struct gwm_screen { xcb_colormap_t cmap; /* the most recently installed colormap */ xcb_timestamp_t cmap_time; /* the time at which it was installed */ uint32_t pixels[ NUM_COLS ]; /* pixel values in the default cmap */ +#if USE_RENDER + uint32_t root_pictformat; +#endif }; extern struct gwm_screen *gwm_screens; |