#include #include #include #include #include #include #include #include #include enum CommandType { COMMAND_NOTHING, COMMAND_DONE, COMMAND_BLOCK, COMMAND_SPAN, }; union Command { int type; struct { int type; int length; const char * data; } block; struct { int type; int position; int length; } span; }; struct State { int width; int height; xcb_connection_t * c; xcb_screen_t * s; xcb_visualtype_t * v; xcb_window_t w; cairo_t * ctx; cairo_surface_t * surface; }; int setup(struct State *); void finalize(struct State *); int handle(struct State *, xcb_generic_event_t *); void draw_body(struct State *, union Command *); xcb_visualtype_t * find_visual(xcb_screen_t *); static const int MARGIN = 20; static const char * const FONT = "Serif 16"; #define _BLOCK(_txt) {.block = {.type = COMMAND_BLOCK, .length = sizeof(_txt) - 1, .data = _txt}} union Command body[] = { {.span = {.type = COMMAND_SPAN, .position = 28, .length = 11}}, _BLOCK( "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam auctor porta eros at tempus. Aliquam " "elementum lectus id mi fermentum, non consectetur urna lobortis." ), _BLOCK("Phasellus dignissim rhoncus magna at imperdiet."), {.type = COMMAND_DONE}, }; #undef _BLOCK int main(int argc, const char ** argv) { (void) argc; (void) argv; struct State state; if (-1 == setup(&state)) { dprintf(2, "Could not connect to X server\n"); // TODO: Helpful (specific) errors. return 1; } for (;;) { xcb_generic_event_t * e = xcb_wait_for_event(state.c); // TODO: Change it to poll-like behaviour. if (handle(&state, e)) break; } finalize(&state); } int setup(struct State * state) { static const int initial_width = 800; static const int initial_height = 600; state->c = xcb_connect(NULL, NULL); if (xcb_connection_has_error(state->c)) return -1; state->s = xcb_setup_roots_iterator(xcb_get_setup(state->c)).data; state->w = xcb_generate_id(state->c); state->v = find_visual(state->s); const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; const uint32_t values[] = { state->s->white_pixel, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_STRUCTURE_NOTIFY, }; xcb_create_window( state->c, XCB_COPY_FROM_PARENT, state->w, state->s->root, 0, 0, initial_width, initial_height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, state->s->root_visual, mask, values); state->surface = cairo_xcb_surface_create(state->c, state->w, state->v, initial_width, initial_height); state->ctx = cairo_create(state->surface); xcb_map_window(state->c, state->w); xcb_flush(state->c); return 0; } void finalize(struct State * state) { cairo_destroy(state->ctx); cairo_surface_destroy(state->surface); xcb_disconnect(state->c); } int handle(struct State * state, xcb_generic_event_t * e) { switch (XCB_EVENT_RESPONSE_TYPE(e)) { case XCB_KEY_PRESS: free(e); return 1; case XCB_EXPOSE: cairo_set_source_rgb(state->ctx, 1., 1., 1.); cairo_paint(state->ctx); draw_body(state, body); // TODO: Redraw outside of handler and don't use global body. cairo_surface_flush(state->surface); xcb_flush(state->c); break; case XCB_CONFIGURE_NOTIFY: ; xcb_configure_notify_event_t * conf = (xcb_configure_notify_event_t *) e; if (state->width != conf->width || state->height != conf->height) { cairo_xcb_surface_set_size(state->surface, conf->width, conf->height); state->width = conf->width; state->height = conf->height; } break; default: break; } free(e); return 0; } xcb_visualtype_t * find_visual(xcb_screen_t * screen) { xcb_depth_iterator_t d = xcb_screen_allowed_depths_iterator(screen); for (; d.rem; xcb_depth_next(&d)) { xcb_visualtype_iterator_t v = xcb_depth_visuals_iterator(d.data); for (; v.rem; xcb_visualtype_next(&v)) if (v.data->visual_id == screen->root_visual) return v.data; } return NULL; } void draw_body(struct State * state, union Command * body) { PangoFontDescription * desc = pango_font_description_from_string(FONT); cairo_set_source_rgb(state->ctx, 0., 0., 0.); int y = MARGIN; PangoAttrList * attrs = pango_attr_list_new(); // TODO-maybe: Don't recreate attributes lists each redraw? for (union Command * command = body; COMMAND_DONE != command->type; ++command) { switch (command->type) { case COMMAND_SPAN: ; PangoAttribute * attr = pango_attr_foreground_new(0x0000, 0x0000, 0xffff); attr->start_index = command->span.position; attr->end_index = command->span.position + command->span.length; pango_attr_list_change(attrs, attr); break; case COMMAND_BLOCK: cairo_move_to(state->ctx, MARGIN, y); PangoLayout * layout = pango_cairo_create_layout(state->ctx); pango_layout_set_attributes(layout, attrs); pango_attr_list_unref(attrs); attrs = pango_attr_list_new(); pango_layout_set_font_description(layout, desc); pango_layout_set_wrap(layout, PANGO_WRAP_WORD); pango_layout_set_width(layout, (state->width - 2 * MARGIN) * PANGO_SCALE); pango_layout_set_text(layout, command->block.data, command->block.length); pango_cairo_update_layout(state->ctx, layout); pango_cairo_show_layout(state->ctx, layout); int height; pango_layout_get_pixel_size(layout, NULL, &height); y += height + MARGIN; break; case COMMAND_NOTHING: default: break; } } pango_attr_list_unref(attrs); pango_font_description_free(desc); }