Skip to content

Instantly share code, notes, and snippets.

@lighth7015
Last active December 25, 2023 16:11
Show Gist options
  • Save lighth7015/b09c240ddef307ac48a5d2cc69aee545 to your computer and use it in GitHub Desktop.
Save lighth7015/b09c240ddef307ac48a5d2cc69aee545 to your computer and use it in GitHub Desktop.
GDI inspired graphics wrapper for XCB

GDI-style XCB wrapper

A drawing library modeled after Windows GDI

Features

  • CreateDC/ReleaseDC
  • DrawText
  • DrawRect

Documentation

Documentation

Compilation

clang -lxcb -o draggable{,.c} draw.c

License

MIT

Support

For support, email [email protected] or DM me on discord (robert_#4066)

Badges

Add badges from somewhere like: shields.io

Apache License

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <xcb/xcb.h>
#include <stdbool.h>
#include "draw.h"
xcb_connection_t *c;
xcb_screen_t *screen;
xcb_gcontext_t gc;
xcb_drawable_t parent;
xcb_drawable_t swin;
int drag_state = 0;
uint32_t offset[2];
uint32_t origin[2];
void draw_button(xcb_drawable_t d) {
DC surface = CreateDC(c, screen, d);
surface.DrawText(surface, "Drag me");
ReleaseDC(surface);
//char string[] = "Drag me";
//uint8_t string_len = strlen(string);
//xcb_image_text_8(c, string_len, d, gc, 6, 12, string);
}
xcb_drawable_t make_button(xcb_drawable_t parent, uint32_t width, uint32_t height) {
xcb_drawable_t child = xcb_generate_id(c);
uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK;
uint32_t values[3] = { screen->white_pixel, screen->black_pixel, 0 };
values[2] = ( XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS |
XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION |
XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
XCB_EVENT_MASK_KEY_PRESS);
xcb_create_window (c, XCB_COPY_FROM_PARENT, child, parent, 0, 0, width, height,
1, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, mask, values);
origin[0] = 0;
origin[1] = 0;
xcb_map_window(c, child);
draw_button(child);
return child;
}
xcb_drawable_t create_window() {
uint32_t mask = 0;
uint32_t values[3] = { 0 };
//gc = xcb_generate_id (c);
mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_GRAPHICS_EXPOSURES;
values[0] = screen->black_pixel;
values[1] = screen->white_pixel;
//xcb_create_gc (c, gc, screen->root, mask, values);
/* create the window */
int handle = xcb_generate_id(c);
mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
values[0] = screen->white_pixel;
values[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_STRUCTURE_NOTIFY |
XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_BUTTON_MOTION;
xcb_create_window( c, /* connection */
screen->root_depth, /* depth */
handle, /* window Id */
screen->root, /* parent window */
0, 0, /* x, y */
300,300, /* width, height */
0, /* border_width */
XCB_WINDOW_CLASS_INPUT_OUTPUT, /* class */
screen->root_visual, /* visual */
mask, values); /* masks */
xcb_map_window(c, handle);
return handle;
}
void event_loop(int window) {
for ( xcb_generic_event_t *e = xcb_wait_for_event (c)
; e
; e = xcb_wait_for_event (c))
{
switch (e->response_type & ~0x80) {
case XCB_MOTION_NOTIFY:
if (((xcb_motion_notify_event_t *)e)->event == swin && drag_state) {
xcb_motion_notify_event_t *motion = (xcb_motion_notify_event_t *)e;
origin[0] += motion->event_x - offset[0];
origin[1] += motion->event_y - offset[1];
xcb_configure_window(c, swin, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, origin);
}
xcb_flush(c);
break;
/* grab the offset from the subwindow's origin */
case XCB_BUTTON_PRESS:
if (((xcb_button_press_event_t *) e)->event == swin) {
xcb_button_press_event_t *ev = (xcb_button_press_event_t *) e;
printf("%d, %d\n", ev->event_x, ev->event_y);
offset[0] = ev->event_x;
offset[1] = ev->event_y;
drag_state = ev->event_y <= 20;
}
break;
case XCB_BUTTON_RELEASE:
drag_state = 0;
break;
case XCB_CONFIGURE_NOTIFY:
if (((xcb_configure_notify_event_t *) e)->event == swin) {
xcb_configure_notify_event_t *ev = (xcb_configure_notify_event_t *) e;
puts("pz");
//origin[0] = ev->x;
//origin[1] = ev->y;
}
break;
case XCB_EXPOSE:
draw_button(swin);
xcb_flush(c);
break;
case XCB_KEY_PRESS:
if (((xcb_key_press_event_t *) e)->detail == 9) {
xcb_key_press_event_t *ev = (xcb_key_press_event_t *) e;
return;
}
break;
}
free (e);
}
}
int main(void) {
c = xcb_connect (NULL, NULL);
screen = xcb_setup_roots_iterator (xcb_get_setup (c)).data;
parent = create_window();
swin = make_button(parent, 320, 240);
xcb_flush (c);
event_loop(parent);
return EXIT_SUCCESS;
}
#include <locale.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <xcb/xcb.h>
#include "draw.h"
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define DEFAULTFN "fixed"
bool DrawRect(DC dc, int16_t x, int16_t y, uint16_t w, uint16_t h, uint32_t color) {
xcb_rectangle_t rect = {x, y, w, h};
uint32_t val[2] = { color, 0 };
xcb_change_gc(dc.c, dc.gc, XCB_GC_FOREGROUND, val);
xcb_poly_rectangle(dc.c, dc.canvas, dc.gc, 1, &rect);
return true;
}
bool FillRect(DC dc, int16_t x, int16_t y, uint16_t w, uint16_t h, uint32_t color) {
xcb_rectangle_t rect = {x, y, w, h};
uint32_t val[2] = { color, 0 };
xcb_change_gc(dc.c, dc.gc, XCB_GC_FOREGROUND, val);
xcb_poly_fill_rectangle(dc.c, dc.canvas, dc.gc, 1, &rect);
return true;
}
void ReleaseDC(DC dc) {
// if (dc.font.xfont) {
// XFreeFont(dc.dpy, dc.font.xfont);
// }
if(dc.canvas) {
xcb_free_pixmap(dc.c, dc.canvas);
}
xcb_free_gc(dc.c, dc.gc);
}
uint32_t GetColor(DC dc, const char *colstr){
unsigned int r = 0, g = 0, b = 0;
uint32_t pixel = 0;
xcb_alloc_color_reply_t *reply;
/* convert 24 bit color values to 48 bit color values
* with assistance from bspwm by Baskerville
* https://github.com/baskerville/bspwm */
fprintf(stderr, "color string: %s\n", colstr+1);
if (sscanf(colstr+1, "%2x%2x%2x", &r, &g, &b) == 3) {
// convert color to 48 bit 0x23 * 0x101 = 0x2323
fprintf(stderr, "r: %x, g: %x, b: %x\n", r, g, b);
r *= 0x101;
g *= 0x101;
b *= 0x101;
if((reply = xcb_alloc_color_reply(dc.c, xcb_alloc_color(dc.c, dc.screen->default_colormap, r, g, b), NULL))) {
pixel = reply->pixel;
fprintf(stderr, "GetColor success\n");
free(reply);
}
}
return pixel;
}
static bool
TextFontInit(DC dc, const char* fontstr) {
bool response = false;
xcb_font_t font;
xcb_void_cookie_t cookie;
xcb_generic_error_t *error;
xcb_query_font_cookie_t fookie;
xcb_query_font_reply_t *reply;
font = xcb_generate_id(dc.c);
cookie = xcb_open_font_checked(dc.c, font, strlen(fontstr), fontstr);
if(!((error = xcb_request_check(dc.c, cookie)))) {
fookie = xcb_query_font(dc.c, font);
if ((reply = xcb_query_font_reply(dc.c, fookie, NULL))) {
dc.font.ascent = reply->font_ascent;
dc.font.descent = reply->font_descent;
dc.font.width = reply->max_bounds.character_width;
dc.font.xfont = font;
response = true;
}
}
fprintf(stderr, "cannot load font");
free(error);
return response;
}
void TextFontLoad(DC dc, const char *fontstr) {
if(!TextFontInit(dc, fontstr ? fontstr : DEFAULTFN)) {
if(fontstr != NULL) {
fprintf(stderr, "cannot load font '%s'\n", fontstr);
}
else if(fontstr == NULL || !TextFontInit(dc, DEFAULTFN)) {
fprintf(stderr, "cannot load font '%s'\n", DEFAULTFN);
}
}
else {
dc.font.height = dc.font.ascent + dc.font.descent;
}
}
bool Map(DC dc, uint16_t w, uint16_t h) {
xcb_void_cookie_t cookie =
xcb_copy_area_checked(dc.c, dc.canvas, dc.window, dc.gc, 0, 0, 0, 0, w, h);
xcb_generic_error_t *error;
bool result = false;
if ((error = xcb_request_check(dc.c, cookie))) {
fprintf(stderr, "Map failure\n");
free(error);
}
else {
fprintf(stderr, "Map success\n");
result = true;
}
xcb_flush(dc.c);
return result;;
}
void Resize(DC dc, uint16_t w, uint16_t h) {
xcb_generic_error_t *error;
xcb_void_cookie_t cookie;
dc.w = w;
dc.h = h;
dc.canvas = xcb_generate_id(dc.c);
cookie = xcb_create_pixmap_checked(dc.c, dc.screen->root_depth, dc.canvas, dc.screen->root, w, h);
if ((error = xcb_request_check(dc.c, cookie))) {
fprintf(stderr, "Resize failure\n");
free(error);
}
else
fprintf(stderr, "Resize success\n");
}
bool DrawTextCW(DC dc, const char *text, size_t n, color_t col) {
xcb_void_cookie_t cookie;
xcb_generic_error_t *error;
bool result = false;
int xp = dc.x + dc.font.height/2,
yp = dc.y + dc.font.ascent+1;
uint32_t val[3] = { FG(dc, col), BG(dc, col), dc.font.xfont };
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
cookie = xcb_change_gc_checked(dc.c, dc.gc, mask, val);
if(((error = xcb_request_check(dc.c, cookie)))) {
fprintf(stderr, "errror could not complete DrawText request");
free(error);
}
else {
cookie = xcb_image_text_8_checked(dc.c, n, dc.canvas, dc.gc, xp, yp, text);
if(((error = xcb_request_check(dc.c, cookie)))) {
fprintf(stderr, "errror could not complete DrawText request");
free(error);
}
else {
result = true;
}
}
return result;
}
uint32_t DrawTextW(DC dc, const char *text, size_t len) {
//xcb_query_text_extents_cookie_t cookie;
//xcb_query_text_extents_reply_t *reply;
int32_t result = 0, width = len;
const char* string = strndup(text, len);
/*
xcb_char2b_t *string;
string = calloc(1, sizeof(xcb_char2b_t));
string->byte1 = 0;
string->byte2 = 0;
memcpy(&string->byte2, text, sizeof(char));
cookie = xcb_query_text_extents(dc.c, dc.font.xfont, len, string);
if ((reply = xcb_query_text_extents_reply(dc.c, cookie, NULL))) {
width = reply->overall_width;
free(reply);
result = width;
}
*/
xcb_image_text_8(dc.c, len, dc.window, dc.gc, 12, 12, string);
return 0;
}
bool DrawText(DC dc, const char *text) {
return DrawTextW(dc, text, strlen(text)) + dc.font.height;
}
bool DrawTextColor(DC dc, const char *text, color_t col) {
char buf[BUFSIZ];
size_t n = strlen(text),
mn = MIN(n, sizeof buf);
/**
* shorten text if necessary
*/
for (mn = MIN(n, sizeof buf); mn > 0; mn--) {
DrawTextW(dc, text, mn); // + dc.font.height / 2 > dc.w);
}
memcpy(buf, text, mn);
if(mn < n) {
for(n = MAX(mn-3, 0); n < mn; buf[n++] = '.');
}
// DrawRect(dc, 0, 0, dc.w, dc.h, true, BG(dc, col));
return DrawTextCW(dc, buf, mn, col);
}
DC CreateDC(xcb_connection_t *connection, xcb_screen_t* screen, xcb_window_t window) {
DC dc = {
.c = connection,
.gc = xcb_generate_id(connection),
.screen = screen,
.mask = XCB_GC_LINE_STYLE | XCB_GC_CAP_STYLE | XCB_GC_JOIN_STYLE,
.window = window,
.DrawRect = DrawRect,
.FillRect = FillRect,
.GetColor = GetColor,
.LoadFont = TextFontInit,
.DrawText = DrawText,
.DrawTextW = DrawTextW,
.DrawTextCW = DrawTextCW,
};
uint32_t values[3] = { screen->black_pixel, screen->white_pixel, 0 };
uint32_t rect[3] = { XCB_LINE_STYLE_SOLID, XCB_CAP_STYLE_BUTT, XCB_JOIN_STYLE_MITER };
uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK;
xcb_create_gc(connection, dc.gc, dc.screen->root, mask, rect);
return dc;
}
#define FG(dc, col) ((col)[(dc).invert ? ColBG : ColFG])
#define BG(dc, col) ((col)[(dc).invert ? ColFG : ColBG])
enum { ColBG, ColFG, ColBorder, ColLast };
typedef uint32_t color_t [ColLast];
typedef struct DC {
uint32_t x, y, w, h;
uint8_t invert;
xcb_connection_t *c;
uint32_t mask;
xcb_screen_t *screen;
xcb_window_t window;
xcb_gcontext_t gc;
xcb_pixmap_t canvas;
struct {
uint16_t ascent;
uint16_t descent;
uint32_t height;
uint32_t width;
xcb_font_t xfont;
} font;
bool (*DrawRect)(struct DC dc, int16_t x, int16_t y, uint16_t w, uint16_t h, uint32_t color);
bool (*FillRect)(struct DC dc, int16_t x, int16_t y, uint16_t w, uint16_t h, uint32_t color);
bool (*Text)(struct DC dc, const char *text, color_t col);
uint32_t (*GetColor)(struct DC dc, const char *colstr);
bool (*InitFont)(struct DC dc, const char *fontstr);
bool (*LoadFont)(struct DC dc, const char *fontstr);
bool (*MapDC)(struct DC dc, uint16_t w, uint16_t h);
bool (*Resize)(struct DC dc, uint16_t w, uint16_t h);
bool (*DrawText)(struct DC dc, const char *text);
uint32_t (*DrawTextW)(struct DC dc, const char *text, size_t len);
bool (*DrawTextCW)(struct DC dc, const char *text, size_t len, color_t col);
} DC, *HDC; /* draw context */
DC CreateDC(xcb_connection_t *connection, xcb_screen_t* screen, xcb_window_t window);
void ReleaseDC(DC dc);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment