diff --git a/doc/dtao.1.ronn b/doc/dtao.1.ronn index 67395d1..1dedc0a 100644 --- a/doc/dtao.1.ronn +++ b/doc/dtao.1.ronn @@ -137,7 +137,7 @@ The `p` and `pa` commands accept the following argument formats: percent of the text ascent or descent -### Interaction [UNIMPLEMENTED] +### Interaction * `^ca(``,``)`, `^ca()`: defines beginning and end of a "clickable area"; when mouse button number is pressed over the text drawn between these two commands, spawn the command diff --git a/dtao.c b/dtao.c index ef1becd..09d5072 100644 --- a/dtao.c +++ b/dtao.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -34,11 +35,14 @@ /* Includes the newline character */ #define MAX_LINE_LEN 8192 +#define MAX_CMD_LEN 256 +#define MAX_CLICKABLES 64 enum align { ALIGN_C, ALIGN_L, ALIGN_R }; static struct wl_display *display; static struct wl_compositor *compositor; +static struct wl_subcompositor *subcompositor; static struct wl_shm *shm; static struct zwlr_layer_shell_v1 *layer_shell; @@ -46,6 +50,9 @@ static struct zwlr_layer_surface_v1 *layer_surface; static struct wl_output *wl_output; static struct wl_surface *wl_surface; +static struct wl_seat *wl_seat; +static struct wl_pointer *wl_pointer; + static int32_t output = -1; static uint32_t width, height, titlewidth; @@ -135,8 +142,35 @@ parse_color(const char *str, pixman_color_t *clr) return 0; } +struct clickable { + uint32_t x1, y1, x2, y2; + uint8_t btn; + char *cmd; +}; + +static int +parse_clickable_area(char *str, struct clickable *c, uint32_t xpos, uint32_t ypos) +{ + c->x1 = xpos; + c->y1 = 0; + + c->btn = 0; + while (*str != ',' && *str != '\0') + if (isdigit(*str)) c->btn = c->btn*10 + (*str++ - '0'); + + if (*str++ == '\0') BARF("bad clickarea cmd"); + c->cmd = malloc(strlen(str) + 1); + + if (!c->cmd) BARF("bad malloc clickarea cmd"); + strcpy(c->cmd, str); + return 0; +} + +static struct clickable clickies[MAX_CLICKABLES]; +static int clicky_i = 0; + static char * -handle_cmd(char *cmd, pixman_color_t *bg, pixman_color_t *fg) +handle_cmd(char *cmd, pixman_color_t *bg, pixman_color_t *fg, uint32_t *xpos, uint32_t *ypos) { char *arg, *end; @@ -158,6 +192,14 @@ handle_cmd(char *cmd, pixman_color_t *bg, pixman_color_t *fg) } else if (parse_color(arg, fg)) { fprintf(stderr, "Bad color string \"%s\"\n", arg); } + } else if (!strcmp(cmd, "ca")) { + if (!*arg) { + clickies[clicky_i].x2 = *xpos; + clickies[clicky_i].y2 = height; + clicky_i++; + } else if (parse_clickable_area(arg, &clickies[clicky_i], *xpos, *ypos)) { + fprintf(stderr, "bad click area \"%s\"\n", arg); + } } else { fprintf(stderr, "Unrecognized command \"%s\"\n", cmd); } @@ -220,7 +262,7 @@ draw_frame(char *text) if (state == UTF8_ACCEPT && *p == '^') { p++; if (*p != '^') { - p = handle_cmd(p, &textbgcolor, &textfgcolor); + p = handle_cmd(p, &textbgcolor, &textfgcolor, &xpos, &ypos); pixman_image_unref(fgfill); fgfill = pixman_image_create_solid_fill(&textfgcolor); continue; @@ -296,6 +338,15 @@ draw_frame(char *text) xdraw = (width - maxxpos) / 2; break; } + + clickies[clicky_i] = (struct clickable) { 0 }; + + while (clicky_i > 0) { + clicky_i--; + clickies[clicky_i].x1 += xdraw; + clickies[clicky_i].x2 += xdraw; + } + pixman_image_composite32(PIXMAN_OP_OVER, background, NULL, bar, 0, 0, 0, 0, xdraw, 0, width, height); pixman_image_composite32(PIXMAN_OP_OVER, foreground, NULL, bar, 0, 0, 0, 0, @@ -345,13 +396,138 @@ static struct zwlr_layer_surface_v1_listener layer_surface_listener = { .closed = layer_surface_closed, }; +struct input_state { + struct wl_surface *surface; + double x, y; + uint32_t button; +}; + +static void +noop() {} + +static void +wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + struct input_state *istate = data; + istate->surface = surface; + istate->x = wl_fixed_to_double(surface_x); + istate->y = wl_fixed_to_double(surface_y); +} + +static void +wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, + wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + struct input_state *istate = data; + istate->x = wl_fixed_to_double(surface_x); + istate->y = wl_fixed_to_double(surface_y); +} + +static void +wl_pointer_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, + uint32_t time, uint32_t button, uint32_t state) +{ + struct input_state *istate = data; + istate->button = state == WL_POINTER_BUTTON_STATE_RELEASED ? 0 : button; +} + +static void +wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface) +{ + struct input_state *istate = data; + istate->surface = NULL; +} + +/* borrowed from dzen's util.c, un-double-forked */ +void +spawn(const char *arg) { + static const char *shell = NULL; + + if(!shell && !(shell = getenv("SHELL"))) + shell = "/bin/sh"; + if(!arg) + return; + if(fork() == 0) { + setsid(); + execl(shell, shell, "-c", arg, (char *)NULL); + fprintf(stderr, "dtao: execl '%s -c %s'", shell, arg); + perror(" failed"); + } + wait(0); +} + +static void +wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) +{ + struct input_state *istate = data; + + if (!istate || istate->button == 0) + return; + + for (struct clickable *c = clickies; c->btn > 0; c++) { + if (c->btn == istate->button - 271 && + istate->x >= c->x1 && istate->x <= c->x2 && + istate->y >= c->y1 && istate->y <= c->y2) { + spawn(c->cmd); + break; + } + } + istate->button = 0; +} + +static const struct wl_pointer_listener wl_pointer_listener = { + .enter = wl_pointer_enter, + .leave = wl_pointer_leave, + .motion = wl_pointer_motion, + .button = wl_pointer_button, + .axis = noop, + .axis_source = noop, + .axis_stop = noop, + .axis_discrete = noop, + .frame = wl_pointer_frame, +}; + +static void +wl_seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) +{ + struct input_state *istate = data; + bool have_pointer = capabilities & WL_SEAT_CAPABILITY_POINTER; + + if (have_pointer && wl_pointer == NULL) { + wl_pointer = wl_seat_get_pointer(wl_seat); + wl_pointer_add_listener(wl_pointer, + &wl_pointer_listener, istate); + } else if (!have_pointer && wl_pointer != NULL) { + wl_pointer_release(wl_pointer); + wl_pointer = NULL; + } +} + +static void +wl_seat_name(void *data, struct wl_seat *wl_seat, const char *name) +{ + fprintf(stderr, "seat name: %s\n", name); +} + +static const struct wl_seat_listener wl_seat_listener = { + .capabilities = wl_seat_capabilities, + .name = wl_seat_name, +}; + static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { + struct input_state *istate = data; if (strcmp(interface, wl_compositor_interface.name) == 0) { compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4); + } else if (strcmp(interface, wl_subcompositor_interface.name) == 0) { + subcompositor = wl_registry_bind( + registry, name, &wl_subcompositor_interface, 1); } else if (strcmp(interface, wl_shm_interface.name) == 0) { shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); } else if (strcmp(interface, wl_output_interface.name) == 0) { @@ -362,10 +538,17 @@ handle_global(void *data, struct wl_registry *registry, } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + wl_seat = wl_registry_bind(registry, name, &wl_seat_interface, 5); + wl_seat_add_listener(wl_seat, &wl_seat_listener, istate); } + } -static const struct wl_registry_listener registry_listener = {.global = handle_global,}; +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = noop, +}; static void read_stdin(void) @@ -563,7 +746,12 @@ main(int argc, char **argv) BARF("Failed to create display"); struct wl_registry *registry = wl_display_get_registry(display); - wl_registry_add_listener(registry, ®istry_listener, NULL); + + struct input_state *istate = calloc(1, sizeof(struct input_state)); + if (!istate) + BARF("Failed to create inputstate"); + + wl_registry_add_listener(registry, ®istry_listener, istate); wl_display_roundtrip(display); if (!compositor || !shm || !layer_shell) @@ -609,6 +797,7 @@ main(int argc, char **argv) wl_compositor_destroy(compositor); wl_registry_destroy(registry); wl_display_disconnect(display); + free(istate); return 0; }