i3/libi3/draw_util.c
Ingo Bürk fdeb4e0c36 Skip drawing for uninitialized surfaces.
We return early from drawing functions if the surface to draw to is not
initialized properly. There is no immediate need to do so, at least no
crashes have been observed, but it mirrors the previous behavior a bit
more closely. Furthermore, i3 should not crash due to not being able to
make some rendering call, so this provides some stability.

relates to #1278
2015-11-23 22:18:02 +01:00

246 lines
8.2 KiB
C

/*
* vim:ts=4:sw=4:expandtab
*
* © 2015 Ingo Bürk and contributors (see also: LICENSE)
*
* draw.c: Utility for drawing.
*
*/
#include <stdlib.h>
#include <err.h>
#include <string.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#ifdef CAIRO_SUPPORT
#include <cairo/cairo-xcb.h>
#endif
#include "libi3.h"
/* The default visual_type to use if none is specified when creating the surface. Must be defined globally. */
xcb_visualtype_t *visual_type;
/* Forward declarations */
static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surface, color_t color);
#define RETURN_UNLESS_SURFACE_INITIALIZED(surface) \
do { \
if ((surface)->id == XCB_NONE) { \
ELOG("Surface %p is not initialized, skipping drawing.\n", surface); \
return; \
} \
} while (0)
/*
* Initialize the surface to represent the given drawable.
*
*/
void draw_util_surface_init(xcb_connection_t *conn, surface_t *surface, xcb_drawable_t drawable,
xcb_visualtype_t *visual, int width, int height) {
surface->id = drawable;
surface->visual_type = (visual == NULL) ? visual_type : visual;
surface->width = width;
surface->height = height;
surface->gc = xcb_generate_id(conn);
xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(conn, surface->gc, surface->id, 0, NULL);
xcb_generic_error_t *error = xcb_request_check(conn, gc_cookie);
if (error != NULL) {
ELOG("Could not create graphical context. Error code: %d\n", error->error_code);
exit(EXIT_FAILURE);
}
#ifdef CAIRO_SUPPORT
surface->surface = cairo_xcb_surface_create(conn, surface->id, surface->visual_type, width, height);
surface->cr = cairo_create(surface->surface);
#endif
}
/*
* Destroys the surface.
*
*/
void draw_util_surface_free(xcb_connection_t *conn, surface_t *surface) {
xcb_free_gc(conn, surface->gc);
#ifdef CAIRO_SUPPORT
cairo_surface_destroy(surface->surface);
cairo_destroy(surface->cr);
#endif
}
/*
* Resize the surface to the given size.
*
*/
void draw_util_surface_set_size(surface_t *surface, int width, int height) {
surface->width = width;
surface->height = height;
#ifdef CAIRO_SUPPORT
cairo_xcb_surface_set_size(surface->surface, width, height);
#endif
}
/*
* Parses the given color in hex format to an internal color representation.
* Note that the input must begin with a hash sign, e.g., "#3fbc59".
*
*/
color_t draw_util_hex_to_color(const char *color) {
char groups[3][3] = {
{color[1], color[2], '\0'},
{color[3], color[4], '\0'},
{color[5], color[6], '\0'}};
return (color_t){
.red = strtol(groups[0], NULL, 16) / 255.0,
.green = strtol(groups[1], NULL, 16) / 255.0,
.blue = strtol(groups[2], NULL, 16) / 255.0,
.colorpixel = get_colorpixel(color)};
}
color_t draw_util_colorpixel_to_color(uint32_t colorpixel) {
return (color_t){
.red = ((colorpixel >> 16) & 0xFF) / 255.0,
.green = ((colorpixel >> 8) & 0xFF) / 255.0,
.blue = (colorpixel & 0xFF) / 255.0,
.colorpixel = colorpixel};
}
/*
* Set the given color as the source color on the surface.
*
*/
static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surface, color_t color) {
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
#ifdef CAIRO_SUPPORT
cairo_set_source_rgb(surface->cr, color.red, color.green, color.blue);
#else
uint32_t colorpixel = color.colorpixel;
xcb_change_gc(conn, surface->gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND,
(uint32_t[]){colorpixel, colorpixel});
#endif
}
/**
* Draw the given text using libi3.
* This function also marks the surface dirty which is needed if other means of
* drawing are used. This will be the case when using XCB to draw text.
*
*/
void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width) {
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
#ifdef CAIRO_SUPPORT
/* Flush any changes before we draw the text as this might use XCB directly. */
CAIRO_SURFACE_FLUSH(surface->surface);
#endif
set_font_colors(surface->gc, fg_color.colorpixel, bg_color.colorpixel);
draw_text(text, surface->id, surface->gc, surface->visual_type, x, y, max_width);
#ifdef CAIRO_SUPPORT
/* Notify cairo that we (possibly) used another way to draw on the surface. */
cairo_surface_mark_dirty(surface->surface);
#endif
}
/**
* Draws a filled rectangle.
* This function is a convenience wrapper and takes care of flushing the
* surface as well as restoring the cairo state.
*
*/
void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t color, double x, double y, double w, double h) {
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
#ifdef CAIRO_SUPPORT
cairo_save(surface->cr);
/* Using the SOURCE operator will copy both color and alpha information directly
* onto the surface rather than blending it. This is a bit more efficient and
* allows better color control for the user when using opacity. */
cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE);
draw_util_set_source_color(conn, surface, color);
cairo_rectangle(surface->cr, x, y, w, h);
cairo_fill(surface->cr);
/* Make sure we flush the surface for any text drawing operations that could follow.
* Since we support drawing text via XCB, we need this. */
CAIRO_SURFACE_FLUSH(surface->surface);
cairo_restore(surface->cr);
#else
draw_util_set_source_color(conn, surface, color);
xcb_rectangle_t rect = {x, y, w, h};
xcb_poly_fill_rectangle(conn, surface->id, surface->gc, 1, &rect);
#endif
}
/**
* Clears a surface with the given color.
*
*/
void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t color) {
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
#ifdef CAIRO_SUPPORT
cairo_save(surface->cr);
/* Using the SOURCE operator will copy both color and alpha information directly
* onto the surface rather than blending it. This is a bit more efficient and
* allows better color control for the user when using opacity. */
cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE);
draw_util_set_source_color(conn, surface, color);
cairo_paint(surface->cr);
/* Make sure we flush the surface for any text drawing operations that could follow.
* Since we support drawing text via XCB, we need this. */
CAIRO_SURFACE_FLUSH(surface->surface);
cairo_restore(surface->cr);
#else
draw_util_set_source_color(conn, surface, color);
xcb_rectangle_t rect = {0, 0, surface->width, surface->height};
xcb_poly_fill_rectangle(conn, surface->id, surface->gc, 1, &rect);
#endif
}
/**
* Copies a surface onto another surface.
*
*/
void draw_util_copy_surface(xcb_connection_t *conn, surface_t *src, surface_t *dest, double src_x, double src_y,
double dest_x, double dest_y, double width, double height) {
RETURN_UNLESS_SURFACE_INITIALIZED(src);
RETURN_UNLESS_SURFACE_INITIALIZED(dest);
#ifdef CAIRO_SUPPORT
cairo_save(dest->cr);
/* Using the SOURCE operator will copy both color and alpha information directly
* onto the surface rather than blending it. This is a bit more efficient and
* allows better color control for the user when using opacity. */
cairo_set_operator(dest->cr, CAIRO_OPERATOR_SOURCE);
cairo_set_source_surface(dest->cr, src->surface, dest_x - src_x, dest_y - src_y);
cairo_rectangle(dest->cr, dest_x, dest_y, width, height);
cairo_fill(dest->cr);
/* Make sure we flush the surface for any text drawing operations that could follow.
* Since we support drawing text via XCB, we need this. */
CAIRO_SURFACE_FLUSH(src->surface);
CAIRO_SURFACE_FLUSH(dest->surface);
cairo_restore(dest->cr);
#else
xcb_copy_area(conn, src->id, dest->id, dest->gc, (int16_t)src_x, (int16_t)src_y,
(int16_t)dest_x, (int16_t)dest_y, (uint16_t)width, (uint16_t)height);
#endif
}