/* * decorate-render.c * * Part of gwm, the Gratuitous Window Manager, * by Gary Wong, . * * 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 . * * $Id$ */ #include #include #include #include #include FT_FREETYPE_H #include FT_BITMAP_H #include #include #include #include #include #if USE_CURSOR #include #endif #include "gwm.h" #include "button.h" #include "decorate-render.h" #include "frame.h" #include "image.h" #include "managed.h" #include "utf8.h" #include "window-table.h" enum style_id { STYLE_TITLE, STYLE_MENU, NUM_STYLES }; #define TITLE_FONT_SIZE 12 #define MENU_FONT_SIZE 14 #define STRING2(x) #x #define STRING(x) STRING2(x) #define TITLE_FONT_SIZE_STRING STRING( TITLE_FONT_SIZE ) #define MENU_FONT_SIZE_STRING STRING( MENU_FONT_SIZE ) #define MENU_X_PAD 4 #define MENU_Y_PAD 2 #define FEEDBACK_WIDTH 96 /* width of size feedback window */ #define FEEDBACK_HEIGHT 24 /* height of size feedback window */ #define ICON_SIZE 256 static const FcChar8 *const style_names[ NUM_STYLES ] = { /* FIXME make this configurable */ (FcChar8 *) "sans:pixelsize=" TITLE_FONT_SIZE_STRING ":bold", (FcChar8 *) "sans:pixelsize=" MENU_FONT_SIZE_STRING }; static struct font { FcPattern *pattern; FcCharSet *charset; FT_Face face; FT_Int32 load_flags; } *fonts; static int num_fonts; static struct style { int *fonts; /* indices into font table above */ int num_fonts; FcCharSet *charset; } styles[ NUM_STYLES ]; static FT_Library ftl; 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 */ { 0x2222, 0x3333, 0xEEEE }, /* COL_MENU_ACTIVE_BACK */ { 0xFFFF, 0xFFFF, 0xFFFF }, /* COL_MENU_ACTIVE_FORE */ { 0xCCCC, 0xCCCC, 0xCCCC }, /* COL_MENU_INACTIVE_BACK */ { 0x0000, 0x0000, 0x0000 } /* COL_MENU_INACTIVE_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 { enum style_id style; 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 { enum style_id style; uint32_t c; /* character code */ unsigned int time; } glyph_cache[ GLYPH_CACHE_SIZE ][ GLYPH_CACHE_ASSOC ]; static unsigned int current_time; static struct font *lookup_font( enum style_id style, uint32_t c ) { int i; if( !FcCharSetHasChar( styles[ style ].charset, c ) ) return NULL; for( i = 0; i < styles[ style ].num_fonts; i++ ) { struct font *font = fonts + styles[ style ].fonts[ i ]; if( FcCharSetHasChar( font->charset, c ) ) { if( !font->face ) { FcChar8 *filename; FcMatrix *matrix = NULL; FT_Matrix ft_matrix; int index = 0, hintstyle = FC_HINT_MEDIUM; double size; FcBool hinting = TRUE, autohint = FALSE, globaladvance = TRUE, embeddedbitmap = TRUE; if( FcPatternGetString( font->pattern, FC_FILE, 0, &filename ) || FcPatternGetDouble( font->pattern, FC_PIXEL_SIZE, 0, &size ) ) continue; FcPatternGetInteger( font->pattern, FC_INDEX, 0, &index ); FcPatternGetMatrix( font->pattern, FC_MATRIX, 0, &matrix ); if( FT_New_Face( ftl, (char *) filename, index, &font->face ) ) continue; FT_Set_Pixel_Sizes( font->face, 0, (int) ( size + 0.5 ) ); if( matrix ) { ft_matrix.xx = matrix->xx * 64.0; ft_matrix.xy = matrix->xy * 64.0; ft_matrix.yx = matrix->yx * 64.0; ft_matrix.yy = matrix->yy * 64.0; FT_Set_Transform( font->face, &ft_matrix, NULL ); } FT_Select_Charmap( font->face, FT_ENCODING_UNICODE ); FcPatternGetBool( font->pattern, FC_HINTING, 0, &hinting ); FcPatternGetInteger( font->pattern, FC_HINT_STYLE, 0, &hintstyle ); FcPatternGetBool( font->pattern, FC_AUTOHINT, 0, &autohint ); FcPatternGetBool( font->pattern, FC_GLOBAL_ADVANCE, 0, &globaladvance ); FcPatternGetBool( font->pattern, FC_EMBEDDED_BITMAP, 0, &embeddedbitmap ); font->load_flags = FT_LOAD_DEFAULT; if( !hinting ) font->load_flags |= FT_LOAD_NO_HINTING; switch( hintstyle ) { case FC_HINT_NONE: font->load_flags |= FT_LOAD_NO_HINTING; break; case FC_HINT_SLIGHT: font->load_flags |= FT_LOAD_TARGET_LIGHT; break; case FC_HINT_MEDIUM: default: font->load_flags |= FT_LOAD_TARGET_NORMAL; break; case FC_HINT_FULL: font->load_flags |= FT_LOAD_TARGET_MONO; break; } if( autohint ) font->load_flags |= FT_LOAD_FORCE_AUTOHINT; if( !globaladvance ) font->load_flags |= FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH; if( !embeddedbitmap ) font->load_flags |= FT_LOAD_NO_BITMAP; } return font; } } return NULL; } static void query_metrics( enum style_id style, uint32_t c, int *x_off, int *y_off ) { int row = c & ( METRIC_CACHE_SIZE - 1 ); int i, max; struct font *font; for( i = 0; i < METRIC_CACHE_ASSOC; i++ ) if( metric_cache[ row ][ i ].style == style && 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; } font = lookup_font( style, c ); /* Cache miss. */ if( !font || FT_Load_Char( font->face, c, font->load_flags ) ) { /* Couldn't load metrics. Don't bother evicting anything. */ *x_off = *y_off = 0; return; } /* Search for a line to evict. */ for( i = 1, 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 ].style = style; metric_cache[ row ][ max ].c = c; metric_cache[ row ][ max ].time = current_time; *x_off = metric_cache[ row ][ max ].x_off = ( font->face->glyph->advance.x + 0x20 ) >> 6; *y_off = metric_cache[ row ][ max ].y_off = ( font->face->glyph->advance.y + 0x20 ) >> 6; } static int query_glyph( enum style_id style, 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 ].style == style && 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( enum style_id style, uint32_t c ) { int row = c & ( GLYPH_CACHE_SIZE - 1 ); int i, max; /* Search for a line to evict. */ for( i = 1, max = 0; i < METRIC_CACHE_ASSOC; i++ ) { assert( glyph_cache[ row ][ i ].style != style || 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 ].style = style; glyph_cache[ row ][ max ].c = c; glyph_cache[ row ][ max ].time = current_time; return ( row << GLYPH_CACHE_ASSOC_BITS ) | max; } static enum xcb_image_order_t image_byte_order; static xcb_render_pictformat_t fmt_a8; static xcb_render_directformat_t dfmt_a8; static xcb_render_glyphset_t glyphset; static struct render_screen { xcb_render_pictformat_t root_pictformat, direct_pictformat; int direct_depth; xcb_render_directformat_t dfmt; xcb_format_t *pfmt; int rbits, gbits, bbits, abits; xcb_render_picture_t pic; enum decoration_col col; } *render_screens; static xcb_void_cookie_t render_text( xcb_drawable_t drawable, int screen, int col, int x, int y, const char *text, enum style_id style, 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 || !( len = utf8_length( (const unsigned char *) text ) ) ) { xcb_void_cookie_t r = { 0 }; return r; } buf = alloca( len * sizeof *buf ); for( i = 0, p = (const unsigned char *) text; i < len; i++ ) buf[ i ] = utf8_next( &p ); src = render_screens[ screen ].pic; dest = xcb_generate_id( c ); xcb_render_create_picture( c, dest, drawable, render_screens[ screen ].root_pictformat, 0, NULL ); if( clip ) xcb_render_set_picture_clip_rectangles( c, dest, 0, 0, 1, clip ); if( render_screens[ 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 ); render_screens[ 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( style, buf[ i ] ) ) < 0 ) { /* Cache miss. */ int bitmap_size; struct font *font; FT_GlyphSlot slot; if( ( index = replace_glyph( style, buf[ i ] ) ) < 0 ) /* Cache set full: spill the partial string to make room for later glyphs. */ break; if( !( font = lookup_font( style, buf[ i ] ) ) || FT_Load_Char( font->face, buf[ i ], font->load_flags | FT_LOAD_RENDER ) || ( bitmap_size = ( ( font->face->glyph->bitmap.width + 3 ) & ~3 ) * font->face->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 = font->face->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 ) { FT_Bitmap bitmap; FT_Bitmap_New( &bitmap ); FT_Bitmap_Convert( ftl, &slot->bitmap, &bitmap, 4 ); glyphs[ num_glyphs ].width = bitmap.width; glyphs[ num_glyphs ].height = 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; memcpy( p, bitmap.buffer, bitmap.pitch * bitmap.rows ); if( bitmap.num_grays != 0x100 ) { unsigned char *c, *end = p + bitmap.pitch * bitmap.rows; for( c = p; c < end; c++ ) *c = ( (unsigned int) *c * 0xFF / ( bitmap.num_grays - 1 ) ); } p += bitmap.pitch * bitmap.rows; FT_Bitmap_Done( ftl, &bitmap ); } 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( style, 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 ); } static xcb_void_cookie_t render_picture( xcb_drawable_t drawable, int screen, int sx, int sy, int width, int height, int dx, int dy, xcb_render_picture_t src, const xcb_rectangle_t *clip ) { xcb_render_picture_t dest; dest = xcb_generate_id( c ); xcb_render_create_picture( c, dest, drawable, render_screens[ screen ].root_pictformat, 0, NULL ); if( clip ) xcb_render_set_picture_clip_rectangles( c, dest, 0, 0, 1, clip ); xcb_render_composite( c, XCB_RENDER_PICT_OP_OVER, src, XCB_NONE, dest, sx, sy, 0, 0, dx, dy, width, height ); return xcb_render_free_picture( c, dest ); } static int text_width( enum style_id style, const char *text ) { const unsigned char *p = (const unsigned char *) text; int width = 0; while( *p ) { int dx, dy; query_metrics( style, utf8_next( &p ), &dx, &dy ); width += dx; } return width; } extern void render_update_window( struct gwm_window *window ) { if( window->type == WINDOW_CHILDLESS ) return; 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, button_size( window->u.frame.button, TRUE ) + 4, TITLE_FONT_SIZE, name ? name : "(Untitled)", STYLE_TITLE, &window->update ); } else if( window->type == WINDOW_MENUITEM ) { struct gwm_window *menu = window->u.menuitem.menu; int x = MENU_X_PAD; if( window->u.menuitem.menu->u.menu.has_icons ) { struct gwm_window *icon; if( window->u.menuitem.icon && ( icon = lookup_window( window->u.menuitem.icon ) ) && icon->type == WINDOW_MANAGED && icon->u.managed.menu_icons ) render_picture( window->w, window->screen, 0, 0, MENU_FONT_SIZE, MENU_FONT_SIZE, x, MENU_Y_PAD, icon->u.managed.menu_icons[ window->screen ], &window->update ); x += MENU_FONT_SIZE + MENU_X_PAD; } render_text( window->w, window->screen, menu->u.menu.active_item >= 0 && menu->u.menu.items[ menu->u.menu.active_item ] == window ? COL_MENU_ACTIVE_FORE : COL_MENU_INACTIVE_FORE, x, MENU_FONT_SIZE, window->u.menuitem.label, STYLE_MENU, &window->update ); } else if( window->type == WINDOW_FEEDBACK ) { char text[ 32 ]; sprintf( text, "%dx%d", window->u.feedback.fb_width, window->u.feedback.fb_height ); render_text( window->w, window->screen, COL_FEEDBACK_FORE, ( FEEDBACK_WIDTH - text_width( STYLE_TITLE, text ) ) >> 1, 16, text, STYLE_TITLE, &window->update ); } } extern void render_window_size( struct gwm_window *window, int *width, int *height ) { switch( window->type ) { case WINDOW_MENUITEM: if( window->u.menuitem.label ) { *width = text_width( STYLE_MENU, window->u.menuitem.label ) + ( MENU_X_PAD << 1 ); *height = MENU_FONT_SIZE + ( MENU_Y_PAD << 1 ); if( window->u.menuitem.menu->u.menu.has_icons ) *width += MENU_FONT_SIZE + MENU_X_PAD; } else { *width = MENU_X_PAD << 1; *height = MENU_Y_PAD << 1; } return; case WINDOW_FEEDBACK: *width = FEEDBACK_WIDTH; *height = FEEDBACK_HEIGHT; return; default: assert( FALSE ); } } static MALLOC uint8_t *assemble_image( int screen, int width, int height, uint32_t *icon, int *len ) { uint8_t *p, *linep, *out; int x, y; int bpp; /* bits per pixel */ int line; /* bytes per scanline */ bpp = render_screens[ screen ].pfmt->bits_per_pixel; line = ( ( bpp * width + render_screens[ screen ].pfmt->scanline_pad - 1 ) & ~( render_screens[ screen ].pfmt->scanline_pad - 1 ) ) >> 3; *len = line * height; linep = out = xmalloc( *len ); for( y = 0; y < height; y++ ) { p = linep; for( x = 0; x < width; x++ ) { uint32_t a = ( *icon >> 24 ) & 0xFF; uint32_t r = ( *icon >> 16 ) & 0xFF; uint32_t g = ( *icon >> 8 ) & 0xFF; uint32_t b = *icon & 0xFF; uint32_t pix; int i; if( gwm_screens[ screen ].root_visual->_class == XCB_VISUAL_CLASS_STATIC_GRAY || gwm_screens[ screen ].root_visual->_class == XCB_VISUAL_CLASS_GRAY_SCALE ) r = g = b = ( r * 0x4C8BU + g * 0x9646U + b * 0x1D2FU ) >> 16; /* Premultiply alpha, and normalise to 32 bits. */ r = ( (unsigned int) r * a ) * ( 0x01010101 / 0xFF ); g = ( (unsigned int) g * a ) * ( 0x01010101 / 0xFF ); b = ( (unsigned int) b * a ) * ( 0x01010101 / 0xFF ); a *= 0x01010101; pix = ( ( r >> ( 32 - render_screens[ screen ].rbits ) ) & render_screens[ screen ].dfmt.red_mask ) << render_screens[ screen ].dfmt.red_shift; pix |= ( ( g >> ( 32 - render_screens[ screen ].gbits ) ) & render_screens[ screen ].dfmt.green_mask ) << render_screens[ screen ].dfmt.green_shift; pix |= ( ( b >> ( 32 - render_screens[ screen ].bbits ) ) & render_screens[ screen ].dfmt.blue_mask ) << render_screens[ screen ].dfmt.blue_shift; pix |= ( ( a >> ( 32 - render_screens[ screen ].abits ) ) & render_screens[ screen ].dfmt.alpha_mask ) << render_screens[ screen ].dfmt.alpha_shift; if( image_byte_order == XCB_IMAGE_ORDER_LSB_FIRST ) for( i = bpp; i; i -= 8 ) { *p++ = ( pix & 0xFF ); pix >>= 8; } else for( i = bpp; i; i -= 8 ) *p++ = ( ( pix >> ( i - 8 ) ) & 0xFF ); icon++; } linep += line; } return out; } extern void render_replace_icons( struct gwm_window *window, int num_icons, int *widths, int *heights, uint32_t **icons, xcb_pixmap_t *bitmaps ) { int i; if( window->u.managed.net_wm_icon && bitmaps ) /* Use old _NET_WM_ICON in preference to new WM_HINTS. */ return; if( window->u.managed.full_icons ) { for( i = 0; i < num_screens; i++ ) { xcb_render_free_picture( c, window->u.managed.full_icons[ i ] ); xcb_render_free_picture( c, window->u.managed.menu_icons[ i ] ); } free( window->u.managed.full_icons ); free( window->u.managed.menu_icons ); } if( num_icons ) { int i, len, best_full, full_size, best_menu, menu_size; xcb_pixmap_t full_map, menu_map, temp_map; xcb_render_picture_t temp; xcb_render_transform_t t; xcb_gcontext_t gc; uint8_t *image; best_full = best_menu = 0; if( widths[ 0 ] > heights[ 0 ] ) full_size = menu_size = widths[ 0 ]; else full_size = menu_size = heights[ 0 ]; for( i = 1; i < num_icons; i++ ) { int s = widths[ i ] > heights[ i ] ? widths[ i ] : heights[ i ]; if( ( full_size < ICON_SIZE && s > full_size ) || ( full_size > ICON_SIZE && s >= ICON_SIZE && s < full_size ) ) best_full = i, full_size = s; if( ( menu_size < MENU_FONT_SIZE && s > menu_size ) || ( menu_size > MENU_FONT_SIZE && s >= MENU_FONT_SIZE && s < menu_size ) ) best_menu = i, menu_size = s; } full_map = xcb_generate_id( c ); menu_map = xcb_generate_id( c ); temp_map = best_menu == best_full ? full_map : xcb_generate_id( c ); gc = xcb_generate_id( c ); window->u.managed.full_icons = xmalloc( num_screens * sizeof *window->u.managed.full_icons ); window->u.managed.menu_icons = xmalloc( num_screens * sizeof *window->u.managed.menu_icons ); for( i = 0; i < num_screens; i++ ) { xcb_create_pixmap( c, render_screens[ i ].direct_depth, full_map, screens[ i ]->root, widths[ best_full ], heights[ best_full ] ); if( temp_map != full_map ) xcb_create_pixmap( c, render_screens[ i ].direct_depth, temp_map, screens[ i ]->root, widths[ best_menu ], heights[ best_menu ] ); xcb_create_pixmap( c, render_screens[ i ].direct_depth, menu_map, screens[ i ]->root, MENU_FONT_SIZE, MENU_FONT_SIZE ); if( icons ) { xcb_create_gc( c, gc, full_map, 0, NULL ); image = assemble_image( i, widths[ best_full ], heights[ best_full ], icons[ best_full ], &len ); put_image( full_map, gc, widths[ best_full ], heights[ best_full ], 0, 0, 0, render_screens[ i ].direct_depth, len, image ); free( image ); if( temp_map != full_map ) { image = assemble_image( i, widths[ best_menu ], heights[ best_menu ], icons[ best_menu ], &len ); put_image( temp_map, gc, widths[ best_menu ], heights[ best_menu ], 0, 0, 0, render_screens[ i ].direct_depth, len, image ); free( image ); } } else { uint32_t values[ 3 ]; values[ 0 ] = render_screens[ i ].dfmt.alpha_mask << render_screens[ i ].dfmt.alpha_shift; values[ 1 ] = ( render_screens[ i ].dfmt.alpha_mask << render_screens[ i ].dfmt.alpha_shift ) | ( render_screens[ i ].dfmt.red_mask << render_screens[ i ].dfmt.red_shift ) | ( render_screens[ i ].dfmt.green_mask << render_screens[ i ].dfmt.green_shift ) | ( render_screens[ i ].dfmt.blue_mask << render_screens[ i ].dfmt.blue_shift ); xcb_create_gc( c, gc, full_map, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values ); xcb_copy_plane( c, bitmaps[ best_full ], full_map, gc, 0, 0, 0, 0, widths[ best_full ], heights[ best_full ], 1 ); if( bitmaps[ 1 ] ) { /* We also have a mask bitmap: apply logical AND to turn the appropriate pixels transparent. */ values[ 0 ] = XCB_GX_AND; /* function */ values[ 1 ] = 0xFFFFFFFF; /* foreground */ values[ 2 ] = 0; /* background */ xcb_change_gc( c, gc, XCB_GC_FUNCTION | XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values ); xcb_copy_plane( c, bitmaps[ 1 ], full_map, gc, 0, 0, 0, 0, widths[ best_full ], heights[ best_full ], 1 ); } } window->u.managed.full_icons[ i ] = xcb_generate_id( c ); window->u.managed.menu_icons[ i ] = xcb_generate_id( c ); xcb_render_create_picture( c, window->u.managed.full_icons[ i ], full_map, render_screens[ i ].direct_pictformat, 0, NULL ); xcb_render_create_picture( c, window->u.managed.menu_icons[ i ], menu_map, render_screens[ i ].direct_pictformat, 0, NULL ); if( temp_map == full_map ) temp = window->u.managed.full_icons[ i ]; else { temp = xcb_generate_id( c ); xcb_render_create_picture( c, temp, temp_map, render_screens[ i ].direct_pictformat, 0, NULL ); } xcb_render_set_picture_filter( c, temp, 4, "best", 0, NULL ); t.matrix11 = 0x10000 * menu_size / MENU_FONT_SIZE; t.matrix12 = 0; t.matrix13 = 0x80000; t.matrix21 = 0; t.matrix22 = t.matrix11; t.matrix23 = 0; t.matrix31 = 0; t.matrix32 = 0; t.matrix33 = 0x10000; if( widths[ best_menu ] < heights[ best_menu ] ) { t.matrix12 = 0; t.matrix13 = ( widths[ best_menu ] - heights[ best_menu ] ) << 15; } else { t.matrix12 = ( heights[ best_menu ] - widths[ best_menu ] ) << 15; t.matrix13 = 0; } xcb_render_set_picture_transform( c, temp, t ); xcb_render_composite( c, XCB_RENDER_PICT_OP_SRC, temp, XCB_NONE, window->u.managed.menu_icons[ i ], 0, 0, 0, 0, 0, 0, MENU_FONT_SIZE, MENU_FONT_SIZE ); xcb_render_set_picture_filter( c, window->u.managed.full_icons[ i ], 4, "good", 0, NULL ); t.matrix11 = t.matrix22 = 0x10000 * full_size / ICON_SIZE; if( widths[ best_full ] < heights[ best_full ] ) { t.matrix12 = 0; t.matrix13 = ( widths[ best_full ] - heights[ best_full ] ) << 15; } else { t.matrix12 = ( heights[ best_full ] - widths[ best_full ] ) << 15; t.matrix13 = 0; } xcb_render_set_picture_transform( c, window->u.managed.full_icons[ i ], t ); xcb_free_pixmap( c, full_map ); xcb_free_pixmap( c, menu_map ); if( temp_map != full_map ) { xcb_free_pixmap( c, temp_map ); xcb_render_free_picture( c, temp ); } xcb_free_gc( c, gc ); } window->u.managed.net_wm_icon = icons != NULL; } else { if( window->u.managed.net_wm_icon ) /* We've lost the _NET_WM_ICON property. If the window still has a plain WM_HINTS icon, then fall back to that. */ async_get_property( window, PROP_WM_HINTS ); window->u.managed.full_icons = window->u.managed.menu_icons = NULL; window->u.managed.net_wm_icon = FALSE; } } 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 = ( prop->value_len << 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 INIT void decorate_compat_init( void ) { int i; #if USE_CURSOR xcb_cursor_context_t *cc; static INITD const char *const cursor_names[ NUM_CURSORS ] = { "top_left_corner", /* CURSOR_TL */ "top_side", /* CURSOR_T */ "top_right_corner", /* CURSOR_TR */ "left_side", /* CURSOR_L */ "fleur", /* CURSOR_C */ "right_side", /* CURSOR_R */ "bottom_left_corner", /* CURSOR_BL */ "bottom_side", /* CURSOR_B */ "bottom_right_corner", /* CURSOR_BR */ "left_ptr", /* CURSOR_ARROW */ "pirate" /* CURSOR_DESTROY */ }; xcb_cursor_context_new( c, screens[ 0 ], &cc ); for( i = 0; i < NUM_CURSORS; i++ ) cursors[ i ] = xcb_cursor_load_cursor( cc, cursor_names[ i ] ); xcb_cursor_context_free( cc ); #else const char *cursor_font_name = "cursor"; xcb_font_t cursor_font; static INITD const int cursor_glyphs[ NUM_CURSORS ] = { 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 */ 68, /* CURSOR_ARROW */ 88 /* CURSOR_DESTROY */ }; 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 ); #endif /* 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 INIT int decorate_render_init( void ) { 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; xcb_render_pictscreen_iterator_t siter; FT_Error err; FcFontSet *sets[ NUM_STYLES ]; FcPattern *style_pats[ NUM_STYLES ]; FcChar32 *hashes; FcResult r; int i, total; xcb_pixmap_t pixmap; uint32_t n; uint32_t icon_sizes[ 6 ]; image_byte_order = xcb_get_setup( c )->image_byte_order; 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 ) return -1; if( !render_reply->major_version && render_reply->minor_version < 1 ) { /* We need at least version 0.6, for SetPictureTransform support. */ free( render_reply ); return -1; } free( render_reply ); formats_reply = xcb_render_query_pict_formats_reply( c, formats_cookie, NULL ); formats = xcb_render_query_pict_formats_formats( formats_reply ); /* First of all, look for an 8 bit direct alpha PictFormat. */ for( i = 0; i < formats_reply->num_formats; i++ ) if( formats[ i ].type == XCB_RENDER_PICT_TYPE_DIRECT && formats[ i ].direct.alpha_mask == 0xFF && !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 ); break; } if( !fmt_a8 ) { /* The A8 PictFormat is guaranteed by the RENDER protocol (section 7). We can't cope without it, since that's what our glyphset uses. */ free( formats_reply ); return -1; } render_screens = xmalloc( num_screens * sizeof *render_screens ); /* Next, look for a PictFormat matching each root visual. */ 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; render_screens[ i ].root_pictformat = 0; 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 ) { render_screens[ i ].root_pictformat = viter.data->format; goto next_screen; } } /* Oh dear. We found a screen with no PictFormat corresponding to its root visual, so we can't render to it. It's much too messy to try rendering on some screens and core graphics on others, so give up rendering entirely. */ free( formats_reply ); free( render_screens ); return -1; next_screen: ; } /* Next, look for the direct PictFormat most suitable for source pictures rendering to each root visual. */ 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; int best_score = -1; int best_format; int red_mask, green_mask, blue_mask; if( gwm_screens[ i ].root_visual->_class == XCB_VISUAL_CLASS_TRUE_COLOR || gwm_screens[ i ].root_visual->_class == XCB_VISUAL_CLASS_DIRECT_COLOR ) { for( red_mask = gwm_screens[ i ].root_visual->red_mask; !( red_mask & 1 ); red_mask >>= 1 ) ; for( green_mask = gwm_screens[ i ].root_visual->green_mask; !( green_mask & 1 ); green_mask >>= 1 ) ; for( blue_mask = gwm_screens[ i ].root_visual->blue_mask; !( blue_mask & 1 ); blue_mask >>= 1 ) ; } else red_mask = green_mask = blue_mask = -1; for( diter = xcb_render_pictscreen_depths_iterator( siter.data ); diter.rem; xcb_render_pictdepth_next( &diter ) ) { xcb_depth_iterator_t pixmap_depths; xcb_render_pictvisual_iterator_t viter; int format; for( pixmap_depths = xcb_screen_allowed_depths_iterator( screens[ i ] ); pixmap_depths.rem; xcb_depth_next( &pixmap_depths ) ) if( pixmap_depths.data->depth == diter.data->depth ) break; if( !pixmap_depths.rem ) /* We're not allowed to allocate pixmaps of this depth: don't bother with this PictFormat. */ continue; for( viter = xcb_render_pictdepth_visuals_iterator( diter.data ); viter.rem; xcb_render_pictvisual_next( &viter ) ) { int score; for( format = 0; format < formats_reply->num_formats; format++ ) if( formats[ format ].id == viter.data->format && formats[ format ].type == XCB_RENDER_PICT_TYPE_DIRECT && formats[ format ].depth >= 8 ) /* Insisting that the depth is at least 8 is somewhat arbitary, but since it's less work for us if we don't have to consider assembling non-byte-aligned images, and it's highly questionable whether alpha composition is particularly effective on 1- and 4-bit depths, we won't even try. */ break; if( format == formats_reply->num_formats ) continue; /* Aha! We've found a direct PictFormat that we can allocate pixmaps for. Decide how good it is... */ score = 0; if( formats[ format ].direct.alpha_mask ) /* Has an alpha component. Excellent. */ score |= 0x80; if( formats[ format ].direct.red_mask == red_mask && formats[ format ].direct.green_mask == green_mask && formats[ format ].direct.blue_mask == blue_mask ) { /* The RGB component depths match the root. Good. */ score |= 0x40; if( formats[ format ].direct.red_mask << formats[ format ].direct.red_shift == gwm_screens[ i ].root_visual->red_mask && formats[ format ].direct.green_mask << formats[ format ].direct.green_shift == gwm_screens[ i ].root_visual->green_mask && formats[ format ].direct.blue_mask << formats[ format ].direct.blue_shift == gwm_screens[ i ].root_visual->blue_mask ) /* The RGB layouts match. Useful. */ score |= 0x20; } if( formats[ format ].depth == screens[ i ]->root_depth ) /* The depth matches. Slightly helpful. */ score |= 0x10; /* All else being equal, prefer deeper alpha channels. */ if( formats[ format ].direct.alpha_mask >= 0xFFFF ) score |= 0x0F; else { int n; for( n = formats[ format ].direct.alpha_mask >> 1; n; n >>= 1 ) score++; } if( score > best_score ) { best_score = score; best_format = format; } } } if( best_score >= 0 ) { xcb_format_iterator_t fiter; int mask; render_screens[ i ].direct_pictformat = formats[ best_format ].id; render_screens[ i ].direct_depth = formats[ best_format ].depth; memcpy( &render_screens[ i ].dfmt, &formats[ best_format ].direct, sizeof render_screens[ i ].dfmt ); for( fiter = xcb_setup_pixmap_formats_iterator( xcb_get_setup( c ) ); fiter.rem; xcb_format_next( &fiter ) ) if( fiter.data->depth == formats[ best_format ].depth ) { render_screens[ i ].pfmt = fiter.data; break; } assert( fiter.rem ); for( render_screens[ i ].rbits = 0, mask = render_screens[ i ].dfmt.red_mask; mask; mask >>= 1, render_screens[ i ].rbits++ ) ; for( render_screens[ i ].gbits = 0, mask = render_screens[ i ].dfmt.green_mask; mask; mask >>= 1, render_screens[ i ].gbits++ ) ; for( render_screens[ i ].bbits = 0, mask = render_screens[ i ].dfmt.blue_mask; mask; mask >>= 1, render_screens[ i ].bbits++ ) ; for( render_screens[ i ].abits = 0, mask = render_screens[ i ].dfmt.alpha_mask; mask; mask >>= 1, render_screens[ i ].abits++ ) ; } else { /* Oh dear. We couldn't find any suitable direct PictFormat to use as a source rendering to the root visual. */ free( formats_reply ); free( render_screens ); return -1; } } free( formats_reply ); if( !FcInit() ) { warning( "font configuration error" ); free( render_screens ); return -1; } if( ( err = FT_Init_FreeType( &ftl ) ) ) { warning( "typeface initialisation error %d", err ); free( render_screens ); FcFini(); return -1; } for( i = 0, total = 0; i < NUM_STYLES; i++ ) { if( !( style_pats[ i ] = FcNameParse( style_names[ i ] ) ) ) { warning( "could not parse font \"%s\"\n", style_names[ i ] ); free( render_screens ); FcFini(); FT_Done_FreeType( ftl ); return -1; } FcConfigSubstitute( NULL, style_pats[ i ], FcMatchPattern ); FcDefaultSubstitute( style_pats[ i ] ); sets[ i ] = FcFontSort( NULL, style_pats[ i ], TRUE, &styles[ i ].charset, &r ); total += sets[ i ]->nfont; } fonts = xmalloc( total * sizeof *fonts ); hashes = alloca( total * sizeof *hashes ); for( i = 0; i < NUM_STYLES; i++ ) { int j; styles[ i ].fonts = xmalloc( sets[ i ]->nfont * sizeof (struct font *) ); for( j = 0; j < sets[ i ]->nfont; j++ ) { FcPattern *pat; if( ( pat = FcFontRenderPrepare( NULL, style_pats[ i ], sets[ i ]->fonts[ j ] ) ) ) { FcChar32 hash = FcPatternHash( pat ); int search; for( search = 0; search < num_fonts; search++ ) if( hashes[ search ] == hash && FcPatternEqual( fonts[ search ].pattern, pat ) ) { FcPatternDestroy( pat ); break; } if( search == num_fonts ) { hashes[ num_fonts ] = hash; fonts[ num_fonts ].pattern = pat; FcPatternGetCharSet( pat, FC_CHARSET, 0, &fonts[ num_fonts ].charset ); fonts[ num_fonts ].face = NULL; num_fonts++; } styles[ i ].fonts[ styles[ i ].num_fonts ] = search; styles[ i ].num_fonts++; } } styles[ i ].fonts = xrealloc( styles[ i ].fonts, styles[ i ].num_fonts * sizeof (struct font *) ); FcFontSetDestroy( sets[ i ] ); FcPatternDestroy( style_pats[ i ] ); } fonts = xrealloc( fonts, num_fonts * sizeof *fonts ); glyphset = xcb_generate_id( c ); xcb_render_create_glyph_set( c, glyphset, fmt_a8 ); pixmap = xcb_generate_id( c ); n = XCB_RENDER_REPEAT_NORMAL; /* A WM_ICON_SIZE property (see ICCCM 2.0, section 4.1.3.2). */ icon_sizes[ 0 ] = MENU_FONT_SIZE; /* min_width */ icon_sizes[ 1 ] = MENU_FONT_SIZE; /* min_height */ icon_sizes[ 2 ] = ICON_SIZE; /* max_width */ icon_sizes[ 3 ] = ICON_SIZE; /* max_height */ icon_sizes[ 4 ] = 1; /* width_inc */ icon_sizes[ 5 ] = 1; /* height_inc */ for( i = 0; i < num_screens; i++ ) { xcb_create_pixmap( c, screens[ i ]->root_depth, pixmap, screens[ i ]->root, 1, 1 ); render_screens[ i ].pic = xcb_generate_id( c ); xcb_render_create_picture( c, render_screens[ i ].pic, pixmap, render_screens[ i ].root_pictformat, XCB_RENDER_CP_REPEAT, &n ); render_screens[ i ].col = -1; xcb_free_pixmap( c, pixmap ); xcb_change_property( c, XCB_PROP_MODE_REPLACE, screens[ i ]->root, WM_ICON_SIZE, WM_ICON_SIZE, 32, 6, icon_sizes ); } decorate_compat_init(); /* FIXME this is for pixel values (backgrounds) and cursors */ return 0; } extern void decorate_render_done( void ) { #if DEBUG int i; for( i = 0; i < NUM_STYLES; i++ ) { FcCharSetDestroy( styles[ i ].charset ); free( styles[ i ].fonts ); } for( i = 0; i < num_fonts; i++ ) { FcPatternDestroy( fonts[ i ].pattern ); FT_Done_Face( fonts[ i ].face ); } free( fonts ); FcFini(); FT_Done_FreeType( ftl ); free( render_screens ); #endif }