3d6440ec26
This modifies `bar_cmd_bindsym` to use `get_mouse_bindsym` for parsing mouse buttons. This also introduces `cmd_bar_bindcode`, which will use `get_mouse_bindcode` for parsing mouse buttons. Like sway bindings, the two commands are encapsulated in a single file with shared code. This also modifies swaybar to operate off of event codes rather than x11 button numbers, which allows for any mouse button to be used. This introduces two new IPC properties: - For `get_bar_config`, `event_code` has been added to the `bindings` section and will include to event code for the button. If the event code can be mapped to a x11 button, `input_code` will still be the x11 button number. Otherwise, `input_code` will be `0`. - Likewise for `click_events`, `event` has been added and will include the event code for the button clicked. If the event code can be mapped to a x11 button, `button` will still be the x11 button number. Otherwise, `button` will be `0`.
320 lines
11 KiB
C
320 lines
11 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
#include <json-c/json.h>
|
|
#include <linux/input-event-codes.h>
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <wlr/util/log.h>
|
|
#include "swaybar/bar.h"
|
|
#include "swaybar/config.h"
|
|
#include "swaybar/i3bar.h"
|
|
#include "swaybar/input.h"
|
|
#include "swaybar/status_line.h"
|
|
|
|
void i3bar_block_unref(struct i3bar_block *block) {
|
|
if (block == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (--block->ref_count == 0) {
|
|
free(block->full_text);
|
|
free(block->short_text);
|
|
free(block->align);
|
|
free(block->name);
|
|
free(block->instance);
|
|
free(block->color);
|
|
free(block);
|
|
}
|
|
}
|
|
|
|
static void i3bar_parse_json(struct status_line *status,
|
|
struct json_object *json_array) {
|
|
struct i3bar_block *block, *tmp;
|
|
wl_list_for_each_safe(block, tmp, &status->blocks, link) {
|
|
wl_list_remove(&block->link);
|
|
i3bar_block_unref(block);
|
|
}
|
|
for (size_t i = 0; i < json_object_array_length(json_array); ++i) {
|
|
json_object *full_text, *short_text, *color, *min_width, *align, *urgent;
|
|
json_object *name, *instance, *separator, *separator_block_width;
|
|
json_object *background, *border, *border_top, *border_bottom;
|
|
json_object *border_left, *border_right, *markup;
|
|
json_object *json = json_object_array_get_idx(json_array, i);
|
|
if (!json) {
|
|
continue;
|
|
}
|
|
json_object_object_get_ex(json, "full_text", &full_text);
|
|
json_object_object_get_ex(json, "short_text", &short_text);
|
|
json_object_object_get_ex(json, "color", &color);
|
|
json_object_object_get_ex(json, "min_width", &min_width);
|
|
json_object_object_get_ex(json, "align", &align);
|
|
json_object_object_get_ex(json, "urgent", &urgent);
|
|
json_object_object_get_ex(json, "name", &name);
|
|
json_object_object_get_ex(json, "instance", &instance);
|
|
json_object_object_get_ex(json, "markup", &markup);
|
|
json_object_object_get_ex(json, "separator", &separator);
|
|
json_object_object_get_ex(json, "separator_block_width", &separator_block_width);
|
|
json_object_object_get_ex(json, "background", &background);
|
|
json_object_object_get_ex(json, "border", &border);
|
|
json_object_object_get_ex(json, "border_top", &border_top);
|
|
json_object_object_get_ex(json, "border_bottom", &border_bottom);
|
|
json_object_object_get_ex(json, "border_left", &border_left);
|
|
json_object_object_get_ex(json, "border_right", &border_right);
|
|
|
|
struct i3bar_block *block = calloc(1, sizeof(struct i3bar_block));
|
|
block->ref_count = 1;
|
|
block->full_text = full_text ?
|
|
strdup(json_object_get_string(full_text)) : NULL;
|
|
block->short_text = short_text ?
|
|
strdup(json_object_get_string(short_text)) : NULL;
|
|
if (color) {
|
|
block->color = malloc(sizeof(uint32_t));
|
|
*block->color = parse_color(json_object_get_string(color));
|
|
}
|
|
if (min_width) {
|
|
json_type type = json_object_get_type(min_width);
|
|
if (type == json_type_int) {
|
|
block->min_width = json_object_get_int(min_width);
|
|
} else if (type == json_type_string) {
|
|
/* the width will be calculated when rendering */
|
|
block->min_width = 0;
|
|
}
|
|
}
|
|
block->align = strdup(align ? json_object_get_string(align) : "left");
|
|
block->urgent = urgent ? json_object_get_int(urgent) : false;
|
|
block->name = name ? strdup(json_object_get_string(name)) : NULL;
|
|
block->instance = instance ?
|
|
strdup(json_object_get_string(instance)) : NULL;
|
|
if (markup) {
|
|
block->markup = false;
|
|
const char *markup_str = json_object_get_string(markup);
|
|
if (strcmp(markup_str, "pango") == 0) {
|
|
block->markup = true;
|
|
}
|
|
}
|
|
block->separator = separator ? json_object_get_int(separator) : true;
|
|
block->separator_block_width = separator_block_width ?
|
|
json_object_get_int(separator_block_width) : 9;
|
|
// Airblader features
|
|
block->background = background ?
|
|
parse_color(json_object_get_string(background)) : 0;
|
|
block->border = border ?
|
|
parse_color(json_object_get_string(border)) : 0;
|
|
block->border_top = border_top ? json_object_get_int(border_top) : 1;
|
|
block->border_bottom = border_bottom ?
|
|
json_object_get_int(border_bottom) : 1;
|
|
block->border_left = border_left ? json_object_get_int(border_left) : 1;
|
|
block->border_right = border_right ?
|
|
json_object_get_int(border_right) : 1;
|
|
wl_list_insert(&status->blocks, &block->link);
|
|
}
|
|
}
|
|
|
|
bool i3bar_handle_readable(struct status_line *status) {
|
|
while (!status->started) { // look for opening bracket
|
|
for (size_t c = 0; c < status->buffer_index; ++c) {
|
|
if (status->buffer[c] == '[') {
|
|
status->started = true;
|
|
status->buffer_index -= ++c;
|
|
memmove(status->buffer, &status->buffer[c], status->buffer_index);
|
|
break;
|
|
} else if (!isspace(status->buffer[c])) {
|
|
wlr_log(WLR_DEBUG, "Invalid i3bar json: expected '[' but encountered '%c'",
|
|
status->buffer[c]);
|
|
status_error(status, "[invalid i3bar json]");
|
|
return true;
|
|
}
|
|
}
|
|
if (status->started) {
|
|
break;
|
|
}
|
|
|
|
errno = 0;
|
|
ssize_t read_bytes = read(status->read_fd, status->buffer, status->buffer_size);
|
|
if (read_bytes > -1) {
|
|
status->buffer_index = read_bytes;
|
|
} else if (errno == EAGAIN) {
|
|
return false;
|
|
} else {
|
|
status_error(status, "[error reading from status command]");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
struct json_object *last_object = NULL;
|
|
struct json_object *test_object;
|
|
size_t buffer_pos = 0;
|
|
while (true) {
|
|
// since the incoming stream is an infinite array
|
|
// parsing is split into two parts
|
|
// first, attempt to parse the current object, reading more if the
|
|
// parser indicates that the current object is incomplete, and failing
|
|
// if the parser fails
|
|
// second, look for separating comma, ignoring whitespace, failing if
|
|
// any other characters are encountered
|
|
if (status->expecting_comma) {
|
|
for (; buffer_pos < status->buffer_index; ++buffer_pos) {
|
|
if (status->buffer[buffer_pos] == ',') {
|
|
status->expecting_comma = false;
|
|
++buffer_pos;
|
|
break;
|
|
} else if (!isspace(status->buffer[buffer_pos])) {
|
|
wlr_log(WLR_DEBUG, "Invalid i3bar json: expected ',' but encountered '%c'",
|
|
status->buffer[buffer_pos]);
|
|
status_error(status, "[invalid i3bar json]");
|
|
return true;
|
|
}
|
|
}
|
|
if (buffer_pos < status->buffer_index) {
|
|
continue; // look for new object without reading more input
|
|
}
|
|
buffer_pos = status->buffer_index = 0;
|
|
} else {
|
|
test_object = json_tokener_parse_ex(status->tokener,
|
|
&status->buffer[buffer_pos], status->buffer_index - buffer_pos);
|
|
enum json_tokener_error err = json_tokener_get_error(status->tokener);
|
|
if (err == json_tokener_success) {
|
|
if (json_object_get_type(test_object) == json_type_array) {
|
|
if (last_object) {
|
|
json_object_put(last_object);
|
|
}
|
|
last_object = test_object;
|
|
} else {
|
|
json_object_put(test_object);
|
|
}
|
|
|
|
// in order to print the json for debugging purposes
|
|
// the last character is temporarily replaced with a null character
|
|
// (the last character is used in case the buffer is full)
|
|
char *last_char_pos =
|
|
&status->buffer[buffer_pos + status->tokener->char_offset - 1];
|
|
char last_char = *last_char_pos;
|
|
while (isspace(last_char)) {
|
|
last_char = *--last_char_pos;
|
|
}
|
|
*last_char_pos = '\0';
|
|
size_t offset = strspn(&status->buffer[buffer_pos], " \f\n\r\t\v");
|
|
wlr_log(WLR_DEBUG, "Received i3bar json: '%s%c'",
|
|
&status->buffer[buffer_pos + offset], last_char);
|
|
*last_char_pos = last_char;
|
|
|
|
buffer_pos += status->tokener->char_offset;
|
|
status->expecting_comma = true;
|
|
|
|
if (buffer_pos < status->buffer_index) {
|
|
continue; // look for comma without reading more input
|
|
}
|
|
buffer_pos = status->buffer_index = 0;
|
|
} else if (err == json_tokener_continue) {
|
|
json_tokener_reset(status->tokener);
|
|
if (status->buffer_index < status->buffer_size) {
|
|
// move the object to the start of the buffer
|
|
status->buffer_index -= buffer_pos;
|
|
memmove(status->buffer, &status->buffer[buffer_pos],
|
|
status->buffer_index);
|
|
buffer_pos = 0;
|
|
} else {
|
|
// expand buffer
|
|
status->buffer_size *= 2;
|
|
char *new_buffer = realloc(status->buffer, status->buffer_size);
|
|
if (new_buffer) {
|
|
status->buffer = new_buffer;
|
|
} else {
|
|
free(status->buffer);
|
|
status_error(status, "[failed to allocate buffer]");
|
|
return true;
|
|
}
|
|
}
|
|
} else {
|
|
char last_char = status->buffer[status->buffer_index - 1];
|
|
status->buffer[status->buffer_index - 1] = '\0';
|
|
wlr_log(WLR_DEBUG, "Failed to parse i3bar json - %s: '%s%c'",
|
|
json_tokener_error_desc(err), &status->buffer[buffer_pos], last_char);
|
|
status_error(status, "[failed to parse i3bar json]");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
errno = 0;
|
|
ssize_t read_bytes = read(status->read_fd, &status->buffer[status->buffer_index],
|
|
status->buffer_size - status->buffer_index);
|
|
if (read_bytes > -1) {
|
|
status->buffer_index += read_bytes;
|
|
} else if (errno == EAGAIN) {
|
|
break;
|
|
} else {
|
|
status_error(status, "[error reading from status command]");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (last_object) {
|
|
wlr_log(WLR_DEBUG, "Rendering last received json");
|
|
i3bar_parse_json(status, last_object);
|
|
json_object_put(last_object);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static uint32_t event_to_x11_button(uint32_t event) {
|
|
switch (event) {
|
|
case BTN_LEFT:
|
|
return 1;
|
|
case BTN_MIDDLE:
|
|
return 2;
|
|
case BTN_RIGHT:
|
|
return 3;
|
|
case SWAY_SCROLL_UP:
|
|
return 4;
|
|
case SWAY_SCROLL_DOWN:
|
|
return 5;
|
|
case SWAY_SCROLL_LEFT:
|
|
return 6;
|
|
case SWAY_SCROLL_RIGHT:
|
|
return 7;
|
|
case BTN_SIDE:
|
|
return 8;
|
|
case BTN_EXTRA:
|
|
return 9;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
enum hotspot_event_handling i3bar_block_send_click(struct status_line *status,
|
|
struct i3bar_block *block, int x, int y, int rx, int ry, int w, int h,
|
|
uint32_t button) {
|
|
wlr_log(WLR_DEBUG, "block %s clicked", block->name);
|
|
if (!block->name || !status->click_events) {
|
|
return HOTSPOT_PROCESS;
|
|
}
|
|
|
|
struct json_object *event_json = json_object_new_object();
|
|
json_object_object_add(event_json, "name",
|
|
json_object_new_string(block->name));
|
|
if (block->instance) {
|
|
json_object_object_add(event_json, "instance",
|
|
json_object_new_string(block->instance));
|
|
}
|
|
|
|
json_object_object_add(event_json, "button",
|
|
json_object_new_int(event_to_x11_button(button)));
|
|
json_object_object_add(event_json, "event", json_object_new_int(button));
|
|
json_object_object_add(event_json, "x", json_object_new_int(x));
|
|
json_object_object_add(event_json, "y", json_object_new_int(y));
|
|
json_object_object_add(event_json, "relative_x", json_object_new_int(rx));
|
|
json_object_object_add(event_json, "relative_y", json_object_new_int(ry));
|
|
json_object_object_add(event_json, "width", json_object_new_int(w));
|
|
json_object_object_add(event_json, "height", json_object_new_int(h));
|
|
if (dprintf(status->write_fd, "%s%s\n", status->clicked ? "," : "",
|
|
json_object_to_json_string(event_json)) < 0) {
|
|
status_error(status, "[failed to write click event]");
|
|
}
|
|
status->clicked = true;
|
|
json_object_put(event_json);
|
|
return HOTSPOT_IGNORE;
|
|
}
|