#include #include #include #include "runes.h" static cairo_pattern_t *runes_display_get_fgcolor(RunesTerm *t); static cairo_pattern_t *runes_display_get_bgcolor(RunesTerm *t); static void runes_display_recalculate_font_metrics(RunesTerm *t); static void runes_display_position_cursor(RunesTerm *t, cairo_t *cr); static void runes_display_paint_rectangle( RunesTerm *t, cairo_t *cr, cairo_pattern_t *pattern, int x, int y, int width, int height); static void runes_display_scroll_up(RunesTerm *t, int rows); static void runes_display_scroll_down(RunesTerm *t, int rows); void runes_display_init(RunesTerm *t) { runes_display_recalculate_font_metrics(t); t->cursorcolor = cairo_pattern_create_rgba(0.0, 1.0, 0.0, 0.5); t->fgcolor = -1; t->bgcolor = -1; } void runes_display_set_window_size(RunesTerm *t) { int width, height; cairo_t *old_cr = NULL; cairo_surface_t *surface; runes_window_backend_get_size(t, &width, &height); if (width == t->xpixel && height == t->ypixel) { return; } t->xpixel = width; t->ypixel = height; t->rows = t->ypixel / t->fonty; t->cols = t->xpixel / t->fontx; t->scroll_top = 0; t->scroll_bottom = t->rows - 1; old_cr = t->cr; /* XXX this should really use cairo_surface_create_similar_image, but when * i did that, drawing calls would occasionally block until an X event * occurred for some reason. should look into this, because i think * create_similar_image does things that are more efficient (using some * xlib shm stuff) */ surface = cairo_image_surface_create( CAIRO_FORMAT_RGB24, t->xpixel, t->ypixel); t->cr = cairo_create(surface); cairo_surface_destroy(surface); cairo_set_source(t->cr, runes_display_get_fgcolor(t)); if (t->layout) { pango_cairo_update_layout(t->cr, t->layout); } else { PangoAttrList *attrs; PangoFontDescription *font_desc; attrs = pango_attr_list_new(); font_desc = pango_font_description_from_string(t->font_name); t->layout = pango_cairo_create_layout(t->cr); pango_layout_set_attributes(t->layout, attrs); pango_layout_set_font_description(t->layout, font_desc); pango_attr_list_unref(attrs); pango_font_description_free(font_desc); } cairo_save(t->cr); if (old_cr) { cairo_set_source_surface(t->cr, cairo_get_target(old_cr), 0.0, 0.0); } else { cairo_set_source(t->cr, t->bgdefault); } cairo_paint(t->cr); cairo_restore(t->cr); if (old_cr) { cairo_destroy(old_cr); } runes_pty_backend_set_window_size(t); runes_display_position_cursor(t, t->cr); } void runes_display_focus_in(RunesTerm *t) { t->unfocused = 0; } void runes_display_focus_out(RunesTerm *t) { t->unfocused = 1; } void runes_display_move_to(RunesTerm *t, int row, int col) { t->row = row; t->col = col; if (row > t->scroll_bottom) { runes_display_scroll_down(t, row - t->scroll_bottom); t->row = t->scroll_bottom; } else if (row < t->scroll_top) { runes_display_scroll_up(t, t->scroll_top - row); t->row = t->scroll_top; } runes_display_position_cursor(t, t->cr); } void runes_display_show_string_ascii(RunesTerm *t, char *buf, size_t len) { if (len) { int remaining = len, space_in_row = t->cols - t->col; do { int to_write = remaining > space_in_row ? space_in_row : remaining; runes_display_paint_rectangle( t, t->cr, runes_display_get_bgcolor(t), t->col, t->row, to_write, 1); pango_layout_set_text(t->layout, buf, to_write); pango_cairo_update_layout(t->cr, t->layout); pango_cairo_show_layout(t->cr, t->layout); buf += to_write; remaining -= to_write; space_in_row = t->cols; if (remaining) { runes_display_move_to(t, t->row + 1, 0); } else { t->col += len; runes_display_position_cursor(t, t->cr); } } while (remaining > 0); } } /* XXX this is gross and kind of slow, but i can't find a better way to do it. * i need to be able to convert the string into clusters before laying it out, * since i'm not going to be using the full layout anyway, but the only way i * can see to do that is with pango_get_log_attrs, which only returns character * (not byte) offsets, so i have to reparse the utf8 myself once i get the * results. not ideal. */ void runes_display_show_string_utf8(RunesTerm *t, char *buf, size_t len) { size_t i, pos, char_len; PangoLogAttr *attrs; char_len = g_utf8_strlen(buf, len); attrs = malloc(sizeof(PangoLogAttr) * (char_len + 1)); pango_get_log_attrs(buf, len, -1, NULL, attrs, char_len + 1); pos = 0; for (i = 1; i < char_len + 1; ++i) { if (attrs[i].is_cursor_position) { char *startpos, *c; size_t cluster_len; char width = 1; startpos = g_utf8_offset_to_pointer(buf, pos); cluster_len = g_utf8_offset_to_pointer(buf, i) - startpos; for (c = startpos; (size_t)(c - startpos) < cluster_len; c = g_utf8_next_char(c)) { if (g_unichar_iswide(g_utf8_get_char(c))) { width = 2; } } runes_display_paint_rectangle( t, t->cr, runes_display_get_bgcolor(t), t->col, t->row, width, 1); pango_layout_set_text(t->layout, startpos, cluster_len); pango_cairo_update_layout(t->cr, t->layout); pango_cairo_show_layout(t->cr, t->layout); if (t->col + width >= t->cols) { runes_display_move_to(t, t->row + 1, 0); } else { runes_display_move_to(t, t->row, t->col + width); } pos = i; } } free(attrs); } void runes_display_clear_screen(RunesTerm *t) { runes_display_paint_rectangle( t, t->cr, t->bgdefault, 0, 0, t->cols, t->rows); } void runes_display_clear_screen_forward(RunesTerm *t) { runes_display_kill_line_forward(t); runes_display_paint_rectangle( t, t->cr, t->bgdefault, 0, t->row, t->cols, t->rows - t->row); } void runes_display_kill_line_forward(RunesTerm *t) { runes_display_paint_rectangle( t, t->cr, t->bgdefault, t->col, t->row, t->cols - t->col, 1); } void runes_display_kill_line_backward(RunesTerm *t) { runes_display_paint_rectangle( t, t->cr, t->bgdefault, 0, t->row, t->col, 1); } void runes_display_insert_lines(RunesTerm *t, int count) { cairo_pattern_t *pattern; cairo_matrix_t matrix; cairo_save(t->cr); cairo_push_group(t->cr); pattern = cairo_pattern_create_for_surface(cairo_get_target(t->cr)); cairo_matrix_init_translate(&matrix, 0.0, -count * t->fonty); cairo_pattern_set_matrix(pattern, &matrix); runes_display_paint_rectangle( t, t->cr, pattern, 0, t->row + count, t->cols, t->scroll_bottom + 1 - t->row - count); cairo_pattern_destroy(pattern); cairo_pop_group_to_source(t->cr); cairo_paint(t->cr); runes_display_paint_rectangle( t, t->cr, t->bgdefault, 0, t->row, t->cols, count); cairo_restore(t->cr); } void runes_display_insert_characters(RunesTerm *t, int count) { cairo_pattern_t *pattern; cairo_matrix_t matrix; cairo_save(t->cr); cairo_push_group(t->cr); pattern = cairo_pattern_create_for_surface(cairo_get_target(t->cr)); cairo_matrix_init_translate(&matrix, -count * t->fontx, 0.0); cairo_pattern_set_matrix(pattern, &matrix); runes_display_paint_rectangle( t, t->cr, pattern, t->col + count, t->row, t->cols - t->col - count, 1); cairo_pattern_destroy(pattern); cairo_pop_group_to_source(t->cr); cairo_paint(t->cr); runes_display_paint_rectangle( t, t->cr, t->bgdefault, t->col, t->row, count, 1); cairo_restore(t->cr); } void runes_display_delete_lines(RunesTerm *t, int count) { cairo_pattern_t *pattern; cairo_matrix_t matrix; cairo_save(t->cr); cairo_push_group(t->cr); pattern = cairo_pattern_create_for_surface(cairo_get_target(t->cr)); cairo_matrix_init_translate(&matrix, 0.0, count * t->fonty); cairo_pattern_set_matrix(pattern, &matrix); runes_display_paint_rectangle( t, t->cr, pattern, 0, t->row, t->cols, t->scroll_bottom + 1 - t->row - count); cairo_pattern_destroy(pattern); cairo_pop_group_to_source(t->cr); cairo_paint(t->cr); runes_display_paint_rectangle( t, t->cr, t->bgdefault, 0, t->scroll_bottom + 1 - count, t->cols, count); cairo_restore(t->cr); } void runes_display_delete_characters(RunesTerm *t, int count) { cairo_pattern_t *pattern; cairo_matrix_t matrix; cairo_save(t->cr); cairo_push_group(t->cr); pattern = cairo_pattern_create_for_surface(cairo_get_target(t->cr)); cairo_matrix_init_translate(&matrix, count * t->fontx, 0.0); cairo_pattern_set_matrix(pattern, &matrix); runes_display_paint_rectangle( t, t->cr, pattern, t->col, t->row, t->cols - t->col - count, 1); cairo_pattern_destroy(pattern); cairo_pop_group_to_source(t->cr); cairo_paint(t->cr); runes_display_paint_rectangle( t, t->cr, t->bgdefault, t->cols - count, t->row, count, 1); cairo_restore(t->cr); } void runes_display_reset_text_attributes(RunesTerm *t) { runes_display_reset_fg_color(t); runes_display_reset_bg_color(t); runes_display_reset_bold(t); runes_display_reset_italic(t); runes_display_reset_underline(t); runes_display_reset_inverse(t); } void runes_display_set_bold(RunesTerm *t) { PangoAttrList *attrs; attrs = pango_layout_get_attributes(t->layout); if (t->bold_is_bold) { pango_attr_list_change( attrs, pango_attr_weight_new(PANGO_WEIGHT_BOLD)); } t->bold = 1; cairo_set_source(t->cr, runes_display_get_fgcolor(t)); } void runes_display_reset_bold(RunesTerm *t) { PangoAttrList *attrs; attrs = pango_layout_get_attributes(t->layout); if (t->bold_is_bold) { pango_attr_list_change( attrs, pango_attr_weight_new(PANGO_WEIGHT_NORMAL)); } t->bold = 0; cairo_set_source(t->cr, runes_display_get_fgcolor(t)); } void runes_display_set_italic(RunesTerm *t) { PangoAttrList *attrs; attrs = pango_layout_get_attributes(t->layout); pango_attr_list_change(attrs, pango_attr_style_new(PANGO_STYLE_ITALIC)); } void runes_display_reset_italic(RunesTerm *t) { PangoAttrList *attrs; attrs = pango_layout_get_attributes(t->layout); pango_attr_list_change(attrs, pango_attr_style_new(PANGO_STYLE_NORMAL)); } void runes_display_set_underline(RunesTerm *t) { PangoAttrList *attrs; attrs = pango_layout_get_attributes(t->layout); pango_attr_list_change( attrs, pango_attr_underline_new(PANGO_UNDERLINE_SINGLE)); } void runes_display_reset_underline(RunesTerm *t) { PangoAttrList *attrs; attrs = pango_layout_get_attributes(t->layout); pango_attr_list_change( attrs, pango_attr_underline_new(PANGO_UNDERLINE_NONE)); } void runes_display_set_inverse(RunesTerm *t) { t->inverse = 1; cairo_set_source(t->cr, runes_display_get_fgcolor(t)); } void runes_display_reset_inverse(RunesTerm *t) { t->inverse = 0; cairo_set_source(t->cr, runes_display_get_fgcolor(t)); } void runes_display_set_fg_color(RunesTerm *t, int color) { t->fgcolor = color; cairo_set_source(t->cr, runes_display_get_fgcolor(t)); } void runes_display_set_fg_color_rgb(RunesTerm *t, int r, int g, int b) { t->fgcolor = -2; if (t->fgcustom) { cairo_pattern_destroy(t->fgcustom); } t->fgcustom = cairo_pattern_create_rgb( (double)r / 255.0, (double)g / 255.0, (double)b / 255.0); cairo_set_source(t->cr, runes_display_get_fgcolor(t)); } void runes_display_reset_fg_color(RunesTerm *t) { runes_display_set_fg_color(t, -1); } void runes_display_set_bg_color(RunesTerm *t, int color) { t->bgcolor = color; cairo_set_source(t->cr, runes_display_get_fgcolor(t)); } void runes_display_set_bg_color_rgb(RunesTerm *t, int r, int g, int b) { t->bgcolor = -2; if (t->bgcustom) { cairo_pattern_destroy(t->bgcustom); } t->bgcustom = cairo_pattern_create_rgb( (double)r / 255.0, (double)g / 255.0, (double)b / 255.0); cairo_set_source(t->cr, runes_display_get_fgcolor(t)); } void runes_display_reset_bg_color(RunesTerm *t) { runes_display_set_bg_color(t, -1); } void runes_display_show_cursor(RunesTerm *t) { t->hide_cursor = 0; } void runes_display_hide_cursor(RunesTerm *t) { t->hide_cursor = 1; } void runes_display_save_cursor(RunesTerm *t) { t->saved_row = t->row; t->saved_col = t->col; /* XXX do other stuff here? */ } void runes_display_restore_cursor(RunesTerm *t) { t->row = t->saved_row; t->col = t->saved_col; } void runes_display_use_alternate_buffer(RunesTerm *t) { if (t->alternate_cr) { return; } runes_display_save_cursor(t); t->alternate_cr = t->cr; t->cr = NULL; t->xpixel = -1; t->ypixel = -1; runes_display_set_window_size(t); } void runes_display_use_normal_buffer(RunesTerm *t) { if (!t->alternate_cr) { return; } runes_display_restore_cursor(t); cairo_destroy(t->cr); t->cr = t->alternate_cr; t->alternate_cr = NULL; t->xpixel = -1; t->ypixel = -1; runes_display_set_window_size(t); } void runes_display_set_scroll_region( RunesTerm *t, int top, int bottom, int left, int right) { top = (top < 1 ? 1 : top) - 1; bottom = (bottom > t->rows ? t->rows : bottom) - 1; left = (left < 1 ? 1 : left) - 1; right = (right > t->cols ? t->cols : right) - 1; if (left != 0 || right != t->cols - 1) { fprintf(stderr, "vertical scroll regions not yet supported\n"); } if (top >= bottom || left >= right) { t->scroll_top = 0; t->scroll_bottom = t->rows - 1; return; } t->scroll_top = top; t->scroll_bottom = bottom; runes_display_move_to(t, t->scroll_top, 0); } void runes_display_cleanup(RunesTerm *t) { int i; g_object_unref(t->layout); for (i = 0; i < 256; ++i) { cairo_pattern_destroy(t->colors[i]); } cairo_pattern_destroy(t->fgdefault); cairo_pattern_destroy(t->bgdefault); if (t->fgcustom) { cairo_pattern_destroy(t->fgcustom); } if (t->bgcustom) { cairo_pattern_destroy(t->bgcustom); } cairo_pattern_destroy(t->cursorcolor); cairo_pattern_destroy(t->mousecursorcolor); cairo_destroy(t->cr); } static cairo_pattern_t *runes_display_get_fgcolor(RunesTerm *t) { int color = t->inverse ? t->bgcolor : t->fgcolor; if (t->inverse && t->bgcolor == t->fgcolor) { return t->bgdefault; } else if (color == -2) { return t->inverse ? t->bgcustom : t->fgcustom; } else if (color == -1) { return t->inverse ? t->bgdefault : t->fgdefault; } else if (t->bold_is_bright && t->bold && color < 8) { return t->colors[color + 8]; } else { return t->colors[color]; } } static cairo_pattern_t *runes_display_get_bgcolor(RunesTerm *t) { int color = t->inverse ? t->fgcolor : t->bgcolor; if (t->inverse && t->bgcolor == t->fgcolor) { return t->fgdefault; } else if (color == -2) { return t->inverse ? t->bgcustom : t->fgcustom; } else if (color == -1) { return t->inverse ? t->fgdefault : t->bgdefault; } else { return t->colors[color]; } } static void runes_display_recalculate_font_metrics(RunesTerm *t) { PangoFontDescription *desc; PangoContext *context; PangoFontMetrics *metrics; int ascent, descent; if (t->layout) { desc = (PangoFontDescription *)pango_layout_get_font_description( t->layout); context = pango_layout_get_context(t->layout); } else { desc = pango_font_description_from_string(t->font_name); context = pango_font_map_create_context( pango_cairo_font_map_get_default()); } metrics = pango_context_get_metrics(context, desc, NULL); t->fontx = PANGO_PIXELS( pango_font_metrics_get_approximate_digit_width(metrics)); ascent = pango_font_metrics_get_ascent(metrics); descent = pango_font_metrics_get_descent(metrics); t->fonty = PANGO_PIXELS(ascent + descent); pango_font_metrics_unref(metrics); if (!t->layout) { pango_font_description_free(desc); g_object_unref(context); } } static void runes_display_position_cursor(RunesTerm *t, cairo_t *cr) { cairo_move_to(cr, t->col * t->fontx, t->row * t->fonty); } static void runes_display_paint_rectangle( RunesTerm *t, cairo_t *cr, cairo_pattern_t *pattern, int x, int y, int width, int height) { cairo_save(cr); cairo_set_source(cr, pattern); cairo_rectangle( cr, x * t->fontx, y * t->fonty, width * t->fontx, height * t->fonty); cairo_fill(cr); cairo_restore(cr); runes_display_position_cursor(t, t->cr); } static void runes_display_scroll_up(RunesTerm *t, int rows) { cairo_pattern_t *pattern; cairo_matrix_t matrix; cairo_save(t->cr); cairo_push_group(t->cr); pattern = cairo_pattern_create_for_surface(cairo_get_target(t->cr)); cairo_matrix_init_translate(&matrix, 0.0, -rows * t->fonty); cairo_pattern_set_matrix(pattern, &matrix); runes_display_paint_rectangle( t, t->cr, pattern, 0, t->scroll_top + rows, t->cols, t->scroll_bottom - t->scroll_top + 1 - rows); cairo_pattern_destroy(pattern); cairo_pop_group_to_source(t->cr); cairo_paint(t->cr); runes_display_paint_rectangle( t, t->cr, t->bgdefault, 0, t->scroll_top, t->cols, rows); cairo_restore(t->cr); } static void runes_display_scroll_down(RunesTerm *t, int rows) { cairo_pattern_t *pattern; cairo_matrix_t matrix; cairo_save(t->cr); cairo_push_group(t->cr); pattern = cairo_pattern_create_for_surface(cairo_get_target(t->cr)); cairo_matrix_init_translate(&matrix, 0.0, rows * t->fonty); cairo_pattern_set_matrix(pattern, &matrix); runes_display_paint_rectangle( t, t->cr, pattern, 0, t->scroll_top, t->cols, t->scroll_bottom - t->scroll_top); cairo_pattern_destroy(pattern); cairo_pop_group_to_source(t->cr); cairo_paint(t->cr); runes_display_paint_rectangle( t, t->cr, t->bgdefault, 0, t->scroll_bottom + 1 - rows, t->cols, rows); cairo_restore(t->cr); }