Skip to content

Instantly share code, notes, and snippets.

@matyklug18
Created December 10, 2021 07:05
Show Gist options
  • Save matyklug18/83a7fe6fb2805c47cf929cf82ce54110 to your computer and use it in GitHub Desktop.
Save matyklug18/83a7fe6fb2805c47cf929cf82ce54110 to your computer and use it in GitHub Desktop.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
//xlib, for interacting with the X server
#include <X11/Xatom.h>
//cairo, for drawing
#include <cairo.h>
#include <cairo-xlib.h>
#include "app.h"
#include "../config.h"
//initialize the app, taking the initial size of the window as a parameter
main_window init(const app_params params) {
//the display, representing the connection to X
Display *dsp;
//the window, representing the X window for this app
Drawable da;
//the screen, representing the monitor (?)
int screen;
//the cairo surface, used when drawing
cairo_surface_t *sfc;
XInitThreads();
//open the display
dsp = XOpenDisplay(NULL);
//set the screen to the default screen (?)
screen = DefaultScreen(dsp);
//create the window, telling X it exists without showing it
da = XCreateSimpleWindow(dsp, DefaultRootWindow(dsp), 0, 0, DisplayWidth(dsp, screen), params.height, 0, 0, 0);
sleep(0.1);
//tell xorg, that we want mouse button events, resize events and repaint needed events (?).
XSelectInput(dsp, da, ButtonPressMask | StructureNotifyMask | ExposureMask);
//make it into a bar/dock, by setting the "_NET_WM_WINDOW_TYPE" to "_NET_WM_WINDOW_TYPE_DOCK"
Atom type = XInternAtom(dsp, "_NET_WM_WINDOW_TYPE", False);
long value = XInternAtom(dsp, "_NET_WM_WINDOW_TYPE_DOCK", False);
XChangeProperty(dsp, da, type,
XA_ATOM, 32, PropModeReplace, (unsigned char *) &value, 1);
//move the bar to the bottom of the screen
#if POSITION==BOTTOM
XMoveWindow(dsp, da, 0, DisplayHeight(dsp, screen));
#elif POSITION=TOP
XMoveWindow(dsp, da, 0, 0);
#endif
//actually show the window
XMapWindow(dsp, da);
//create the cairo surface, so we can draw to the window
sfc = cairo_xlib_surface_create(dsp, da, DefaultVisual(dsp, screen), DisplayWidth(dsp, screen), params.height);
sleep(0.1);
//resize the surface (?)
cairo_xlib_surface_set_size(sfc, DisplayWidth(dsp, screen), params.height);
//create the cairo type, which is usually used when drawing
cairo_t* ctx = cairo_create(sfc);
//make the struct
main_window win_return = (main_window) {ctx, sfc, da, dsp};
win_return.font_pos = params.font_pos;
win_return.font_size = params.font_size;
return win_return;
}
//the paint function, paints on the window using the cairo variables
void paint(main_window* app, const char* text_in) {
// (?)
cairo_push_group(app->cairo);
//set the background color to a dark color (#282a36)
cairo_set_source_rgb(app->cairo, COLOR);
//paint the background with the color
cairo_paint(app->cairo);
//set the drawing location to (50, 32)
cairo_move_to(app->cairo, app->font_size / 2, app->font_pos);
//select the font
cairo_select_font_face(app->cairo, "Hack Nerd Font Mono", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
//set the size of the font
cairo_set_font_size(app->cairo, app->font_size);
//change the color from black to a light color (#f8f8f2)
cairo_set_source_rgb(app->cairo, 0.973, 0.973, 0.949);
//render the text in the *text* variable
cairo_show_text(app->cairo, text_in);
// (?)
cairo_pop_group_to_source(app->cairo);
cairo_paint(app->cairo);
//update the cairo surface of the window, then the window itself
cairo_surface_flush(app->cairo_surface);
XFlush(app->display);
}
//when the window is closed or the app crashed, destroy cairo and close the connection to X.
//if this isnt done, we get an error.
void exit_app(main_window* app) {
cairo_destroy(app->cairo);
XCloseDisplay(app->display);
}
char out[256];
//update the text && repaint
void update_text(main_window* app) {
//memset(out, 0, 256);
app->text_update(out);
paint(app, out);
}
//a function that runs asynchronously from the main thread, managing the functionality of the app
//modifies the text painted in paint() to the output of the update_text function.
void* update(void* inp_app) {
main_window* app = (main_window*) inp_app;
while(True) {
update_text(app);
sleep(app->interval);
}
return NULL;
}
//simple function to resize the cairo surface && repaint it
void resize(main_window* app, const unsigned int width, const unsigned int height) {
//resize the cairo surface
cairo_xlib_surface_set_size(app->cairo_surface, width, height);
//repaint the app
paint(app, out);
}
//event loop
void* event_loop(void* inp_app) {
main_window* app = (main_window*) inp_app;
//the event loop. when an event is recieved, its managed in one of the IFs inside
for(;;) {
//declare the event variable
XEvent e;
//fill the event variable with the next event
//wait if there is no next event
XNextEvent(app->display, &e);
if(e.type == MapNotify) {
}
//if the event recieved was ButtonPress, which is recieved when a mouse button is pressed...
if(e.type == ButtonPress) {
//get the events data from xbutton,
//which is the data for the event, since the type is ButtonPress.
XButtonEvent ev = e.xbutton;
printf("%d %d\n", ev.x, ev.y);
fflush(stdout);
}
//if the event was ConfigureNotify, which is usually reported when the window, for example, changes size...
if(e.type == ConfigureNotify) {
//call the resize() function
resize(app, e.xconfigure.width, e.xconfigure.height);
}
//if the event was ConfigureNotify, which is usually reported, when the window needs redrawing, because, for example, new area was exposed.
if(e.type == Expose) {
paint(app, out);
}
}
//because the infinite loop exited, close the app
exit_app(app);
return NULL;
}
void start(main_window* app, void (*text_update)(char*), float interval) {
//set few basic vars
app->text_update = text_update;
app->interval = interval;
//paint the app before any events occur, so its not just empty (not sure if required)
paint(app, "");
//start the update loop
pthread_t update_thread_id;
pthread_create(&update_thread_id, NULL, update, app);
//start the event loop
pthread_t event_thread_id;
pthread_create(&event_thread_id, NULL, event_loop, app);
}
// --------------
// TextBar v0.1.0
// --------------
// Usage: <text generator> | textbar | <input handler>
//
// Shows the last two lines from stdin on a bar
// Every odd line is on the left
// Every even line is on the right
// Updating one side at a time is not possible
// Line buffered
// Prints all mouse clicks to stdout
// In the format of `X Y` (without the '`')
#include <pthread.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <cairo.h>
#include <cairo-xlib.h>
Display* dpy;
Window win;
cairo_surface_t* sfc;
cairo_t* ctx;
char text[256] = {0};
char text_other[256] = {0};
int width;
// CONFIG
#define POSITION 0 // 1 means top, 0 means bottom. Blame C.
#define FONT "Fira Code"
#define FONT_SIZE 16
#define BG_COLOR 0.157, 0.165, 0.212, 0.950
#define FG_COLOR 0.973, 0.973, 0.949, 1.000
// COMPUTATION
#define HEIGHT FONT_SIZE*2
#define FONT_POS FONT_SIZE*1.4
void init() {
dpy = XOpenDisplay(NULL);
int screen = DefaultScreen(dpy);
width = DisplayWidth(dpy, screen);
XVisualInfo visualinfo;
XMatchVisualInfo(dpy, screen, 32, TrueColor, &visualinfo);
GC gc;
XSetWindowAttributes attr;
attr.colormap = XCreateColormap(dpy, DefaultRootWindow(dpy), visualinfo.visual, AllocNone);
attr.background_pixel = 0;
attr.border_pixel = 0;
win = XCreateWindow(dpy, DefaultRootWindow(dpy),
0, 0, width, HEIGHT,
0, visualinfo.depth, InputOutput, visualinfo.visual, CWBackPixel | CWColormap | CWBorderPixel, &attr);
gc = XCreateGC(dpy, win, 0, 0);
XSync(dpy, False);
Atom wm_type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
long wm_type_dock = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False);
XChangeProperty(dpy, win, wm_type,
XA_ATOM, 32, PropModeReplace, (unsigned char *) &wm_type_dock, 1);
Atom wm_strut = XInternAtom(dpy, "_NET_WM_STRUT", False);
#if POSITION == 0
XMoveWindow(dpy, win, 0, DisplayHeight(dpy, screen));
uint32_t strut[4] = {0, 0, 0, HEIGHT};
XChangeProperty(dpy, win, wm_strut,
XA_CARDINAL, 32, PropModeReplace, (unsigned char *) strut, 4);
#elif POSITION == 1
uint32_t strut[4] = {0, 0, HEIGHT, 0};
XChangeProperty(dpy, win, wm_strut,
XA_CARDINAL, 32, PropModeReplace, (unsigned char *) strut, 4);
XMoveWindow(dpy, win, 0, 0);
#endif
XMapWindow(dpy, win);
sfc = cairo_xlib_surface_create(dpy, win, visualinfo.visual, DisplayWidth(dpy, screen), HEIGHT);
ctx = cairo_create(sfc);
}
pthread_mutex_t do_paint;
void paint(Display* dpy) {
if(!pthread_mutex_trylock(&do_paint)) {
XClearWindow(dpy, win);
cairo_push_group(ctx);
cairo_set_source_rgba(ctx, BG_COLOR);
cairo_paint(ctx);
cairo_move_to(ctx, FONT_SIZE / 2, FONT_POS);
cairo_select_font_face(ctx, FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(ctx, FONT_SIZE);
cairo_set_source_rgba(ctx, FG_COLOR);
cairo_show_text(ctx, text);
cairo_text_extents_t extents;
cairo_text_extents(ctx, text_other, &extents);
cairo_move_to(ctx, (width-extents.width) - FONT_SIZE / 2, FONT_POS);
cairo_show_text(ctx, text_other);
cairo_pop_group_to_source(ctx);
cairo_paint(ctx);
cairo_surface_flush(sfc);
XFlush(dpy);
pthread_mutex_unlock(&do_paint);
}
}
void update() {
while(1) {
fgets(text, 256, stdin);
fgets(text_other, 256, stdin);
text[strcspn(text, "\n")] = 0;
text_other[strcspn(text_other, "\n")] = 0;
paint(dpy);
}
}
void x_loop() {
Display* x_dpy = XOpenDisplay(NULL);
XSelectInput(x_dpy, win, ButtonPressMask | StructureNotifyMask | ExposureMask);
while(1) {
XEvent e;
XNextEvent(x_dpy, &e);
if(e.type == ButtonPress) {
printf("%d %d\n", e.xbutton.x, e.xbutton.y);
fflush(stdout);
} else
if(e.type == ConfigureNotify) {
cairo_xlib_surface_set_size(sfc, e.xconfigure.width, e.xconfigure.height);
} else
if(e.type == Expose) {
paint(x_dpy);
}
}
}
int main() {
init();
pthread_mutex_init(&do_paint, NULL);
pthread_t x_thread_id;
pthread_create(&x_thread_id, NULL, &x_loop, NULL);
update();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment