Skip to content

Instantly share code, notes, and snippets.

@h4k1m0u
Last active February 6, 2022 12:57
Show Gist options
  • Save h4k1m0u/703a8c1afd4f256fd32f5446b8e6dae6 to your computer and use it in GitHub Desktop.
Save h4k1m0u/703a8c1afd4f256fd32f5446b8e6dae6 to your computer and use it in GitHub Desktop.
Draws vector graphics with cairo on a xcb window

XCB

  • Client-side of X11 display server (replacement for xlib), stands for X protocol C-language Binding.
  • Check the official tutorial.

Terminology

From this Stackoverflow question:

  • Each X server has one display identified by a display number (by default 0).
  • A display is a collection of monitors (screens) sharing the same input devices (keyboard, mouse).
  • Multi-user systems have multiple displays, so that more than one person can do graphic work. For instance, two users connected to the GUI of the same machine will have different values for $DISPLAY variable.

According to this page:

  • A screen supports multiple color depths, each one associated with many visual types to define the color mapping.
  • For inspiration, also check this example, and this other example.

Examples

Cairo

  • Library for vector graphics drawing that can output to X windows, png/pdf files, OpenGL...
  • Check the official tutorial.
  • Another tutorial worth looking at.

Installation

$ sudo apt install libcairo2-dev

Examples

Cairo & XCB

For drawing vector graphics with Cairo on XCB windows, see these functions.

// FILE: src/cairo_utils.cpp
#include <cairo-xcb.h>
#include <cairo.h>
#include <math.h>
#include "cairo_utils.hpp"
/**
* Initial example: https://www.cairographics.org/FAQ/
*/
void CairoUtils::draw(xcb_connection_t* connection, xcb_window_t window, xcb_visualtype_t* visual_type) {
/*
// create an image surface in argb format
const int width = 400;
const int height = 400;
cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
*/
// create surface for xlib window
int width = 500,
height = 500;
cairo_surface_t* surface = cairo_xcb_surface_create(
connection,
window,
visual_type,
width, height);
// create context with surface as target
cairo_t* context = cairo_create(surface);
// draw a line
cairo_set_source_rgb(context, 0.0, 1.0, 0.0);
cairo_move_to(context, 0.0, 0.0);
cairo_line_to(context, 400.0, 400.0);
cairo_stroke(context);
// draw a circle
cairo_set_source_rgb(context, 0.0, 0.0, 1.0);
cairo_arc(context, 200.0, 200.0, 20.0, 0.0, 2*M_PI);
cairo_stroke_preserve(context);
// fill drawn circle
cairo_set_source_rgb(context, 0.0, 1.0, 1.0);
cairo_fill(context);
// write surface to a png image
// cairo_surface_write_to_png(surface, "hello.png");
// free context & created surface
cairo_destroy(context);
cairo_surface_destroy(surface);
}
// FILE: include/cairo_utils.hpp
#ifndef CAIRO_UTILS_HPP
#define CAIRO_UTILS_HPP
#include <xcb/xcb.h>
/* Draw vector shapes with Cairo on xlib surface */
namespace CairoUtils {
void draw(xcb_connection_t* connection, xcb_window_t window, xcb_visualtype_t* visual_type);
}
#endif // CAIRO_UTILS_HPP
cmake_minimum_required(VERSION 3.10)
project(XCB-Cairo-Example)
# autocomplete with clangd in vim (for YCM)
# set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# fill in Cairo headers & lib locations variable from /usr/lib/x86_64-linux-gnu/pkgconfig/{cairo.pc, xcb.pc}
find_package(PkgConfig REQUIRED)
pkg_check_modules(CAIRO REQUIRED cairo)
pkg_check_modules(XCB REQUIRED xcb)
# xcb/cairo example executable
add_executable(main src/main.cpp src/cairo_utils.cpp)
target_include_directories(main PRIVATE ${XCB_INCLUDE_DIRS} ${CAIRO_INCLUDE_DIRS} include)
target_link_libraries(main ${XCB_LIBRARIES} ${CAIRO_LIBRARIES})
// FILE: src/main.cpp
#include <xcb/xcb.h>
#include <iostream>
#include "cairo_utils.hpp"
/**
* Draws vector graphics with cairo on a xcb window
* Leave application with <Ctrl-C> as key events not handled yet
*/
int main() {
// connect to X server identified by `$DISPLAY`
int screen_number;
xcb_connection_t* connection = xcb_connect(NULL, &screen_number);
// data returned by server after connection
const xcb_setup_t* setup = xcb_get_setup(connection);
// get screens (monitors) managed by display (i.e. X server)
xcb_screen_iterator_t iter_screens = xcb_setup_roots_iterator(setup);
xcb_screen_t* screen = iter_screens.data; // first screen
uint16_t width_screen = screen->width_in_pixels;
uint16_t height_screen = screen->height_in_pixels;
std::cout << "# of screen: " << iter_screens.rem << '\n';
std::cout << "screen width: " << width_screen << '\n';
std::cout << "screen height: " << height_screen << '\n';
// screen supports multiple depths, each depth having multiple visual types (color mapping)
xcb_visualtype_t* visual_type;
xcb_depth_iterator_t iter_depths = xcb_screen_allowed_depths_iterator(screen);
for (; iter_depths.rem; xcb_depth_next(&iter_depths)) {
xcb_depth_t* depth = iter_depths.data;
xcb_visualtype_iterator_t iter_visuals = xcb_depth_visuals_iterator(depth);
for (; iter_visuals.rem; xcb_visualtype_next(&iter_visuals)) {
xcb_visualtype_t* visual_type_current = iter_visuals.data;
if (visual_type_current->visual_id == screen->root_visual) {
visual_type = visual_type_current;
goto endloop;
}
}
}
endloop:
// allocate an XID to identify object created (e.g. window, pixmap...)
xcb_window_t window = xcb_generate_id(connection);
// white bg & register for selected events (value_list items respect bit-order in `xcb_cw_t` enum)
uint32_t value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
uint32_t value_list[] = {
screen->white_pixel,
XCB_EVENT_MASK_EXPOSURE,
};
int width = 500,
height = 500;
// create window identified by created xid
xcb_create_window(
connection,
XCB_COPY_FROM_PARENT,
window,
screen->root,
0, 0, // x & y
width, height,
10, // border size
XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual,
value_mask, value_list);
// set window title
std::string title = "Hello XCB/Cairo worlds";
xcb_change_property(
connection,
XCB_PROP_MODE_REPLACE,
window,
XCB_ATOM_WM_NAME,
XCB_ATOM_STRING,
8,
title.length(),
title.c_str());
// make a window visible
xcb_map_window(connection, window);
// send commands to server (forces window to show)
xcb_flush(connection);
// receiving events (blocking way using infinite loop)
xcb_generic_event_t* event;
while ((event = xcb_wait_for_event(connection))) {
// wait for window to render, otherwise white bg overwrites cairo drawing
// https://gitlab.freedesktop.org/cairo/cairo/-/issues/531
if (event->response_type == XCB_EXPOSE) {
std::cout << "Rendering graphics with Cairo" << '\n';
CairoUtils::draw(connection, window, visual_type);
// send commands to server (forces vector to show)
xcb_flush(connection);
}
delete event;
}
// close connection to server
xcb_disconnect(connection);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment