diff --git a/Makefile b/Makefile index 06bafc1..3e5a145 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ dwlb.o: utf8.h config.h xdg-shell-protocol.h xdg-output-unstable-v1-protocol.h w dwlb: xdg-shell-protocol.o xdg-output-unstable-v1-protocol.o wlr-layer-shell-unstable-v1-protocol.o # Library dependencies -dwlb: CFLAGS+=$(shell pkg-config --cflags wayland-client fcft pixman-1) -dwlb: LDLIBS+=$(shell pkg-config --libs wayland-client fcft pixman-1) -lrt +dwlb: CFLAGS+=$(shell pkg-config --cflags wayland-client wayland-cursor fcft pixman-1) +dwlb: LDLIBS+=$(shell pkg-config --libs wayland-client wayland-cursor fcft pixman-1) -lrt .PHONY: all clean install diff --git a/dwlb.c b/dwlb.c index 6743184..fa4591c 100644 --- a/dwlb.c +++ b/dwlb.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -11,45 +12,56 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include "utf8.h" #include "xdg-shell-protocol.h" #include "xdg-output-unstable-v1-protocol.h" #include "wlr-layer-shell-unstable-v1-protocol.h" -#define DIE(fmt, ...) \ +#define DIE(fmt, ...) \ do { \ + cleanup(); \ fprintf(stderr, fmt "\n", ##__VA_ARGS__); \ exit(1); \ } while (0) - #define EDIE(fmt, ...) \ DIE(fmt ": %s", ##__VA_ARGS__, strerror(errno)); -#define CLEANUP_DIE(why) \ - do { \ - cleanup(); \ - DIE(why); \ - } while(0) - -#define CLEANUP_EDIE(why) \ - do { \ - cleanup(); \ - EDIE(why); \ - } while(0) - #define MIN(a, b) \ ((a) < (b) ? (a) : (b)) #define MAX(a, b) \ ((a) > (b) ? (a) : (b)) -#define LENGTH(X) \ - (sizeof X / sizeof X[0]) +#define LENGTH(x) \ + (sizeof x / sizeof x[0]) + +#define ARRAY_INIT_CAP 8 +#define ARRAY_EXPAND(arr, len, cap, inc) \ + do { \ + uint32_t new_len, new_cap; \ + new_len = (len) + (inc); \ + if (new_len > (cap)) { \ + new_cap = new_len * 2; \ + if (new_cap < ARRAY_INIT_CAP) \ + new_cap = ARRAY_INIT_CAP; \ + if (!((arr) = realloc((arr), sizeof(*(arr)) * new_cap))) \ + EDIE("realloc"); \ + (cap) = new_cap; \ + } \ + (len) = new_len; \ + } while (0) +#define ARRAY_APPEND(arr, len, cap, ptr) \ + do { \ + ARRAY_EXPAND((arr), (len), (cap), 1); \ + (ptr) = &(arr)[(len) - 1]; \ + } while (0) #define PROGRAM "dwlb" #define VERSION "0.1" @@ -85,6 +97,22 @@ " -v get version information\n" \ " -h view this help text\n" +typedef struct { + pixman_color_t color; + bool bg; + char *start; +} StatusColor; + +typedef struct { + uint32_t button; + uint32_t x1; + uint32_t x2; + bool incomplete; + char command[128]; + char *start; + char *end; +} StatusButton; + typedef struct Bar Bar; struct Bar { struct zxdg_output_v1 *xdg_output; @@ -109,14 +137,35 @@ struct Bar { char layout[32]; char title[512]; char status[512]; + + StatusColor *status_colors; + uint32_t status_colors_l, status_colors_c; + StatusButton *status_buttons; + uint32_t status_buttons_l, status_buttons_c; bool hidden; bool bottom; + bool redraw; Bar *prev, *next; }; +typedef struct Seat Seat; +struct Seat { + struct wl_seat *wl_seat; + struct wl_pointer *wl_pointer; + + uint32_t registry_name; + + Bar *pointer_bar; + uint32_t pointer_x; + uint32_t pointer_y; + uint32_t pointer_button; + + Seat *prev, *next; +}; + static int sock_fd; static char socketdir[256]; static char *socketpath = NULL; @@ -129,11 +178,14 @@ static struct wl_compositor *compositor; static struct wl_shm *shm; static struct zwlr_layer_shell_v1 *layer_shell; static struct zxdg_output_manager_v1 *output_manager; +static struct wl_cursor_image *cursor_image; +static struct wl_surface *cursor_surface; + +static Bar *bar_list = NULL; +static Seat *seat_list = NULL; static struct fcft_font *font; -static Bar *bars = NULL; - static uint32_t height; static uint32_t textpadding; @@ -171,123 +223,81 @@ allocate_shm_file(size_t size) return fd; } -/* Color parsing logic adapted from [sway] */ -static int -parse_color(const char *str, pixman_color_t *clr) -{ - if (*str == '#') - str++; - int len = strlen(str); - - // Disallows "0x" prefix that strtoul would ignore - if ((len != 6 && len != 8) || !isxdigit(str[0]) || !isxdigit(str[1])) - return -1; - - char *ptr; - uint32_t parsed = strtoul(str, &ptr, 16); - if (*ptr) - return -1; - - if (len == 8) { - clr->alpha = (parsed & 0xff) * 0x101; - parsed >>= 8; - } else { - clr->alpha = 0xffff; - } - clr->red = ((parsed >> 16) & 0xff) * 0x101; - clr->green = ((parsed >> 8) & 0xff) * 0x101; - clr->blue = ((parsed >> 0) & 0xff) * 0x101; - return 0; -} - -static char * -handle_cmd(char *cmd, pixman_color_t *fg, pixman_color_t *bg, - pixman_color_t *def_fg, pixman_color_t *def_bg) -{ - char *arg, *end; - - if (!(arg = strchr(cmd, '(')) || !(end = strchr(arg + 1, ')'))) - return cmd; - - *arg++ = '\0'; - *end = '\0'; - - if (!strcmp(cmd, "bg")) { - if (bg && def_bg) { - if (!*arg) - *bg = *def_bg; - else - parse_color(arg, bg); - } - } else if (!strcmp(cmd, "fg")) { - if (fg && def_fg) { - if (!*arg) - *fg = *def_fg; - else - parse_color(arg, fg); - } - } - - /* Restore string for later redraws */ - *--arg = '('; - *end = ')'; - return end; -} - static uint32_t draw_text(char *text, uint32_t xpos, uint32_t ypos, pixman_image_t *foreground, pixman_image_t *background, - pixman_color_t *fgcolor, - pixman_color_t *bgcolor, - uint32_t maxxpos, - uint32_t bufheight, + pixman_color_t *fg_color, + pixman_color_t *bg_color, + uint32_t max_xpos, + uint32_t buf_height, uint32_t padding, - bool commands) + StatusColor *colors, + uint32_t colors_l, + StatusButton *buttons, + uint32_t buttons_l) { - if (!*text || !maxxpos) + if (!*text || !max_xpos) return xpos; uint32_t ixpos = xpos; uint32_t nxpos; - if ((nxpos = xpos + padding) + padding >= maxxpos) + if ((nxpos = xpos + padding) + padding >= max_xpos) return xpos; xpos = nxpos; - bool drawfg = foreground && fgcolor; - bool drawbg = background && bgcolor; + bool draw_fg = foreground && fg_color; + bool draw_bg = background && bg_color; - pixman_image_t *fgfill; - pixman_color_t cur_fgcolor; - pixman_color_t cur_bgcolor; - if (drawfg) { - fgfill = pixman_image_create_solid_fill(fgcolor); - cur_fgcolor = *fgcolor; - } - if (drawbg) - cur_bgcolor = *bgcolor; + pixman_image_t *fg_fill; + pixman_color_t *cur_bg_color; + if (draw_fg) + fg_fill = pixman_image_create_solid_fill(fg_color); + if (draw_bg) + cur_bg_color = bg_color; + if (buttons) + for (uint32_t i = 0; i < buttons_l; i++) + buttons[i].incomplete = true; + uint32_t codepoint; uint32_t state = UTF8_ACCEPT; - uint32_t lastcp = 0; - + uint32_t last_cp = 0; + uint32_t color_ind = 0; + for (char *p = text; *p; p++) { - /* If commands are enabled, check for inline ^ commands */ - if (commands && state == UTF8_ACCEPT && *p == '^') { - p++; - if (*p != '^') { - p = handle_cmd(p, &cur_fgcolor, &cur_bgcolor, fgcolor, bgcolor); - if (drawfg) { - pixman_image_unref(fgfill); - fgfill = pixman_image_create_solid_fill(&cur_fgcolor); + /* Check for color changes */ + if (state == UTF8_ACCEPT) { + if (colors && (draw_fg || draw_bg)) { + while (color_ind < colors_l && p == colors[color_ind].start) { + if (colors[color_ind].bg) { + if (draw_bg) + cur_bg_color = &colors[color_ind].color; + } else { + if (draw_fg) { + pixman_image_unref(fg_fill); + fg_fill = pixman_image_create_solid_fill(&colors[color_ind].color); + } + } + color_ind++; + } + } + + if (buttons) { + for (uint32_t i = 0; i < buttons_l; i++) { + if (p == buttons[i].start) { + buttons[i].x1 = xpos; + } else if (p == buttons[i].end) { + buttons[i].x2 = xpos; + buttons[i].incomplete = false; + } } - continue; } } - + /* Returns nonzero if more bytes are needed */ if (utf8decode(&state, &codepoint, *p)) continue; @@ -300,109 +310,111 @@ draw_text(char *text, /* Adjust x position based on kerning with previous glyph */ long x_kern = 0; - if (lastcp) - fcft_kerning(font, lastcp, codepoint, &x_kern, NULL); - if ((nxpos = xpos + x_kern + glyph->advance.x) + padding > maxxpos) + if (last_cp) + fcft_kerning(font, last_cp, codepoint, &x_kern, NULL); + if ((nxpos = xpos + x_kern + glyph->advance.x) + padding > max_xpos) break; - lastcp = codepoint; + last_cp = codepoint; xpos += x_kern; - if (drawfg) { + if (draw_fg) { /* Detect and handle pre-rendered glyphs (e.g. emoji) */ if (pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) { /* Only the alpha channel of the mask is used, so we can * use fgfill here to blend prerendered glyphs with the * same opacity */ pixman_image_composite32( - PIXMAN_OP_OVER, glyph->pix, fgfill, foreground, 0, 0, 0, 0, + PIXMAN_OP_OVER, glyph->pix, fg_fill, foreground, 0, 0, 0, 0, xpos + glyph->x, ypos - glyph->y, glyph->width, glyph->height); } else { /* Applying the foreground color here would mess up * component alphas for subpixel-rendered text, so we * apply it when blending. */ pixman_image_composite32( - PIXMAN_OP_OVER, fgfill, glyph->pix, foreground, 0, 0, 0, 0, + PIXMAN_OP_OVER, fg_fill, glyph->pix, foreground, 0, 0, 0, 0, xpos + glyph->x, ypos - glyph->y, glyph->width, glyph->height); } } - if (drawbg) + if (draw_bg) pixman_image_fill_boxes(PIXMAN_OP_OVER, background, - &cur_bgcolor, 1, &(pixman_box32_t){ + cur_bg_color, 1, &(pixman_box32_t){ .x1 = xpos, .x2 = nxpos, - .y1 = 0, .y2 = bufheight + .y1 = 0, .y2 = buf_height }); /* increment pen position */ xpos = nxpos; ypos += glyph->advance.y; } - if (drawfg) - pixman_image_unref(fgfill); - - if (!lastcp) + + if (draw_fg) + pixman_image_unref(fg_fill); + if (!last_cp) return ixpos; - if (state != UTF8_ACCEPT) - fprintf(stderr, "malformed UTF-8 sequence\n"); + if (buttons) + for (uint32_t i = 0; i < buttons_l; i++) + if (buttons[i].incomplete) + buttons[i].x2 = xpos; nxpos = xpos + padding; - if (drawbg) { + if (draw_bg) { /* Fill padding background */ pixman_image_fill_boxes(PIXMAN_OP_OVER, background, - &cur_bgcolor, 1, &(pixman_box32_t){ + bg_color, 1, &(pixman_box32_t){ .x1 = ixpos, .x2 = ixpos + padding, - .y1 = 0, .y2 = bufheight + .y1 = 0, .y2 = buf_height }); pixman_image_fill_boxes(PIXMAN_OP_OVER, background, - bgcolor, 1, &(pixman_box32_t){ + bg_color, 1, &(pixman_box32_t){ .x1 = xpos, .x2 = nxpos, - .y1 = 0, .y2 = bufheight + .y1 = 0, .y2 = buf_height }); } return nxpos; } -#define TEXT_WIDTH(text, maxwidth, padding, commands) \ - draw_text(text, 0, 0, NULL, NULL, NULL, NULL, maxwidth, 0, padding, commands) +#define TEXT_WIDTH(text, maxwidth, padding) \ + draw_text(text, 0, 0, NULL, NULL, NULL, NULL, maxwidth, 0, padding, NULL, 0, NULL, 0) static int -draw_frame(Bar *b) +draw_frame(Bar *bar) { /* Allocate buffer to be attached to the surface */ - int fd = allocate_shm_file(b->bufsize); + int fd = allocate_shm_file(bar->bufsize); if (fd == -1) return -1; - uint32_t *data = mmap(NULL, b->bufsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + uint32_t *data = mmap(NULL, bar->bufsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { close(fd); return -1; } - struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, b->bufsize); - struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, b->width, b->height, b->stride, WL_SHM_FORMAT_ARGB8888); + struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, bar->bufsize); + struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, bar->width, bar->height, bar->stride, WL_SHM_FORMAT_ARGB8888); wl_buffer_add_listener(buffer, &wl_buffer_listener, NULL); wl_shm_pool_destroy(pool); close(fd); /* Pixman image corresponding to main buffer */ - pixman_image_t *bar = pixman_image_create_bits(PIXMAN_a8r8g8b8, b->width, b->height, data, b->width * 4); + pixman_image_t *final = pixman_image_create_bits(PIXMAN_a8r8g8b8, bar->width, bar->height, data, bar->width * 4); /* Text background and foreground layers */ - pixman_image_t *foreground = pixman_image_create_bits(PIXMAN_a8r8g8b8, b->width, b->height, NULL, b->width * 4); - pixman_image_t *background = pixman_image_create_bits(PIXMAN_a8r8g8b8, b->width, b->height, NULL, b->width * 4); + pixman_image_t *foreground = pixman_image_create_bits(PIXMAN_a8r8g8b8, bar->width, bar->height, NULL, bar->width * 4); + pixman_image_t *background = pixman_image_create_bits(PIXMAN_a8r8g8b8, bar->width, bar->height, NULL, bar->width * 4); /* Draw on images */ uint32_t xpos_left = 0; - uint32_t ypos = (b->height + font->ascent - font->descent) / 2; + uint32_t ypos = (bar->height + font->ascent - font->descent) / 2; uint32_t boxs = font->height / 9; uint32_t boxw = font->height / 6 + 2; for (uint32_t i = 0; i < LENGTH(tags); i++) { - bool active = b->mtags & 1 << i; - bool occupied = b->ctags & 1 << i; - bool urgent = b->urg & 1 << i; + bool active = bar->mtags & 1 << i; + bool occupied = bar->ctags & 1 << i; + bool urgent = bar->urg & 1 << i; if (hide_vacant && !active && !occupied && !urgent) continue; @@ -417,43 +429,47 @@ draw_frame(Bar *b) .y1 = boxs, .y2 = boxs + boxw }); - xpos_left = draw_text(tags[i], xpos_left, ypos, foreground, background, fg_color, - bg_color, b->width, b->height, b->textpadding, false); + xpos_left = draw_text(tags[i], xpos_left, ypos, foreground, background, fg_color, bg_color, + bar->width, bar->height, bar->textpadding, NULL, 0, NULL, 0); } - xpos_left = draw_text(b->layout, xpos_left, ypos, foreground, background, &inactive_fg_color, - &inactive_bg_color, b->width, b->height, b->textpadding, false); + xpos_left = draw_text(bar->layout, xpos_left, ypos, foreground, background, + &inactive_fg_color, &inactive_bg_color, bar->width, + bar->height, bar->textpadding, NULL, 0, NULL, 0); - uint32_t status_width = TEXT_WIDTH(b->status, b->width - xpos_left, b->textpadding, true); - draw_text(b->status, b->width - status_width, ypos, foreground, + uint32_t status_width = TEXT_WIDTH(bar->status, bar->width - xpos_left, bar->textpadding); + draw_text(bar->status, bar->width - status_width, ypos, foreground, background, &inactive_fg_color, &inactive_bg_color, - b->width, b->height, b->textpadding, status_commands); + bar->width, bar->height, bar->textpadding, + bar->status_colors, bar->status_colors_l, + bar->status_buttons, bar->status_buttons_l); - xpos_left = draw_text(b->title, xpos_left, ypos, foreground, background, - b->selmon ? &active_fg_color : &inactive_fg_color, - b->selmon ? &active_bg_color : &inactive_bg_color, - b->width - status_width, b->height, b->textpadding, false); + xpos_left = draw_text(bar->title, xpos_left, ypos, foreground, background, + bar->selmon ? &active_fg_color : &inactive_fg_color, + bar->selmon ? &active_bg_color : &inactive_bg_color, + bar->width - status_width, bar->height, bar->textpadding, + NULL, 0, NULL, 0); pixman_image_fill_boxes(PIXMAN_OP_SRC, background, - b->selmon ? &active_bg_color : &inactive_bg_color, 1, + bar->selmon ? &active_bg_color : &inactive_bg_color, 1, &(pixman_box32_t){ - .x1 = xpos_left, .x2 = b->width - status_width, - .y1 = 0, .y2 = b->height + .x1 = xpos_left, .x2 = bar->width - status_width, + .y1 = 0, .y2 = bar->height }); /* Draw background and foreground on bar */ - pixman_image_composite32(PIXMAN_OP_OVER, background, NULL, bar, 0, 0, 0, 0, 0, 0, b->width, b->height); - pixman_image_composite32(PIXMAN_OP_OVER, foreground, NULL, bar, 0, 0, 0, 0, 0, 0, b->width, b->height); + pixman_image_composite32(PIXMAN_OP_OVER, background, NULL, final, 0, 0, 0, 0, 0, 0, bar->width, bar->height); + pixman_image_composite32(PIXMAN_OP_OVER, foreground, NULL, final, 0, 0, 0, 0, 0, 0, bar->width, bar->height); pixman_image_unref(foreground); pixman_image_unref(background); - pixman_image_unref(bar); + pixman_image_unref(final); - munmap(data, b->bufsize); + munmap(data, bar->bufsize); - wl_surface_attach(b->wl_surface, buffer, 0, 0); - wl_surface_damage_buffer(b->wl_surface, 0, 0, b->width, b->height); - wl_surface_commit(b->wl_surface); + wl_surface_attach(bar->wl_surface, buffer, 0, 0); + wl_surface_damage_buffer(bar->wl_surface, 0, 0, bar->width, bar->height); + wl_surface_commit(bar->wl_surface); return 0; } @@ -465,18 +481,18 @@ layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *surface, { zwlr_layer_surface_v1_ack_configure(surface, serial); - Bar *b = (Bar *)data; + Bar *bar = (Bar *)data; - if (b->configured && w == b->width && h == b->height) + if (bar->configured && w == bar->width && h == bar->height) return; - b->width = w; - b->height = h; - b->stride = b->width * 4; - b->bufsize = b->stride * b->height; - b->configured = true; + bar->width = w; + bar->height = h; + bar->stride = bar->width * 4; + bar->bufsize = bar->stride * bar->height; + bar->configured = true; - draw_frame(b); + draw_frame(bar); } static void @@ -500,13 +516,13 @@ cleanup(void) static void output_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) { - Bar *b = (Bar *)data; + Bar *bar = (Bar *)data; /* Is this necessary? */ - if (b->xdg_output_name) - free(b->xdg_output_name); - if (!(b->xdg_output_name = strdup(name))) - CLEANUP_EDIE("strdup"); + if (bar->xdg_output_name) + free(bar->xdg_output_name); + if (!(bar->xdg_output_name = strdup(name))) + EDIE("strdup"); } static void @@ -541,56 +557,215 @@ static const struct zxdg_output_v1_listener output_listener = { }; static void -show_bar(Bar *b) +shell_command(char *command) { - b->wl_surface = wl_compositor_create_surface(compositor); - if (!b->wl_surface) - CLEANUP_DIE("Could not create wl_surface"); + if (fork() == 0) { + setsid(); + execl("/bin/sh", "sh", "-c", command, NULL); + perror(" failed"); + exit(EXIT_SUCCESS); + } +} - b->layer_surface = zwlr_layer_shell_v1_get_layer_surface(layer_shell, b->wl_surface, b->wl_output, +static void +pointer_enter(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + Seat *seat = (Seat *)data; + + Bar *bar; + DL_FOREACH(bar_list, bar) + if (bar->wl_surface == surface) + break; + seat->pointer_bar = bar; + + if (!cursor_image) { + struct wl_cursor_theme *cursor_theme = wl_cursor_theme_load(NULL, 24, shm); + cursor_image = wl_cursor_theme_get_cursor(cursor_theme, "left_ptr")->images[0]; + cursor_surface = wl_compositor_create_surface(compositor); + wl_surface_attach(cursor_surface, wl_cursor_image_get_buffer(cursor_image), 0, 0); + wl_surface_commit(cursor_surface); + } + wl_pointer_set_cursor(pointer, serial, cursor_surface, + cursor_image->hotspot_x, + cursor_image->hotspot_y); +} + +static void +pointer_leave(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface) +{ + Seat *seat = (Seat *)data; + + seat->pointer_bar = NULL; +} + +static void +pointer_button(void *data, struct wl_pointer *pointer, uint32_t serial, + uint32_t time, uint32_t button, uint32_t state) +{ + Seat *seat = (Seat *)data; + + seat->pointer_button = state == WL_POINTER_BUTTON_STATE_PRESSED ? button : 0; +} + +static void +pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time, + wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + Seat *seat = (Seat *)data; + + seat->pointer_x = wl_fixed_to_int(surface_x); + seat->pointer_y = wl_fixed_to_int(surface_y); +} + +static void +pointer_frame(void *data, struct wl_pointer *pointer) +{ + Seat *seat = (Seat *)data; + + if (!seat->pointer_button || !seat->pointer_bar) + return; + + for (uint32_t i = 0; i < seat->pointer_bar->status_buttons_l; i++) {\ + if (seat->pointer_button == seat->pointer_bar->status_buttons[i].button + && seat->pointer_x >= seat->pointer_bar->status_buttons[i].x1 + && seat->pointer_x < seat->pointer_bar->status_buttons[i].x2) { + shell_command(seat->pointer_bar->status_buttons[i].command); + break; + } + } + + seat->pointer_button = 0; +} + +static void +pointer_axis(void *data, struct wl_pointer *pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) +{ +} + +static void +pointer_axis_discrete(void *data, struct wl_pointer *pointer, + uint32_t axis, int32_t discrete) +{ +} + +static void +pointer_axis_source(void *data, struct wl_pointer *pointer, + uint32_t axis_source) +{ +} + +static void +pointer_axis_stop(void *data, struct wl_pointer *pointer, + uint32_t time, uint32_t axis) +{ +} + +static void +pointer_axis_value120(void *data, struct wl_pointer *pointer, + uint32_t axis, int32_t discrete) +{ +} + +static const struct wl_pointer_listener pointer_listener = { + .axis = pointer_axis, + .axis_discrete = pointer_axis_discrete, + .axis_source = pointer_axis_source, + .axis_stop = pointer_axis_stop, + .axis_value120 = pointer_axis_value120, + .button = pointer_button, + .enter = pointer_enter, + .frame = pointer_frame, + .leave = pointer_leave, + .motion = pointer_motion, +}; + +static void +seat_capabilities(void *data, struct wl_seat *wl_seat, + uint32_t capabilities) +{ + Seat *seat = (Seat *)data; + + uint32_t has_pointer = capabilities & WL_SEAT_CAPABILITY_POINTER; + if (has_pointer && !seat->wl_pointer) { + seat->wl_pointer = wl_seat_get_pointer(seat->wl_seat); + wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat); + } else if (!has_pointer && seat->wl_pointer) { + wl_pointer_destroy(seat->wl_pointer); + seat->wl_pointer = NULL; + } +} + +static void +seat_name(void *data, struct wl_seat *wl_seat, const char *name) +{ +} + +static const struct wl_seat_listener seat_listener = { + .capabilities = seat_capabilities, + .name = seat_name, +}; + +static void +show_bar(Bar *bar) +{ + bar->wl_surface = wl_compositor_create_surface(compositor); + if (!bar->wl_surface) + DIE("Could not create wl_surface"); + + bar->layer_surface = zwlr_layer_shell_v1_get_layer_surface(layer_shell, bar->wl_surface, bar->wl_output, ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, PROGRAM); - if (!b->layer_surface) - CLEANUP_DIE("Could not create layer_surface"); - zwlr_layer_surface_v1_add_listener(b->layer_surface, &layer_surface_listener, b); + if (!bar->layer_surface) + DIE("Could not create layer_surface"); + zwlr_layer_surface_v1_add_listener(bar->layer_surface, &layer_surface_listener, bar); - zwlr_layer_surface_v1_set_size(b->layer_surface, 0, b->height); - zwlr_layer_surface_v1_set_anchor(b->layer_surface, - (b->bottom ? ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM : ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) + zwlr_layer_surface_v1_set_size(bar->layer_surface, 0, bar->height); + zwlr_layer_surface_v1_set_anchor(bar->layer_surface, + (bar->bottom ? ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM : ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT); - zwlr_layer_surface_v1_set_exclusive_zone(b->layer_surface, b->height); - wl_surface_commit(b->wl_surface); + zwlr_layer_surface_v1_set_exclusive_zone(bar->layer_surface, bar->height); + wl_surface_commit(bar->wl_surface); - b->hidden = false; + bar->hidden = false; } static void -hide_bar(Bar *b) +hide_bar(Bar *bar) { - zwlr_layer_surface_v1_destroy(b->layer_surface); - wl_surface_destroy(b->wl_surface); + zwlr_layer_surface_v1_destroy(bar->layer_surface); + wl_surface_destroy(bar->wl_surface); - b->configured = false; - b->hidden = true; + bar->configured = false; + bar->hidden = true; } static void -setup_bar(Bar *b) +setup_bar(Bar *bar) { - b->height = height; - b->textpadding = textpadding; - b->bottom = bottom; - b->hidden = hidden; + bar->height = height; + bar->textpadding = textpadding; + bar->bottom = bottom; + bar->hidden = hidden; - snprintf(b->layout, sizeof b->layout, "[]="); + snprintf(bar->layout, sizeof bar->layout, "[]="); - b->xdg_output = zxdg_output_manager_v1_get_xdg_output(output_manager, b->wl_output); - if (!b->xdg_output) - CLEANUP_DIE("Could not create xdg_output"); - zxdg_output_v1_add_listener(b->xdg_output, &output_listener, b); + bar->xdg_output = zxdg_output_manager_v1_get_xdg_output(output_manager, bar->wl_output); + if (!bar->xdg_output) + DIE("Could not create xdg_output"); + zxdg_output_v1_add_listener(bar->xdg_output, &output_listener, bar); - if (!b->hidden) - show_bar(b); + if (!bar->hidden) + show_bar(bar); +} + +static void +setup_seat(Seat *seat) +{ + wl_seat_add_listener(seat->wl_seat, &seat_listener, seat); } static void @@ -601,49 +776,81 @@ handle_global(void *data, struct wl_registry *registry, compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4); } else if (!strcmp(interface, wl_shm_interface.name)) { shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); - } else if (!strcmp(interface, wl_output_interface.name)) { - Bar *b = malloc(sizeof(Bar)); - if (!b) - CLEANUP_EDIE("malloc"); - memset(b, 0, sizeof(Bar)); - b->registry_name = name; - b->wl_output = wl_registry_bind(registry, name, &wl_output_interface, 1); - DL_APPEND(bars, b); - if (ready) - setup_bar(b); } else if (!strcmp(interface, zwlr_layer_shell_v1_interface.name)) { layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1); } else if (!strcmp(interface, zxdg_output_manager_v1_interface.name)) { output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, 2); + } else if (!strcmp(interface, wl_output_interface.name)) { + Bar *bar = malloc(sizeof(Bar)); + if (!bar) + EDIE("malloc"); + memset(bar, 0, sizeof(Bar)); + bar->registry_name = name; + bar->wl_output = wl_registry_bind(registry, name, &wl_output_interface, 1); + DL_APPEND(bar_list, bar); + if (ready) + setup_bar(bar); + } else if (!strcmp(interface, wl_seat_interface.name)) { + Seat *seat = malloc(sizeof(Seat)); + if (!seat) + EDIE("malloc"); + memset(seat, 0, sizeof(Seat)); + seat->registry_name = name; + seat->wl_seat = wl_registry_bind(registry, name, &wl_seat_interface, 7); + DL_APPEND(seat_list, seat); + setup_seat(seat); } } static void -teardown_bar(Bar *b) +teardown_bar(Bar *bar) { - zxdg_output_v1_destroy(b->xdg_output); - if (!b->hidden) { - zwlr_layer_surface_v1_destroy(b->layer_surface); - wl_surface_destroy(b->wl_surface); + zxdg_output_v1_destroy(bar->xdg_output); + if (!bar->hidden) { + zwlr_layer_surface_v1_destroy(bar->layer_surface); + wl_surface_destroy(bar->wl_surface); } - if (b->xdg_output_name) - free(b->xdg_output_name); - free(b); + if (bar->xdg_output_name) + free(bar->xdg_output_name); + if (bar->status_colors) + free(bar->status_colors); + if (bar->status_buttons) + free(bar->status_buttons); + free(bar); +} + +static void +teardown_seat(Seat *seat) +{ + wl_seat_destroy(seat->wl_seat); + free(seat); } static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { - Bar *b; - DL_FOREACH(bars, b) - if (b->registry_name == name) + do { + Bar *bar; + DL_FOREACH(bar_list, bar) + if (bar->registry_name == name) + break; + if (!bar) break; - - if (!b) + DL_DELETE(bar_list, bar); + teardown_bar(bar); return; - - DL_DELETE(bars, b); - teardown_bar(b); + } while(0); + + do { + Seat *seat; + DL_FOREACH(seat_list, seat) + if (seat->registry_name == name) + break; + if (!seat) + break; + DL_DELETE(seat_list, seat); + teardown_seat(seat); + } while(0); } static const struct wl_registry_listener registry_listener = { @@ -677,7 +884,7 @@ read_stdin(void) if (rv == -1) { if (errno == EWOULDBLOCK) break; - CLEANUP_EDIE("read"); + EDIE("read"); } if (rv == 0) { run_display = 0; @@ -686,7 +893,7 @@ read_stdin(void) if ((len += rv) > stdinbuf_cap / 2) if (!(stdinbuf = realloc(stdinbuf, (stdinbuf_cap *= 2)))) - CLEANUP_EDIE("realloc"); + EDIE("realloc"); } char *linebeg, *lineend; @@ -700,12 +907,12 @@ read_stdin(void) ADVANCE_IF_LAST_CONT(); - Bar *b; - DL_FOREACH(bars, b) - if (b->xdg_output_name) - if (!strcmp(wordbeg, b->xdg_output_name)) + Bar *bar; + DL_FOREACH(bar_list, bar) + if (bar->xdg_output_name) + if (!strcmp(wordbeg, bar->xdg_output_name)) break; - if (!b) + if (!bar) continue; ADVANCE_IF_LAST_CONT(); @@ -713,66 +920,184 @@ read_stdin(void) uint32_t val; if (!strcmp(wordbeg, "tags")) { ADVANCE_IF_LAST_CONT(); - if ((val = atoi(wordbeg)) != b->ctags) { - b->ctags = val; - b->redraw = true; + if ((val = atoi(wordbeg)) != bar->ctags) { + bar->ctags = val; + bar->redraw = true; } ADVANCE_IF_LAST_CONT(); - if ((val = atoi(wordbeg)) != b->mtags) { - b->mtags = val; - b->redraw = true; + if ((val = atoi(wordbeg)) != bar->mtags) { + bar->mtags = val; + bar->redraw = true; } ADVANCE_IF_LAST_CONT(); /* skip sel */ ADVANCE(); - if ((val = atoi(wordbeg)) != b->urg) { - b->urg = val; - b->redraw = true; + if ((val = atoi(wordbeg)) != bar->urg) { + bar->urg = val; + bar->redraw = true; } } else if (!strcmp(wordbeg, "layout")) { - if (strcmp(b->layout, wordend) != 0) { - snprintf(b->layout, sizeof b->layout, "%s", wordend); - b->redraw = true; + if (strcmp(bar->layout, wordend) != 0) { + snprintf(bar->layout, sizeof bar->layout, "%s", wordend); + bar->redraw = true; } } else if (!strcmp(wordbeg, "title")) { - if (strcmp(b->title, wordend) != 0) { - snprintf(b->title, sizeof b->title, "%s", wordend); - b->redraw = true; + if (strcmp(bar->title, wordend) != 0) { + snprintf(bar->title, sizeof bar->title, "%s", wordend); + bar->redraw = true; } } else if (!strcmp(wordbeg, "selmon")) { ADVANCE(); - if ((val = atoi(wordbeg)) != b->selmon) { - b->selmon = val; - b->redraw = true; + if ((val = atoi(wordbeg)) != bar->selmon) { + bar->selmon = val; + bar->redraw = true; } } } } static void -bar_set_top(Bar *b) +set_top(Bar *bar) { - if (!b->hidden) { - zwlr_layer_surface_v1_set_anchor(b->layer_surface, + if (!bar->hidden) { + zwlr_layer_surface_v1_set_anchor(bar->layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT); - b->redraw = true; + bar->redraw = true; } - b->bottom = false; + bar->bottom = false; } static void -bar_set_bottom(Bar *b) +set_bottom(Bar *bar) { - if (!b->hidden) { - zwlr_layer_surface_v1_set_anchor(b->layer_surface, + if (!bar->hidden) { + zwlr_layer_surface_v1_set_anchor(bar->layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT); - b->redraw = true; + bar->redraw = true; } - b->bottom = true; + bar->bottom = true; +} + +/* Color parsing logic adapted from [sway] */ +static int +parse_color(const char *str, pixman_color_t *clr) +{ + if (*str == '#') + str++; + int len = strlen(str); + + // Disallows "0x" prefix that strtoul would ignore + if ((len != 6 && len != 8) || !isxdigit(str[0]) || !isxdigit(str[1])) + return -1; + + char *ptr; + uint32_t parsed = strtoul(str, &ptr, 16); + if (*ptr) + return -1; + + if (len == 8) { + clr->alpha = (parsed & 0xff) * 0x101; + parsed >>= 8; + } else { + clr->alpha = 0xffff; + } + clr->red = ((parsed >> 16) & 0xff) * 0x101; + clr->green = ((parsed >> 8) & 0xff) * 0x101; + clr->blue = ((parsed >> 0) & 0xff) * 0x101; + return 0; +} + +static void +set_status(Bar *bar, char *text) +{ + bar->status_colors_l = 0; + bar->status_buttons_l = 0; + + size_t str_pos = 0; + uint32_t codepoint; + uint32_t state = UTF8_ACCEPT; + + StatusButton *left_button = NULL; + StatusButton *middle_button = NULL; + StatusButton *right_button = NULL; + + for (char *p = text; *p && str_pos < sizeof(bar->status) - 1; p++) { + if (state == UTF8_ACCEPT && *p == '^') { + p++; + if (*p != '^') { + char *arg, *end; + if (!(arg = strchr(p, '(')) || !(end = strchr(arg + 1, ')'))) + continue; + *arg++ = '\0'; + *end = '\0'; + + if (!strcmp(p, "bg")) { + StatusColor *status_color; + ARRAY_APPEND(bar->status_colors, bar->status_colors_l, bar->status_colors_c, status_color); + if (!*arg) + status_color->color = inactive_bg_color; + else + parse_color(arg, &status_color->color); + status_color->bg = true; + status_color->start = bar->status + str_pos; + } else if (!strcmp(p, "fg")) { + StatusColor *status_color; + ARRAY_APPEND(bar->status_colors, bar->status_colors_l, bar->status_colors_c, status_color); + if (!*arg) + status_color->color = inactive_fg_color; + else + parse_color(arg, &status_color->color); + status_color->bg = false; + status_color->start = bar->status + str_pos; + } else if (!strcmp(p, "lm")) { + if (left_button) { + left_button->end = bar->status + str_pos; + left_button = NULL; + } else if (*arg) { + ARRAY_APPEND(bar->status_buttons, bar->status_buttons_l, bar->status_buttons_c, left_button); + left_button->button = BTN_LEFT; + snprintf(left_button->command, sizeof left_button->command, "%s", arg); + left_button->start = bar->status + str_pos; + } + } else if (!strcmp(p, "mm")) { + if (middle_button) { + middle_button->end = bar->status + str_pos; + middle_button = NULL; + } else if (*arg) { + ARRAY_APPEND(bar->status_buttons, bar->status_buttons_l, bar->status_buttons_c, middle_button); + middle_button->button = BTN_MIDDLE; + snprintf(middle_button->command, sizeof middle_button->command, "%s", arg); + middle_button->start = bar->status + str_pos; + } + } else if (!strcmp(p, "rm")) { + if (right_button) { + right_button->end = bar->status + str_pos; + right_button = NULL; + } else if (*arg) { + ARRAY_APPEND(bar->status_buttons, bar->status_buttons_l, bar->status_buttons_c, right_button); + right_button->button = BTN_RIGHT; + snprintf(right_button->command, sizeof right_button->command, "%s", arg); + right_button->start = bar->status + str_pos; + } + } + + *--arg = '('; + *end = ')'; + + p = end; + continue; + } + } + + bar->status[str_pos++] = *p; + utf8decode(&state, &codepoint, *p); + } + + bar->status[str_pos] = '\0'; } static void @@ -780,10 +1105,10 @@ read_socket(void) { int cli_fd; if ((cli_fd = accept(sock_fd, NULL, 0)) == -1) - CLEANUP_EDIE("accept"); + EDIE("accept"); ssize_t len = recv(cli_fd, sockbuf, sizeof sockbuf - 1, 0); if (len == -1) - CLEANUP_EDIE("recv"); + EDIE("recv"); close(cli_fd); if (len == 0) return; @@ -795,25 +1120,25 @@ read_socket(void) ADVANCE_IF_LAST_BREAK(); - Bar *b; + Bar *bar; bool all = false; if (!strcmp(wordbeg, "all")) { all = true; } else if (!strcmp(wordbeg, "selected")) { - DL_FOREACH(bars, b) - if (b->selmon) + DL_FOREACH(bar_list, bar) + if (bar->selmon) break; } else if (!strcmp(wordbeg, "first")) { - b = bars; + bar = bar_list; } else { - DL_FOREACH(bars, b) - if (b->xdg_output_name) - if (!strcmp(wordbeg, b->xdg_output_name)) + DL_FOREACH(bar_list, bar) + if (bar->xdg_output_name) + if (!strcmp(wordbeg, bar->xdg_output_name)) break; } - if (!all && !b) + if (!all && !bar) break; ADVANCE(); @@ -822,81 +1147,77 @@ read_socket(void) if (!wordend) break; if (all) { - DL_FOREACH(bars, b) { - if (strcmp(b->status, wordend) != 0) { - snprintf(b->status, sizeof b->status, "%s", wordend); - b->redraw = true; - } + DL_FOREACH(bar_list, bar) { + set_status(bar, wordend); + bar->redraw = true; } } else { - if (strcmp(b->status, wordend) != 0) { - snprintf(b->status, sizeof b->status, "%s", wordend); - b->redraw = true; - } + set_status(bar, wordend); + bar->redraw = true; } } else if (!strcmp(wordbeg, "show")) { if (all) { - DL_FOREACH(bars, b) - if (b->hidden) - show_bar(b); + DL_FOREACH(bar_list, bar) + if (bar->hidden) + show_bar(bar); } else { - if (b->hidden) - show_bar(b); + if (bar->hidden) + show_bar(bar); } } else if (!strcmp(wordbeg, "hide")) { if (all) { - DL_FOREACH(bars, b) - if (!b->hidden) - hide_bar(b); + DL_FOREACH(bar_list, bar) + if (!bar->hidden) + hide_bar(bar); } else { - if (!b->hidden) - hide_bar(b); + if (!bar->hidden) + hide_bar(bar); } } else if (!strcmp(wordbeg, "toggle-visibility")) { if (all) { - DL_FOREACH(bars, b) - if (b->hidden) - show_bar(b); + DL_FOREACH(bar_list, bar) + if (bar->hidden) + show_bar(bar); else - hide_bar(b); + hide_bar(bar); } else { - if (b->hidden) - show_bar(b); + if (bar->hidden) + show_bar(bar); else - hide_bar(b); + hide_bar(bar); } } else if (!strcmp(wordbeg, "set-top")) { if (all) { - DL_FOREACH(bars, b) - if (b->bottom) - bar_set_top(b); + DL_FOREACH(bar_list, bar) + if (bar->bottom) + set_top(bar); } else { - if (b->bottom) - bar_set_top(b); + if (bar->bottom) + set_top(bar); } } else if (!strcmp(wordbeg, "set-bottom")) { if (all) { - DL_FOREACH(bars, b) - if (!b->bottom) - bar_set_bottom(b); + DL_FOREACH(bar_list, bar) + if (!bar->bottom) + set_bottom(bar); } else { - if (!b->bottom) - bar_set_bottom(b); + if (!bar->bottom) + set_bottom(bar); } } else if (!strcmp(wordbeg, "toggle-location")) { if (all) { - DL_FOREACH(bars, b) - if (b->bottom) - bar_set_top(b); + DL_FOREACH(bar_list, bar) + if (bar->bottom) + set_top(bar); else - bar_set_bottom(b); + set_bottom(bar); } else { - if (b->bottom) - bar_set_top(b); + if (bar->bottom) + set_top(bar); else - bar_set_bottom(b); + set_bottom(bar); } } } while (0); @@ -910,32 +1231,31 @@ event_loop(void) while (run_display) { fd_set rfds; FD_ZERO(&rfds); + FD_SET(wl_fd, &rfds); FD_SET(STDIN_FILENO, &rfds); FD_SET(sock_fd, &rfds); - FD_SET(wl_fd, &rfds); - /* Does this need to be inside the loop? */ wl_display_flush(display); if (select(MAX(sock_fd, wl_fd) + 1, &rfds, NULL, NULL, NULL) == -1) continue; + if (FD_ISSET(wl_fd, &rfds)) + if (wl_display_dispatch(display) == -1) + break; + if (FD_ISSET(STDIN_FILENO, &rfds)) read_stdin(); if (FD_ISSET(sock_fd, &rfds)) read_socket(); - - if (FD_ISSET(wl_fd, &rfds)) - if (wl_display_dispatch(display) == -1) - break; - Bar *b; - DL_FOREACH(bars, b) { - if (b->redraw) { - if (!b->hidden) - draw_frame(b); - b->redraw = false; + Bar *bar; + DL_FOREACH(bar_list, bar) { + if (bar->redraw) { + if (!bar->hidden) + draw_frame(bar); + bar->redraw = false; } } } @@ -991,7 +1311,8 @@ main(int argc, char **argv) { char *xdgruntimedir; struct sockaddr_un sock_address; - Bar *b, *t; + Bar *bar, *bar2; + Seat *seat, *seat2; /* Establish socket directory */ if (!(xdgruntimedir = getenv("XDG_RUNTIME_DIR"))) @@ -1131,8 +1452,8 @@ main(int argc, char **argv) height = font->height + vertical_padding * 2; /* Setup bars */ - DL_FOREACH(bars, b) - setup_bar(b); + DL_FOREACH(bar_list, bar) + setup_bar(bar); wl_display_roundtrip(display); /* Configure stdin */ @@ -1157,16 +1478,17 @@ main(int argc, char **argv) socketpath = (char *)&sock_address.sun_path; unlink(socketpath); if (bind(sock_fd, (struct sockaddr *) &sock_address, sizeof sock_address) == -1) - CLEANUP_EDIE("bind"); + EDIE("bind"); if (listen(sock_fd, SOMAXCONN) == -1) - CLEANUP_EDIE("listen"); + EDIE("listen"); fcntl(sock_fd, F_SETFD, FD_CLOEXEC | fcntl(sock_fd, F_GETFD)); /* Set up signals */ signal(SIGINT, sig_handler); signal(SIGHUP, sig_handler); signal(SIGTERM, sig_handler); - + signal(SIGCHLD, SIG_IGN); + /* Run */ ready = true; event_loop(); @@ -1178,8 +1500,10 @@ main(int argc, char **argv) zwlr_layer_shell_v1_destroy(layer_shell); zxdg_output_manager_v1_destroy(output_manager); - DL_FOREACH_SAFE(bars, b, t) - teardown_bar(b); + DL_FOREACH_SAFE(bar_list, bar, bar2) + teardown_bar(bar); + DL_FOREACH_SAFE(seat_list, seat, seat2) + teardown_seat(seat); fcft_destroy(font); fcft_fini();