Create a gist now

Instantly share code, notes, and snippets.

@Cloudef /lolisama.c
Last active Dec 10, 2015

What would you like to do?
/* gcc main.c -lxcb -lm $(pkg-config --libs --cflags pangocairo cairo) -o main
* I hate x11 */
#include <malloc.h>
#include <limits.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include <pango/pango.h>
#include <cairo/cairo-xcb.h>
#include <xcb/xcb.h>
#define LENGTH(x) (sizeof(x)/sizeof(*x))
#define XCB_EVENT_RESPONSE_TYPE_MASK (0x7f)
#define XCB_EVENT_RESPONSE_TYPE(e) (e->response_type & XCB_EVENT_RESPONSE_TYPE_MASK)
#define XCB_MOVE_RESIZE XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT
#define XCB_MOVE XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y
#define XCB_RESIZE XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT
static xcb_connection_t *xcb;
static xcb_window_t xcbw;
static xcb_screen_t *xcb_screen;
static cairo_surface_t *cairo_surface;
static cairo_t *cairo;
static PangoLayout *layout = NULL;
static int wx = 0, wy = 0, ww = 0, wh = 0;
enum {
XSEL_DATA,
ATOM,
INTEGER,
UTF8_STRING,
PRIMARY
};
static const char *natoms[] = {
"XSEL_DATA",
"ATOM",
"INTEGER"
};
static const char *textsel[] = {
"UTF8_STRING"
};
static const char *clips[] = {
"PRIMARY"
};
static xcb_atom_t atoms[LENGTH(natoms)+LENGTH(textsel)+LENGTH(clips)];
static int execget(char *bin, char *buffer, size_t len) {
FILE *p;
size_t read;
memset(buffer, 0, len);
if (!(p = popen(bin, "r")))
return 0;
read = fread(buffer, 1, len-1, p);
fclose(p);
if (read && buffer[read-1] == '\n')
buffer[read-1] = 0;
return read;
}
static xcb_generic_event_t* _xcb_wait_for_event(unsigned int seconds, unsigned int nanoseconds) {
int ret; fd_set fds;
unsigned int xfd;
struct timeval timeout;
xcb_generic_event_t *ev;
if ((ev = xcb_poll_for_event(xcb)))
return ev;
timeout.tv_sec = seconds; timeout.tv_usec = nanoseconds;
xfd = xcb_get_file_descriptor(xcb);
FD_ZERO(&fds); FD_SET(xfd, &fds);
if ((ret = select(xfd+1, &fds, 0, 0, &timeout)) == -1)
return NULL;
return xcb_poll_for_event(xcb);
}
static void* fetch_xsel(xcb_window_t win, xcb_atom_t property, xcb_atom_t type, size_t *len) {
xcb_get_property_reply_t *xsel = NULL;
void *data, *string = NULL; *len = 0;
if (!property) return NULL;
if (!(xsel = xcb_get_property_reply(xcb, xcb_get_property(xcb, 0,
win, property, type, 0, UINT_MAX), 0)))
return NULL;
if (!(data = xcb_get_property_value(xsel))) {
free(xsel);
return NULL;
}
if (!xsel->value_len) {
free(xsel);
return NULL;
}
if (!(string = malloc(xsel->value_len+1))) {
free(xsel);
return NULL;
}
memcpy(string, data, xsel->value_len);
memset(string+xsel->value_len, 0, 1);
*len = xsel->value_len; free(xsel);
xcb_delete_property(xcb, win, property);
return string;
}
static char *str_replace(const char *s, const char *old, const char *new)
{
size_t slen = strlen(s)+1;
char *cout=0, *p=0, *tmp=NULL; cout=malloc(slen); p=cout;
if (!p) return 0;
while (*s)
if (!strncmp(s, old, strlen(old))) {
p -= (intptr_t)cout;
cout = realloc(cout, slen += strlen(new)-strlen(old));
tmp = strcpy(p=cout+(intptr_t)p, new);
p += strlen(tmp);
s += strlen(old);
} else *p++=*s++;
*p=0;
return cout;
}
/* trim trailing and leading whitespace */
static char* trim_whitespace(char *buffer, size_t len, size_t *nlen) {
char *s =buffer; size_t lead = 0, trail = 0; *nlen = 0;
char *nbuffer = NULL;
for (; *s && isspace(*s); ++s) ++lead;
if (!*s) return NULL;
for (s = buffer+len-1; len && (isspace(*s) || *s == '\n'); --s, --len)
++trail;
if (len<=trail+lead)
return NULL;
*nlen = len-trail-lead+1;
if (!(nbuffer = malloc(*nlen+1)))
return NULL;
memcpy(nbuffer, buffer+lead, *nlen);
nbuffer[*nlen] = 0;
return nbuffer;
}
/* is data multiline? */
static int isml(char *buffer, size_t len) {
size_t i; char ml = 0;
for (i = 0; i != len; ++i) {
if (ml && (buffer[i] != '\n' && !isspace(buffer[i])))
return 1;
if (buffer[i] == '\n') ml = 1;
}
return 0;
}
/* is data binary? */
static int isbinary(char *buffer, size_t len) {
size_t i; char gotzero = 0;
for (i = 0; i != len; ++i) {
if (buffer[i] == 0) {
if (!gotzero) gotzero = 1;
else break;
}
}
return i<len;
}
static int init_clipboard_protocol(void) {
unsigned int i = 0;
xcb_intern_atom_reply_t *reply;
xcb_intern_atom_cookie_t cookies[LENGTH(natoms)];
xcb_intern_atom_cookie_t sel_cookies[LENGTH(textsel)];
xcb_intern_atom_cookie_t clip_cookies[LENGTH(clips)];
memset(atoms, XCB_NONE, LENGTH(atoms));
for (i = 0; i != LENGTH(natoms); ++i)
cookies[i] = xcb_intern_atom(xcb, 0, strlen(natoms[i]), natoms[i]);
for (i = 0; i != LENGTH(textsel); ++i)
sel_cookies[i] = xcb_intern_atom(xcb, 0, strlen(textsel[i]), textsel[i]);
for (i = 0; i != LENGTH(clips); ++i)
clip_cookies[i] = xcb_intern_atom(xcb, 0, strlen(clips[i]), clips[i]);
for (i = 0; i != LENGTH(natoms); ++i) {
if (!(reply = xcb_intern_atom_reply(xcb, cookies[i], NULL)))
continue;
atoms[i] = reply->atom;
printf("[%d] %s = 0x%x\n", i, natoms[i], reply->atom);
free(reply);
}
for (i = 0; i != LENGTH(textsel); ++i) {
if (!(reply = xcb_intern_atom_reply(xcb, sel_cookies[i], NULL)))
continue;
atoms[LENGTH(natoms)+i] = reply->atom;
printf("[%d] %s = 0x%x\n", LENGTH(natoms)+i, textsel[i], reply->atom);
free(reply);
}
for (i = 0; i != LENGTH(clips); ++i) {
if (!(reply = xcb_intern_atom_reply(xcb, clip_cookies[i], NULL)))
continue;
atoms[LENGTH(natoms)+LENGTH(textsel)+i] = reply->atom;
printf("[%d] %s = 0x%x\n", LENGTH(natoms)+LENGTH(textsel)+i, clips[i], reply->atom);
free(reply);
}
return 1;
}
static int init_window(void) {
xcbw = xcb_generate_id(xcb);
xcb_screen = xcb_setup_roots_iterator(xcb_get_setup(xcb)).data;
xcb_create_window(xcb, xcb_screen->root_depth, xcbw, xcb_screen->root,
0, 0, 1, 1, 0, XCB_COPY_FROM_PARENT, xcb_screen->root_visual,
XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK,
(unsigned int[]){xcb_screen->black_pixel, 1,
XCB_EVENT_MASK_PROPERTY_CHANGE|XCB_EVENT_MASK_EXPOSURE|XCB_EVENT_MASK_POINTER_MOTION});
xcb_map_window(xcb, xcbw);
xcb_visualtype_t *visual_type = NULL;
xcb_depth_iterator_t depth_iter;
depth_iter = xcb_screen_allowed_depths_iterator(xcb_screen);
for (; depth_iter.rem; xcb_depth_next(&depth_iter)) {
xcb_visualtype_iterator_t visual_iter;
visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) {
if (xcb_screen->root_visual == visual_iter.data->visual_id) {
visual_type = visual_iter.data;
break;
}
}
}
if (!visual_type)
return 0;
if (!(cairo_surface = cairo_xcb_surface_create(xcb, xcbw, visual_type, 1, 1)))
return 0;
if (!(cairo = cairo_create(cairo_surface)))
return 0;
return 1;
}
static int mousepos(int *x, int *y)
{
xcb_query_pointer_reply_t *pointer;
if (x) *x = 0; if (y) *y = 0;
if (!(pointer = xcb_query_pointer_reply(xcb, xcb_query_pointer(xcb, xcb_screen->root), 0)))
return 0;
*x = pointer->root_x; *y = pointer->root_y;
return 1;
}
static void fit_window_to_layout(PangoLayout *layout)
{
int x = 0, y = 0, w = 0, h = 0;
mousepos(&x, &y);
pango_layout_get_pixel_size(layout, &w, &h);
unsigned int size[2] = {w+20, h+20};
int pos[2] = {x-w/2+20, y+10};
if (pos[0]+size[0] > xcb_screen->width_in_pixels)
pos[0] -= (pos[0]+size[0])-xcb_screen->width_in_pixels;
if (pos[0] < 0) pos[0] = 0;
if (pos[1]+size[1] > xcb_screen->height_in_pixels)
pos[1] = y-10-h-20;
xcb_configure_window(xcb, xcbw, XCB_RESIZE, size);
xcb_configure_window(xcb, xcbw, XCB_MOVE, pos);
cairo_xcb_surface_set_size(cairo_surface, size[0], size[1]);
xcb_map_window(xcb, xcbw);
xcb_configure_window(xcb, xcbw, XCB_CONFIG_WINDOW_STACK_MODE, (unsigned int[]){XCB_STACK_MODE_ABOVE});
wx = pos[0]; wy = pos[1];
ww = size[0]; wh = size[1];
}
static void clear()
{
cairo_set_source_rgb(cairo, 0, 0, 0);
cairo_paint(cairo);
}
static void show_layout(PangoLayout *layout)
{
fit_window_to_layout(layout);
cairo_move_to(cairo, 0, 0);
clear();
cairo_move_to(cairo, 10, 10);
cairo_set_source_rgb(cairo, 1.0, 1.0, 1.0);
pango_cairo_update_layout(cairo, layout);
pango_cairo_show_layout(cairo, layout);
xcb_flush(xcb);
}
static void draw_text(const char *text)
{
PangoFontDescription *desc;
char buffer[2048];
char cmd[256];
char *stripped;
if (!(stripped = str_replace(text, "'", "'\\''"))) {
xcb_unmap_window(xcb, xcbw);
return;
}
snprintf(cmd, sizeof(cmd)-1, "./callback.sh '%s'", stripped);
free(stripped);
execget(cmd, buffer, sizeof(buffer));
if (!strlen(buffer)) {
xcb_unmap_window(xcb, xcbw);
return;
}
if (layout) g_object_unref(layout);
layout = pango_cairo_create_layout(cairo);
pango_layout_set_width(layout, 800 * PANGO_SCALE);
pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
pango_layout_set_text(layout, buffer, -1);
desc = pango_font_description_from_string("Monospace 9");
pango_layout_set_font_description(layout, desc);
pango_font_description_free(desc);
show_layout(layout);
}
int main(int argc, const char *argv[])
{
int mx, my, lx = 0, ly = 0, sx = 0, sy = 0, drawn = 1;
int mousewait = 0;
size_t len, nlen;
char *string, *lstring = NULL, *tmp;
xcb_get_property_reply_t *reply;
xcb_generic_event_t *ev;
xcb_selection_notify_event_t *e;
if (!(xcb = xcb_connect(NULL, NULL)))
goto xcb_fail;
if (xcb_connection_has_error(xcb))
goto xcb_fail;
if (!init_window())
goto window_fail;
xcb_unmap_window(xcb, xcbw);
if (!(init_clipboard_protocol()))
goto clipboard_fail;
xcb_flush(xcb);
while (!xcb_connection_has_error(xcb)) {
while ((ev = _xcb_wait_for_event(0, 5000 * 25))) {
switch (XCB_EVENT_RESPONSE_TYPE(ev)) {
case XCB_EXPOSE:
if (layout) show_layout(layout);
break;
case XCB_SELECTION_NOTIFY:
e = (xcb_selection_notify_event_t*)ev;
if ((reply = xcb_get_property_reply(xcb, xcb_get_property(xcb, 0, e->requestor, e->property,
XCB_GET_PROPERTY_TYPE_ANY, 0, UINT_MAX), NULL))) {
if ((string = fetch_xsel(e->requestor, e->property, e->target, &len))) {
if (!lstring || strcmp(string, lstring)) {
if (lstring) {
drawn = 0;
free(lstring);
}
lstring = strdup(string);
} else if (lstring && !drawn && !isbinary(string, strlen(string)) && !isml(string, strlen(string))) {
if ((tmp = trim_whitespace(string, strlen(string), &nlen))) {
free(string); string = tmp;
}
if (strlen(string)) {
printf("PRIMARY: %s\n", string);
mousepos(&sx, &sy);
draw_text(string);
}
drawn = 1;
}
free(string);
string = NULL;
}
free(reply);
}
break;
}
free(ev);
}
if (mousepos(&mx, &my) && sx && sy) {
if (!(mx > wx && my > wy && mx < wx+ww && my < wy+wh)) {
float dx = mx-sx;
float dy = my-sy;
if (sqrtf(dx*dx + dy*dy) > 80) {
xcb_unmap_window(xcb, xcbw);
sx = sy = 0;
}
}
}
if (mx != lx || my != ly) mousewait = 5;
if (mousewait && --mousewait) {
xcb_convert_selection(xcb, xcbw, atoms[PRIMARY], atoms[UTF8_STRING], atoms[XSEL_DATA], XCB_CURRENT_TIME);
xcb_flush(xcb);
}
lx = mx; ly = my;
}
if (lstring) free(lstring);
cairo_destroy(cairo);
xcb_disconnect(xcb);
return 0;
clipboard_fail:
cairo_destroy(cairo);
window_fail:
xcb_disconnect(xcb);
xcb_fail:
return 1;
}
/* vim: set ts=8 sw=3 tw=0 :*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment