lemonbar/bar.c
Otto Modinos 7d2c7ab438 Allow having clickable areas inside another
Previously, if you started several areas, one inside another, only the
inermost one would get registered and eventually triggered. This patch
fixes that, allowing you to use multiple areas around text to respond to
several different buttons, for example, both scroll-up and scroll-down.

If you define two areas that respond to the same button, only the
innermost one would get triggered. I think this makes sense.
2015-02-11 14:51:10 +02:00

1232 lines
35 KiB
C

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <poll.h>
#include <getopt.h>
#include <unistd.h>
#include <errno.h>
#include <xcb/xcb.h>
#include <xcb/xinerama.h>
#include <xcb/randr.h>
// Here be dragons
#define max(a,b) ((a) > (b) ? (a) : (b))
#define min(a,b) ((a) < (b) ? (a) : (b))
#define indexof(c,s) (strchr((s),(c))-(s))
typedef struct font_t {
xcb_font_t ptr;
int descent, height;
uint16_t char_max;
uint16_t char_min;
xcb_charinfo_t *width_lut;
} font_t;
typedef struct monitor_t {
int x, y, width;
xcb_window_t window;
xcb_pixmap_t pixmap;
struct monitor_t *prev, *next;
} monitor_t;
typedef struct area_t {
bool active;
int begin, end, align, button;
xcb_window_t window;
char *cmd;
} area_t;
#define N 20
typedef struct area_stack_t {
int pos;
area_t slot[N];
} area_stack_t;
enum {
ATTR_OVERL = (1<<0),
ATTR_UNDERL = (1<<1),
};
enum {
ALIGN_L = 0,
ALIGN_C,
ALIGN_R
};
enum {
GC_DRAW = 0,
GC_CLEAR,
GC_ATTR,
GC_MAX
};
#define MAX_FONT_COUNT 5
static xcb_connection_t *c;
static xcb_screen_t *scr;
static xcb_gcontext_t gc[GC_MAX];
static xcb_visualid_t visual;
static xcb_colormap_t colormap;
static monitor_t *monhead, *montail;
static font_t *font_list[MAX_FONT_COUNT];
static int font_count = 0;
static int font_index = -1;
static uint32_t attrs = 0;
static bool dock = false;
static bool topbar = true;
static int bw = -1, bh = -1, bx = 0, by = 0;
static int bu = 1; // Underline height
static uint32_t fgc, bgc, ugc;
static uint32_t dfgc, dbgc;
static area_stack_t astack;
void
update_gc (void)
{
xcb_change_gc(c, gc[GC_DRAW], XCB_GC_BACKGROUND | XCB_GC_FOREGROUND, (const uint32_t []){ fgc, bgc });
xcb_change_gc(c, gc[GC_CLEAR], XCB_GC_FOREGROUND, (const uint32_t []){ bgc });
xcb_change_gc(c, gc[GC_ATTR], XCB_GC_FOREGROUND, (const uint32_t []){ ugc });
}
void
fill_rect (xcb_drawable_t d, xcb_gcontext_t gc, int x, int y, int width, int height)
{
xcb_poly_fill_rectangle(c, d, gc, 1, (const xcb_rectangle_t []){ { x, y, width, height } });
}
int
draw_char (monitor_t *mon, font_t *cur_font, int x, int align, uint16_t ch)
{
int ch_width = cur_font->width_lut[ch - cur_font->char_min].character_width;
switch (align) {
case ALIGN_C:
xcb_copy_area(c, mon->pixmap, mon->pixmap, gc[GC_DRAW], mon->width / 2 - x / 2, 0,
mon->width / 2 - (x + ch_width) / 2, 0, x, bh);
x = mon->width / 2 - (x + ch_width) / 2 + x;
break;
case ALIGN_R:
xcb_copy_area(c, mon->pixmap, mon->pixmap, gc[GC_DRAW], mon->width - x, 0,
mon->width - x - ch_width, 0, x, bh);
x = mon->width - ch_width;
break;
}
// Draw the background first
fill_rect(mon->pixmap, gc[GC_CLEAR], x, by, ch_width, bh);
// xcb accepts string in UCS-2 BE, so swap
ch = (ch >> 8) | (ch << 8);
// String baseline coordinates
xcb_image_text_16(c, 1, mon->pixmap, gc[GC_DRAW], x, bh / 2 + cur_font->height / 2 - cur_font->descent, (xcb_char2b_t *)&ch);
// We can render both at the same time
if (attrs & ATTR_OVERL)
fill_rect(mon->pixmap, gc[GC_ATTR], x, 0, ch_width, bu);
if (attrs & ATTR_UNDERL)
fill_rect(mon->pixmap, gc[GC_ATTR], x, bh - bu, ch_width, bu);
return ch_width;
}
uint32_t
parse_color (const char *str, char **end, const uint32_t def)
{
xcb_alloc_named_color_reply_t *nc_reply;
int str_len;
uint32_t ret;
if (!str)
return def;
// Reset
if (str[0] == '-') {
if (end)
*end = (char *)str + 1;
return def;
}
// Hex representation
if (str[0] == '#') {
errno = 0;
uint32_t tmp = strtoul(str + 1, end, 16);
// Some error checking is definitely good
if (errno)
tmp = def;
// Xorg uses colors with premultiplied alpha
unsigned int a = (tmp&0xff000000) >> 24;
unsigned int r = (tmp&0x00ff0000) >> 16;
unsigned int g = (tmp&0x0000ff00) >> 8;
unsigned int b = (tmp&0x000000ff);
if (a) {
r = (r * a) / 255;
g = (g * a) / 255;
b = (b * a) / 255;
// Clamp on overflow
if (r > 255) r = 255;
if (g > 255) g = 255;
if (b > 255) b = 255;
} else
r = g = b = 0;
return a << 24 | r << 16 | g << 8 | b;
}
// Actual color name, resolve it
str_len = 0;
while (isalpha(str[str_len]))
str_len++;
nc_reply = xcb_alloc_named_color_reply(c, xcb_alloc_named_color(c, colormap, str_len, str), NULL);
if (!nc_reply)
fprintf(stderr, "Could not alloc color \"%.*s\"\n", str_len, str);
ret = (nc_reply) ? nc_reply->pixel : def;
free(nc_reply);
if (end)
*end = (char *)str + str_len;
return ret;
}
void
set_attribute (const char modifier, const char attribute)
{
int pos = indexof(attribute, "ou");
if (pos < 0) {
fprintf(stderr, "Invalid attribute \"%c\" found\n", attribute);
return;
}
switch (modifier) {
case '+': attrs |= (1<<pos); break;
case '-': attrs &=~(1<<pos); break;
case '!': attrs ^= (1<<pos); break;
}
}
area_t *
area_get (xcb_window_t win, const int btn, const int x)
{
/* Looping backwards ensures that we get the innermost area first */
for (int i = astack.pos; i >= 0; i--) {
area_t *a = &astack.slot[i];
if (a->window == win && a->button == btn
&& x >= a->begin && x < a->end)
return a;
}
return NULL;
}
void
area_shift (xcb_window_t win, const int align, int delta)
{
if (align == ALIGN_L)
return;
if (align == ALIGN_C)
delta /= 2;
for (int i = 0; i < astack.pos; i++) {
if (astack.slot[i].window == win && astack.slot[i].align == align) {
astack.slot[i].begin -= delta;
astack.slot[i].end -= delta;
}
}
}
bool
area_add (char *str, const char *optend, char **end, monitor_t *mon, const int x, const int align, const int button)
{
char *p = str;
char *trail;
area_t *a;
// A wild close area tag appeared!
if (*p != ':') {
*end = p;
/* Find most recent unclosed area. */
int i;
for (i = astack.pos - 1; i >= 0 && !astack.slot[i].active; i--)
;
a = &astack.slot[i];
// Basic safety checks
if (!a->cmd || a->align != align || a->window != mon->window)
return false;
const int size = x - a->begin;
switch (align) {
case ALIGN_L:
a->end = x;
break;
case ALIGN_C:
a->begin = mon->width / 2 - size / 2 + a->begin / 2;
a->end = a->begin + size;
break;
case ALIGN_R:
// The newest is the rightmost one
a->begin = mon->width - size;
a->end = mon->width;
break;
}
a->active = false;
return true;
}
if (astack.pos >= N) {
fprintf(stderr, "astack overflow!\n");
return false;
}
a = &astack.slot[astack.pos++];
// Found the closing : and check if it's just an escaped one
for (trail = strchr(++p, ':'); trail && trail[-1] == '\\'; trail = strchr(trail + 1, ':'))
;
// Find the trailing : and make sure it's whitin the formatting block, also reject empty commands
if (!trail || p == trail || trail > optend) {
*end = p;
return false;
}
*trail = '\0';
// Sanitize the user command by unescaping all the :
for (char *needle = p; *needle; needle++) {
int delta = trail - &needle[1];
if (needle[0] == '\\' && needle[1] == ':') {
memmove(&needle[0], &needle[1], delta);
needle[delta] = 0;
}
}
// This is a pointer to the string buffer allocated in the main
a->cmd = p;
a->active = true;
a->align = align;
a->begin = x;
a->window = mon->window;
a->button = button;
*end = trail + 1;
return true;
}
bool
font_has_glyph (font_t *font, const uint16_t c)
{
return (c >= font->char_min &&
c <= font->char_max &&
font->width_lut &&
font->width_lut[c - font->char_min].character_width);
}
// returns NULL if character cannot be printed
font_t *
select_drawable_font (const uint16_t c)
{
// If the user has specified a font to use, try that first.
if (font_index != -1 && font_has_glyph(font_list[font_index - 1], c))
return font_list[font_index - 1];
// If the end is reached without finding an apropriate font, return NULL.
// If the font can draw the character, return it.
for (int i = 0; i < font_count; i++) {
if (font_has_glyph(font_list[i], c))
return font_list[i];
}
return NULL;
}
void
parse (char *text)
{
font_t *cur_font;
monitor_t *cur_mon;
int pos_x, align, button;
char *p = text, *end;
uint32_t tmp;
pos_x = 0;
align = ALIGN_L;
cur_mon = monhead;
memset(&astack, 0, sizeof(area_stack_t));
for (monitor_t *m = monhead; m != NULL; m = m->next)
fill_rect(m->pixmap, gc[GC_CLEAR], 0, 0, m->width, bh);
for (;;) {
if (*p == '\0' || *p == '\n')
return;
if (*p == '%' && p++ && *p == '{' && (end = strchr(p++, '}'))) {
while (p < end) {
while (isspace(*p))
p++;
switch (*p++) {
case '+': set_attribute('+', *p++); break;
case '-': set_attribute('-', *p++); break;
case '!': set_attribute('!', *p++); break;
case 'R':
tmp = fgc;
fgc = bgc;
bgc = tmp;
update_gc();
break;
case 'l': pos_x = 0; align = ALIGN_L; break;
case 'c': pos_x = 0; align = ALIGN_C; break;
case 'r': pos_x = 0; align = ALIGN_R; break;
case 'A':
button = XCB_BUTTON_INDEX_1;
// The range is 1-5
if (isdigit(*p) && (*p > '0' && *p < '6'))
button = *p++ - '0';
area_add(p, end, &p, cur_mon, pos_x, align, button);
break;
case 'B': bgc = parse_color(p, &p, dbgc); update_gc(); break;
case 'F': fgc = parse_color(p, &p, dfgc); update_gc(); break;
case 'U': ugc = parse_color(p, &p, dbgc); update_gc(); break;
case 'S':
if (*p == '+' && cur_mon->next)
{ cur_mon = cur_mon->next; }
else if (*p == '-' && cur_mon->prev)
{ cur_mon = cur_mon->prev; }
else if (*p == 'f')
{ cur_mon = monhead; }
else if (*p == 'l')
{ cur_mon = montail ? montail : monhead; }
else if (isdigit(*p))
{ cur_mon = monhead;
for (int i = 0; i != *p-'0' && cur_mon->next; i++)
cur_mon = cur_mon->next;
}
else
{ p++; continue; }
p++;
pos_x = 0;
break;
case 'T':
font_index = (int)strtoul(p, NULL, 10);
// User-specified 'font_index' ∊ (0,font_count]
// Otherwise just fallback to the automatic font selection
if (!font_index || font_index > font_count)
font_index = -1;
p = end;
break;
// In case of error keep parsing after the closing }
default:
p = end;
}
}
// Eat the trailing }
p++;
} else { // utf-8 -> ucs-2
uint8_t *utf = (uint8_t *)p;
uint16_t ucs;
// ASCII
if (utf[0] < 0x80) {
ucs = utf[0];
p += 1;
}
// Two byte utf8 sequence
else if ((utf[0] & 0xe0) == 0xc0) {
ucs = (utf[0] & 0x1f) << 6 | (utf[1] & 0x3f);
p += 2;
}
// Three byte utf8 sequence
else if ((utf[0] & 0xf0) == 0xe0) {
ucs = (utf[0] & 0xf) << 12 | (utf[1] & 0x3f) << 6 | (utf[2] & 0x3f);
p += 3;
}
// Four byte utf8 sequence
else if ((utf[0] & 0xf8) == 0xf0) {
ucs = 0xfffd;
p += 4;
}
// Five byte utf8 sequence
else if ((utf[0] & 0xfc) == 0xf8) {
ucs = 0xfffd;
p += 5;
}
// Siz byte utf8 sequence
else if ((utf[0] & 0xfe) == 0xfc) {
ucs = 0xfffd;
p += 6;
}
// Not a valid utf-8 sequence
else {
ucs = utf[0];
p += 1;
}
cur_font = select_drawable_font(ucs);
if (!cur_font)
continue;
xcb_change_gc(c, gc[GC_DRAW] , XCB_GC_FONT, (const uint32_t []){ cur_font->ptr });
int w = draw_char(cur_mon, cur_font, pos_x, align, ucs);
pos_x += w;
area_shift(cur_mon->window, align, w);
}
}
}
font_t *
font_load (const char *str)
{
xcb_query_font_cookie_t queryreq;
xcb_query_font_reply_t *font_info;
xcb_void_cookie_t cookie;
xcb_font_t font;
font = xcb_generate_id(c);
cookie = xcb_open_font_checked(c, font, strlen(str), str);
if (xcb_request_check (c, cookie)) {
fprintf(stderr, "Could not load font %s\n", str);
return NULL;
}
font_t *ret = calloc(1, sizeof(font_t));
if (!ret)
return NULL;
queryreq = xcb_query_font(c, font);
font_info = xcb_query_font_reply(c, queryreq, NULL);
ret->ptr = font;
ret->descent = font_info->font_descent;
ret->height = font_info->font_ascent + font_info->font_descent;
ret->char_max = font_info->max_byte1 << 8 | font_info->max_char_or_byte2;
ret->char_min = font_info->min_byte1 << 8 | font_info->min_char_or_byte2;
// Copy over the width lut as it's part of font_info
int lut_size = sizeof(xcb_charinfo_t) * xcb_query_font_char_infos_length(font_info);
if (lut_size) {
ret->width_lut = malloc(lut_size);
memcpy(ret->width_lut, xcb_query_font_char_infos(font_info), lut_size);
}
free(font_info);
return ret;
}
enum {
NET_WM_WINDOW_TYPE,
NET_WM_WINDOW_TYPE_DOCK,
NET_WM_DESKTOP,
NET_WM_STRUT_PARTIAL,
NET_WM_STRUT,
NET_WM_STATE,
NET_WM_STATE_STICKY,
NET_WM_STATE_ABOVE,
};
void
set_ewmh_atoms (void)
{
const char *atom_names[] = {
"_NET_WM_WINDOW_TYPE",
"_NET_WM_WINDOW_TYPE_DOCK",
"_NET_WM_DESKTOP",
"_NET_WM_STRUT_PARTIAL",
"_NET_WM_STRUT",
"_NET_WM_STATE",
// Leave those at the end since are batch-set
"_NET_WM_STATE_STICKY",
"_NET_WM_STATE_ABOVE",
};
const int atoms = sizeof(atom_names)/sizeof(char *);
xcb_intern_atom_cookie_t atom_cookie[atoms];
xcb_atom_t atom_list[atoms];
xcb_intern_atom_reply_t *atom_reply;
// As suggested fetch all the cookies first (yum!) and then retrieve the
// atoms to exploit the async'ness
for (int i = 0; i < atoms; i++)
atom_cookie[i] = xcb_intern_atom(c, 0, strlen(atom_names[i]), atom_names[i]);
for (int i = 0; i < atoms; i++) {
atom_reply = xcb_intern_atom_reply(c, atom_cookie[i], NULL);
if (!atom_reply)
return;
atom_list[i] = atom_reply->atom;
free(atom_reply);
}
// Prepare the strut array
for (monitor_t *mon = monhead; mon; mon = mon->next) {
int strut[12] = {0};
if (topbar) {
strut[2] = bh;
strut[8] = mon->x;
strut[9] = mon->x + mon->width;
} else {
strut[3] = bh;
strut[10] = mon->x;
strut[11] = mon->x + mon->width;
}
xcb_change_property(c, XCB_PROP_MODE_REPLACE, mon->window, atom_list[NET_WM_WINDOW_TYPE], XCB_ATOM_ATOM, 32, 1, &atom_list[NET_WM_WINDOW_TYPE_DOCK]);
xcb_change_property(c, XCB_PROP_MODE_APPEND, mon->window, atom_list[NET_WM_STATE], XCB_ATOM_ATOM, 32, 2, &atom_list[NET_WM_STATE_STICKY]);
xcb_change_property(c, XCB_PROP_MODE_REPLACE, mon->window, atom_list[NET_WM_DESKTOP], XCB_ATOM_CARDINAL, 32, 1, (const uint32_t []){ -1 } );
xcb_change_property(c, XCB_PROP_MODE_REPLACE, mon->window, atom_list[NET_WM_STRUT_PARTIAL], XCB_ATOM_CARDINAL, 32, 12, strut);
xcb_change_property(c, XCB_PROP_MODE_REPLACE, mon->window, atom_list[NET_WM_STRUT], XCB_ATOM_CARDINAL, 32, 4, strut);
xcb_change_property(c, XCB_PROP_MODE_REPLACE, mon->window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, 3, "bar");
}
}
monitor_t *
monitor_new (int x, int y, int width, int height)
{
monitor_t *ret;
ret = calloc(1, sizeof(monitor_t));
if (!ret) {
fprintf(stderr, "Failed to allocate new monitor\n");
exit(EXIT_FAILURE);
}
ret->x = x;
ret->y = (topbar ? by : height - bh - by) + y;
ret->width = width;
ret->next = ret->prev = NULL;
ret->window = xcb_generate_id(c);
int depth = (visual == scr->root_visual) ? XCB_COPY_FROM_PARENT : 32;
xcb_create_window(c, depth, ret->window, scr->root,
ret->x, ret->y, width, bh, 0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, visual,
XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP,
(const uint32_t []){ bgc, bgc, dock, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS, colormap });
ret->pixmap = xcb_generate_id(c);
xcb_create_pixmap(c, depth, ret->pixmap, ret->window, width, bh);
return ret;
}
void
monitor_add (monitor_t *mon)
{
if (!monhead) {
monhead = mon;
} else if (!montail) {
montail = mon;
monhead->next = mon;
mon->prev = monhead;
} else {
mon->prev = montail;
montail->next = mon;
montail = montail->next;
}
}
int
rect_sort_cb (const void *p1, const void *p2)
{
const xcb_rectangle_t *r1 = (xcb_rectangle_t *)p1;
const xcb_rectangle_t *r2 = (xcb_rectangle_t *)p2;
if (r1->x < r2->x || r1->y < r2->y)
return -1;
if (r1->x > r2->x || r1->y > r2->y)
return 1;
return 0;
}
void
monitor_create_chain (xcb_rectangle_t *rects, const int num)
{
int i;
int width = 0, height = 0;
int left = bx;
// Sort before use
qsort(rects, num, sizeof(xcb_rectangle_t), rect_sort_cb);
for (i = 0; i < num; i++) {
int h = rects[i].y + rects[i].height;
// Accumulated width of all monitors
width += rects[i].width;
// Get height of screen from y_offset + height of lowest monitor
if (h >= height)
height = h;
}
if (bw < 0)
bw = width - bx;
// Use the first font height as all the font heights have been set to the biggest of the set
if (bh < 0 || bh > height)
bh = font_list[0]->height + bu + 2;
// Check the geometry
if (bx + bw > width || by + bh > height) {
fprintf(stderr, "The geometry specified doesn't fit the screen!\n");
exit(EXIT_FAILURE);
}
// Left is a positive number or zero therefore monitors with zero width are excluded
width = bw;
for (i = 0; i < num; i++) {
if (rects[i].y + rects[i].height < by)
continue;
if (rects[i].width > left) {
monitor_t *mon = monitor_new(
rects[i].x + left,
rects[i].y,
min(width, rects[i].width - left),
rects[i].height);
monitor_add(mon);
width -= rects[i].width - left;
// No need to check for other monitors
if (width <= 0)
break;
}
left -= rects[i].width;
if (left < 0)
left = 0;
}
}
void
get_randr_monitors (void)
{
xcb_randr_get_screen_resources_current_reply_t *rres_reply;
xcb_randr_output_t *outputs;
int i, j, num, valid = 0;
rres_reply = xcb_randr_get_screen_resources_current_reply(c,
xcb_randr_get_screen_resources_current(c, scr->root), NULL);
if (!rres_reply) {
fprintf(stderr, "Failed to get current randr screen resources\n");
return;
}
num = xcb_randr_get_screen_resources_current_outputs_length(rres_reply);
outputs = xcb_randr_get_screen_resources_current_outputs(rres_reply);
// There should be at least one output
if (num < 1) {
free(rres_reply);
return;
}
xcb_rectangle_t rects[num];
// Get all outputs
for (i = 0; i < num; i++) {
xcb_randr_get_output_info_reply_t *oi_reply;
xcb_randr_get_crtc_info_reply_t *ci_reply;
oi_reply = xcb_randr_get_output_info_reply(c, xcb_randr_get_output_info(c, outputs[i], XCB_CURRENT_TIME), NULL);
// Output disconnected or not attached to any CRTC ?
if (!oi_reply || oi_reply->crtc == XCB_NONE || oi_reply->connection != XCB_RANDR_CONNECTION_CONNECTED) {
free(oi_reply);
rects[i].width = 0;
continue;
}
ci_reply = xcb_randr_get_crtc_info_reply(c,
xcb_randr_get_crtc_info(c, oi_reply->crtc, XCB_CURRENT_TIME), NULL);
free(oi_reply);
if (!ci_reply) {
fprintf(stderr, "Failed to get RandR ctrc info\n");
free(rres_reply);
return;
}
// There's no need to handle rotated screens here (see #69)
rects[i] = (xcb_rectangle_t){ ci_reply->x, ci_reply->y, ci_reply->width, ci_reply->height };
free(ci_reply);
valid++;
}
free(rres_reply);
// Check for clones and inactive outputs
for (i = 0; i < num; i++) {
if (rects[i].width == 0)
continue;
for (j = 0; j < num; j++) {
// Does I countain J ?
if (i != j && rects[j].width) {
if (rects[j].x >= rects[i].x && rects[j].x + rects[j].width <= rects[i].x + rects[i].width &&
rects[j].y >= rects[i].y && rects[j].y + rects[j].height <= rects[i].y + rects[i].height) {
rects[j].width = 0;
valid--;
}
}
}
}
if (valid < 1) {
fprintf(stderr, "No usable RandR output found\n");
return;
}
xcb_rectangle_t r[valid];
for (i = j = 0; i < num && j < valid; i++)
if (rects[i].width != 0)
r[j++] = rects[i];
monitor_create_chain(r, valid);
}
void
get_xinerama_monitors (void)
{
xcb_xinerama_query_screens_reply_t *xqs_reply;
xcb_xinerama_screen_info_iterator_t iter;
int screens;
xqs_reply = xcb_xinerama_query_screens_reply(c,
xcb_xinerama_query_screens_unchecked(c), NULL);
iter = xcb_xinerama_query_screens_screen_info_iterator(xqs_reply);
screens = iter.rem;
xcb_rectangle_t rects[screens];
// Fetch all the screens first
for (int i = 0; iter.rem; i++) {
rects[i].x = iter.data->x_org;
rects[i].y = iter.data->y_org;
rects[i].width = iter.data->width;
rects[i].height = iter.data->height;
xcb_xinerama_screen_info_next(&iter);
}
free(xqs_reply);
monitor_create_chain(rects, screens);
}
xcb_visualid_t
get_visual (void)
{
xcb_depth_iterator_t iter;
iter = xcb_screen_allowed_depths_iterator(scr);
// Try to find a RGBA visual
while (iter.rem) {
xcb_visualtype_t *vis = xcb_depth_visuals(iter.data);
if (iter.data->depth == 32)
return vis->visual_id;
xcb_depth_next(&iter);
}
// Fallback to the default one
return scr->root_visual;
}
// Parse an X-styled geometry string, we don't support signed offsets tho.
bool
parse_geometry_string (char *str, int *tmp)
{
char *p = str;
int i = 0, j;
if (!str || !str[0])
return false;
// The leading = is optional
if (*p == '=')
p++;
while (*p) {
// A geometry string has only 4 fields
if (i >= 4) {
fprintf(stderr, "Invalid geometry specified\n");
return false;
}
// Move on if we encounter a 'x' or '+'
if (*p == 'x') {
if (i > 0) // The 'x' must precede '+'
break;
i++; p++; continue;
}
if (*p == '+') {
if (i < 1) // Stray '+', skip the first two fields
i = 2;
else
i++;
p++; continue;
}
// A digit must follow
if (!isdigit(*p)) {
fprintf(stderr, "Invalid geometry specified\n");
return false;
}
// Try to parse the number
errno = 0;
j = strtoul(p, &p, 10);
if (errno) {
fprintf(stderr, "Invalid geometry specified\n");
return false;
}
tmp[i] = j;
}
return true;
}
void
parse_font_list (char *str)
{
char *tok;
if (!str)
return;
tok = strtok(str, ",");
while (tok) {
if (font_count > MAX_FONT_COUNT - 1) {
fprintf(stderr, "Too many fonts; maximum %i\n", MAX_FONT_COUNT);
return;
}
// Load the selected font
font_t *font = font_load(tok);
if (font)
font_list[font_count++] = font;
else
fprintf(stderr, "Could not load font \"%s\"...skipping\n", tok);
tok = strtok(NULL, ",");
}
}
void
xconn (void)
{
// Connect to X
c = xcb_connect (NULL, NULL);
if (xcb_connection_has_error(c)) {
fprintf(stderr, "Couldn't connect to X\n");
exit(EXIT_FAILURE);
}
// Grab infos from the first screen
scr = xcb_setup_roots_iterator(xcb_get_setup(c)).data;
// Try to get a RGBA visual and build the colormap for that
visual = get_visual();
colormap = xcb_generate_id(c);
xcb_create_colormap(c, XCB_COLORMAP_ALLOC_NONE, colormap, scr->root, visual);
}
void
init (void)
{
// This has to be declared as an array because otherwise the compiler would turn it into a const
// string, making strtok choke very hard on this
char fallback_font[] = "fixed";
// Try to load a default font
if (!font_count)
parse_font_list(fallback_font);
// We tried and failed hard, there's something wrong
if (!font_count)
exit(EXIT_FAILURE);
// To make the alignment uniform, find maximum height
int maxh = font_list[0]->height;
for (int i = 1; i < font_count; i++)
maxh = max(maxh, font_list[i]->height);
// Set maximum height to all fonts
for (int i = 0; i < font_count; i++)
font_list[i]->height = maxh;
// Generate a list of screens
const xcb_query_extension_reply_t *qe_reply;
// Initialiaze monitor list head and tail
monhead = montail = NULL;
// Check if RandR is present
qe_reply = xcb_get_extension_data(c, &xcb_randr_id);
if (qe_reply && qe_reply->present) {
get_randr_monitors();
} else {
qe_reply = xcb_get_extension_data(c, &xcb_xinerama_id);
// Check if Xinerama extension is present and active
if (qe_reply && qe_reply->present) {
xcb_xinerama_is_active_reply_t *xia_reply;
xia_reply = xcb_xinerama_is_active_reply(c, xcb_xinerama_is_active(c), NULL);
if (xia_reply && xia_reply->state)
get_xinerama_monitors();
free(xia_reply);
}
}
if (!monhead) {
// If I fits I sits
if (bw < 0)
bw = scr->width_in_pixels - bx;
// Adjust the height
if (bh < 0 || bh > scr->height_in_pixels)
bh = maxh + bu + 2;
// Check the geometry
if (bx + bw > scr->width_in_pixels || by + bh > scr->height_in_pixels) {
fprintf(stderr, "The geometry specified doesn't fit the screen!\n");
exit(EXIT_FAILURE);
}
// If no RandR outputs or Xinerama screens, fall back to using whole screen
monhead = monitor_new(0, 0, bw, scr->height_in_pixels);
}
if (!monhead)
exit(EXIT_FAILURE);
// For WM that support EWMH atoms
set_ewmh_atoms();
// Create the gc for drawing
gc[GC_DRAW] = xcb_generate_id(c);
xcb_create_gc(c, gc[GC_DRAW], monhead->pixmap, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, (const uint32_t []){ fgc, bgc });
gc[GC_CLEAR] = xcb_generate_id(c);
xcb_create_gc(c, gc[GC_CLEAR], monhead->pixmap, XCB_GC_FOREGROUND, (const uint32_t []){ bgc });
gc[GC_ATTR] = xcb_generate_id(c);
xcb_create_gc(c, gc[GC_ATTR], monhead->pixmap, XCB_GC_FOREGROUND, (const uint32_t []){ ugc });
// Make the bar visible and clear the pixmap
for (monitor_t *mon = monhead; mon; mon = mon->next) {
fill_rect(mon->pixmap, gc[GC_CLEAR], 0, 0, mon->width, bh);
xcb_map_window(c, mon->window);
// Make sure that the window really gets in the place it's supposed to be
// Some WM such as Openbox need this
xcb_configure_window(c, mon->window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, (const uint32_t []){ mon->x, mon->y });
}
xcb_flush(c);
}
void
cleanup (void)
{
for (int i = 0; i < font_count; i++) {
xcb_close_font(c, font_list[i]->ptr);
free(font_list[i]->width_lut);
free(font_list[i]);
}
while (monhead) {
monitor_t *next = monhead->next;
xcb_destroy_window(c, monhead->window);
xcb_free_pixmap(c, monhead->pixmap);
free(monhead);
monhead = next;
}
xcb_free_colormap(c, colormap);
if (gc[GC_DRAW])
xcb_free_gc(c, gc[GC_DRAW]);
if (gc[GC_CLEAR])
xcb_free_gc(c, gc[GC_CLEAR]);
if (gc[GC_ATTR])
xcb_free_gc(c, gc[GC_ATTR]);
if (c)
xcb_disconnect(c);
}
void
sighandle (int signal)
{
if (signal == SIGINT || signal == SIGTERM)
exit(EXIT_SUCCESS);
}
int
main (int argc, char **argv)
{
struct pollfd pollin[2] = {
{ .fd = STDIN_FILENO, .events = POLLIN },
{ .fd = -1 , .events = POLLIN },
};
xcb_generic_event_t *ev;
xcb_expose_event_t *expose_ev;
xcb_button_press_event_t *press_ev;
char input[4096] = {0, };
bool permanent = false;
int geom_v[4] = { -1, -1, 0, 0 };
// Install the parachute!
atexit(cleanup);
signal(SIGINT, sighandle);
signal(SIGTERM, sighandle);
// Connect to the Xserver and initialize scr
xconn();
// B/W combo
dbgc = bgc = parse_color("black", NULL, scr->black_pixel);
dfgc = fgc = parse_color("white", NULL, scr->white_pixel);
ugc = fgc;
char ch;
while ((ch = getopt(argc, argv, "hg:bdf:a:pu:B:F:")) != -1) {
switch (ch) {
case 'h':
printf ("usage: %s [-h | -g | -b | -d | -f | -a | -p | -u | -B | -F]\n"
"\t-h Show this help\n"
"\t-g Set the bar geometry {width}x{height}+{xoffset}+{yoffset}\n"
"\t-b Put bar at the bottom of the screen\n"
"\t-d Force docking (use this if your WM isn't EWMH compliant)\n"
"\t-f Bar font list, comma separated\n"
"\t-p Don't close after the data ends\n"
"\t-u Set the underline/overline height in pixels\n"
"\t-B Set background color in #AARRGGBB\n"
"\t-F Set foreground color in #AARRGGBB\n", argv[0]);
exit (EXIT_SUCCESS);
case 'g': (void)parse_geometry_string(optarg, geom_v); break;
case 'p': permanent = true; break;
case 'b': topbar = false; break;
case 'd': dock = true; break;
case 'f': parse_font_list(optarg); break;
case 'u': bu = strtoul(optarg, NULL, 10); break;
case 'B': dbgc = bgc = parse_color(optarg, NULL, scr->black_pixel); break;
case 'F': dfgc = fgc = parse_color(optarg, NULL, scr->white_pixel); break;
}
}
// Copy the geometry values in place
bw = geom_v[0];
bh = geom_v[1];
bx = geom_v[2];
by = geom_v[3];
// Do the heavy lifting
init();
// Get the fd to Xserver
pollin[1].fd = xcb_get_file_descriptor(c);
for (;;) {
bool redraw = false;
// If connection is in error state, then it has been shut down.
if (xcb_connection_has_error(c))
break;
if (poll(pollin, 2, -1) > 0) {
if (pollin[0].revents & POLLHUP) { // No more data...
if (permanent) pollin[0].fd = -1; // ...null the fd and continue polling :D
else break; // ...bail out
}
if (pollin[0].revents & POLLIN) { // New input, process it
if (fgets(input, sizeof(input), stdin) == NULL)
break; // EOF received
parse(input);
redraw = true;
}
if (pollin[1].revents & POLLIN) { // Xserver broadcasted an event
while ((ev = xcb_poll_for_event(c))) {
expose_ev = (xcb_expose_event_t *)ev;
switch (ev->response_type & 0x7F) {
case XCB_EXPOSE:
if (expose_ev->count == 0)
redraw = true;
break;
case XCB_BUTTON_PRESS:
press_ev = (xcb_button_press_event_t *)ev;
{
area_t *area = area_get(press_ev->event, press_ev->detail, press_ev->event_x);
// Respond to the click
if (area && area->button == press_ev->detail) {
write(STDOUT_FILENO, area->cmd, strlen(area->cmd));
write(STDOUT_FILENO, "\n", 1);
}
}
break;
}
free(ev);
}
}
}
if (redraw) { // Copy our temporary pixmap onto the window
for (monitor_t *mon = monhead; mon; mon = mon->next) {
xcb_copy_area(c, mon->pixmap, mon->window, gc[GC_DRAW], 0, 0, 0, 0, mon->width, bh);
}
}
xcb_flush(c);
}
return EXIT_SUCCESS;
}