sway/swaybar/ipc.c
Furkan Sahin 00e9a94152 swaybar: Fix 100% cpu usage if dbus dies.
Currently, swaybar does not gracefully die if it detects
that the dbus connection was lost. Although it's not recommended
to restart dbus without restarting the compositor, it can very
easily happen. In the case it does, compositor's tray should
not consume 100% cpu until it has to be force killed.

apply suggestions

just setting the bar to not running will call teardown and unref the
dbus.
2024-09-28 16:23:21 +02:00

604 lines
19 KiB
C

#include <limits.h>
#include <poll.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <json.h>
#include "swaybar/config.h"
#include "swaybar/ipc.h"
#include "swaybar/status_line.h"
#if HAVE_TRAY
#include "swaybar/tray/tray.h"
#endif
#include "config.h"
#include "ipc-client.h"
#include "list.h"
#include "log.h"
#include "loop.h"
#include "util.h"
void ipc_send_workspace_command(struct swaybar *bar, const char *ws) {
uint32_t size = strlen("workspace \"\"") + strlen(ws);
for (size_t i = 0; i < strlen(ws); ++i) {
if (ws[i] == '"' || ws[i] == '\\') {
++size;
}
}
char *command = malloc(size + 1);
if (!command) {
return;
}
strcpy(command, "workspace \"");
strcpy(&command[size - 1], "\"");
for (size_t i = 0, d = strlen("workspace \""); i < strlen(ws); ++i) {
if (ws[i] == '"' || ws[i] == '\\') {
command[d++] = '\\';
}
command[d++] = ws[i];
}
free(ipc_single_command(bar->ipc_socketfd, IPC_COMMAND, command, &size));
free(command);
}
char *parse_font(const char *font) {
char *new_font = NULL;
if (strncmp("pango:", font, 6) == 0) {
font += 6;
}
new_font = strdup(font);
return new_font;
}
static void ipc_parse_colors(
struct swaybar_config *config, json_object *colors) {
struct {
const char *name;
uint32_t *color;
} properties[] = {
{ "background", &config->colors.background },
{ "statusline", &config->colors.statusline },
{ "separator", &config->colors.separator },
{ "focused_background", &config->colors.focused_background },
{ "focused_statusline", &config->colors.focused_statusline },
{ "focused_separator", &config->colors.focused_separator },
{ "focused_workspace_border", &config->colors.focused_workspace.border },
{ "focused_workspace_bg", &config->colors.focused_workspace.background },
{ "focused_workspace_text", &config->colors.focused_workspace.text },
{ "active_workspace_border", &config->colors.active_workspace.border },
{ "active_workspace_bg", &config->colors.active_workspace.background },
{ "active_workspace_text", &config->colors.active_workspace.text },
{ "inactive_workspace_border", &config->colors.inactive_workspace.border },
{ "inactive_workspace_bg", &config->colors.inactive_workspace.background },
{ "inactive_workspace_text", &config->colors.inactive_workspace.text },
{ "urgent_workspace_border", &config->colors.urgent_workspace.border },
{ "urgent_workspace_bg", &config->colors.urgent_workspace.background },
{ "urgent_workspace_text", &config->colors.urgent_workspace.text },
{ "binding_mode_border", &config->colors.binding_mode.border },
{ "binding_mode_bg", &config->colors.binding_mode.background },
{ "binding_mode_text", &config->colors.binding_mode.text },
};
for (size_t i = 0; i < sizeof(properties) / sizeof(properties[i]); i++) {
json_object *object;
if (json_object_object_get_ex(colors, properties[i].name, &object)) {
const char *hexstring = json_object_get_string(object);
if (!parse_color(hexstring, properties[i].color)) {
sway_log(SWAY_ERROR, "Ignoring invalid %s: %s",
properties[i].name, hexstring);
}
}
}
}
static bool ipc_parse_config(
struct swaybar_config *config, const char *payload) {
json_object *bar_config = json_tokener_parse(payload);
json_object *success;
if (json_object_object_get_ex(bar_config, "success", &success)
&& !json_object_get_boolean(success)) {
sway_log(SWAY_ERROR, "No bar with that ID. Use 'swaymsg -t "
"get_bar_config' to get the available bar configs.");
json_object_put(bar_config);
return false;
}
json_object *bar_height = json_object_object_get(bar_config, "bar_height");
if (bar_height) {
config->height = json_object_get_int(bar_height);
}
json_object *binding_mode_indicator =
json_object_object_get(bar_config, "binding_mode_indicator");
if (binding_mode_indicator) {
config->binding_mode_indicator =
json_object_get_boolean(binding_mode_indicator);
}
json_object *bindings = json_object_object_get(bar_config, "bindings");
while (config->bindings->length) {
struct swaybar_binding *binding = config->bindings->items[0];
list_del(config->bindings, 0);
free_binding(binding);
}
if (bindings) {
int length = json_object_array_length(bindings);
for (int i = 0; i < length; ++i) {
json_object *bindobj = json_object_array_get_idx(bindings, i);
struct swaybar_binding *binding =
calloc(1, sizeof(struct swaybar_binding));
binding->button = json_object_get_int(
json_object_object_get(bindobj, "event_code"));
binding->command = strdup(json_object_get_string(
json_object_object_get(bindobj, "command")));
binding->release = json_object_get_boolean(
json_object_object_get(bindobj, "release"));
list_add(config->bindings, binding);
}
}
json_object *colors = json_object_object_get(bar_config, "colors");
if (colors) {
ipc_parse_colors(config, colors);
}
json_object *font = json_object_object_get(bar_config, "font");
if (font) {
pango_font_description_free(config->font_description);
char *font_value = parse_font(json_object_get_string(font));
config->font_description = pango_font_description_from_string(font_value);
free(font_value);
}
json_object *gaps = json_object_object_get(bar_config, "gaps");
if (gaps) {
json_object *top = json_object_object_get(gaps, "top");
if (top) {
config->gaps.top = json_object_get_int(top);
}
json_object *right = json_object_object_get(gaps, "right");
if (right) {
config->gaps.right = json_object_get_int(right);
}
json_object *bottom = json_object_object_get(gaps, "bottom");
if (bottom) {
config->gaps.bottom = json_object_get_int(bottom);
}
json_object *left = json_object_object_get(gaps, "left");
if (left) {
config->gaps.left = json_object_get_int(left);
}
}
json_object *hidden_state =
json_object_object_get(bar_config, "hidden_state");
if (hidden_state) {
free(config->hidden_state);
config->hidden_state = strdup(json_object_get_string(hidden_state));
}
json_object *mode = json_object_object_get(bar_config, "mode");
if (mode) {
free(config->mode);
config->mode = strdup(json_object_get_string(mode));
}
json_object *outputs = json_object_object_get(bar_config, "outputs");
struct config_output *output, *tmp;
wl_list_for_each_safe(output, tmp, &config->outputs, link) {
wl_list_remove(&output->link);
free(output->name);
free(output);
}
if (outputs) {
int length = json_object_array_length(outputs);
for (int i = 0; i < length; ++i) {
json_object *output = json_object_array_get_idx(outputs, i);
const char *name = json_object_get_string(output);
if (strcmp("*", name) == 0) {
struct config_output *coutput, *tmp;
wl_list_for_each_safe(coutput, tmp, &config->outputs, link) {
wl_list_remove(&coutput->link);
free(coutput->name);
free(coutput);
}
break;
}
struct config_output *coutput = calloc(
1, sizeof(struct config_output));
coutput->name = strdup(name);
wl_list_insert(&config->outputs, &coutput->link);
}
}
json_object *pango_markup =
json_object_object_get(bar_config, "pango_markup");
if (pango_markup) {
config->pango_markup = json_object_get_boolean(pango_markup);
}
json_object *position = json_object_object_get(bar_config, "position");
if (position) {
config->position = parse_position(json_object_get_string(position));
}
json_object *separator_symbol =
json_object_object_get(bar_config, "separator_symbol");
if (separator_symbol) {
free(config->sep_symbol);
config->sep_symbol = strdup(json_object_get_string(separator_symbol));
}
json_object *status_command =
json_object_object_get(bar_config, "status_command");
if (status_command) {
const char *command = json_object_get_string(status_command);
free(config->status_command);
config->status_command = strdup(command);
}
json_object *status_edge_padding =
json_object_object_get(bar_config, "status_edge_padding");
if (status_edge_padding) {
config->status_edge_padding = json_object_get_int(status_edge_padding);
}
json_object *status_padding =
json_object_object_get(bar_config, "status_padding");
if (status_padding) {
config->status_padding = json_object_get_int(status_padding);
}
json_object *strip_workspace_name =
json_object_object_get(bar_config, "strip_workspace_name");
if (strip_workspace_name) {
config->strip_workspace_name =
json_object_get_boolean(strip_workspace_name);
}
json_object *strip_workspace_numbers =
json_object_object_get(bar_config, "strip_workspace_numbers");
if (strip_workspace_numbers) {
config->strip_workspace_numbers =
json_object_get_boolean(strip_workspace_numbers);
}
json_object *workspace_buttons =
json_object_object_get(bar_config, "workspace_buttons");
if (workspace_buttons) {
config->workspace_buttons = json_object_get_boolean(workspace_buttons);
}
json_object *workspace_min_width =
json_object_object_get(bar_config, "workspace_min_width");
if (workspace_min_width) {
config->workspace_min_width = json_object_get_int(workspace_min_width);
}
json_object *wrap_scroll = json_object_object_get(bar_config, "wrap_scroll");
if (wrap_scroll) {
config->wrap_scroll = json_object_get_boolean(wrap_scroll);
}
#if HAVE_TRAY
json_object *tray_outputs, *tray_padding, *tray_bindings, *icon_theme;
if (config->tray_outputs && config->tray_outputs->length) {
list_free_items_and_destroy(config->tray_outputs);
}
if ((json_object_object_get_ex(bar_config, "tray_outputs", &tray_outputs))) {
config->tray_outputs = create_list();
int length = json_object_array_length(tray_outputs);
for (int i = 0; i < length; ++i) {
json_object *output= json_object_array_get_idx(tray_outputs, i);
const char *name = json_object_get_string(output);
if (strcmp(name, "none") == 0) {
config->tray_hidden = true;
list_free_items_and_destroy(config->tray_outputs);
config->tray_outputs = create_list();
break;
}
list_add(config->tray_outputs, strdup(name));
}
}
if ((json_object_object_get_ex(bar_config, "tray_padding", &tray_padding))) {
config->tray_padding = json_object_get_int(tray_padding);
}
struct tray_binding *tray_bind = NULL, *tmp_tray_bind = NULL;
wl_list_for_each_safe(tray_bind, tmp_tray_bind, &config->tray_bindings,
link) {
wl_list_remove(&tray_bind->link);
free_tray_binding(tray_bind);
}
if ((json_object_object_get_ex(bar_config, "tray_bindings", &tray_bindings))) {
int length = json_object_array_length(tray_bindings);
for (int i = 0; i < length; ++i) {
json_object *bind = json_object_array_get_idx(tray_bindings, i);
struct tray_binding *binding =
calloc(1, sizeof(struct tray_binding));
binding->button = json_object_get_int(
json_object_object_get(bind, "event_code"));
binding->command = strdup(json_object_get_string(
json_object_object_get(bind, "command")));
wl_list_insert(&config->tray_bindings, &binding->link);
}
}
if ((json_object_object_get_ex(bar_config, "icon_theme", &icon_theme))) {
config->icon_theme = strdup(json_object_get_string(icon_theme));
}
#endif
json_object_put(bar_config);
return true;
}
bool ipc_get_workspaces(struct swaybar *bar) {
struct swaybar_output *output;
wl_list_for_each(output, &bar->outputs, link) {
free_workspaces(&output->workspaces);
output->focused = false;
}
uint32_t len = 0;
char *res = ipc_single_command(bar->ipc_socketfd,
IPC_GET_WORKSPACES, NULL, &len);
json_object *results = json_tokener_parse(res);
if (!results) {
free(res);
return false;
}
bar->visible_by_urgency = false;
size_t length = json_object_array_length(results);
json_object *ws_json;
json_object *num, *name, *visible, *focused, *out, *urgent;
for (size_t i = 0; i < length; ++i) {
ws_json = json_object_array_get_idx(results, i);
json_object_object_get_ex(ws_json, "num", &num);
json_object_object_get_ex(ws_json, "name", &name);
json_object_object_get_ex(ws_json, "visible", &visible);
json_object_object_get_ex(ws_json, "focused", &focused);
json_object_object_get_ex(ws_json, "output", &out);
json_object_object_get_ex(ws_json, "urgent", &urgent);
wl_list_for_each(output, &bar->outputs, link) {
const char *ws_output = json_object_get_string(out);
if (ws_output != NULL && strcmp(ws_output, output->name) == 0) {
struct swaybar_workspace *ws =
calloc(1, sizeof(struct swaybar_workspace));
ws->num = json_object_get_int(num);
ws->name = strdup(json_object_get_string(name));
ws->label = strdup(ws->name);
// ws->num will be -1 if workspace name doesn't begin with int.
if (ws->num != -1) {
size_t len_offset = snprintf(NULL, 0, "%d", ws->num);
if (bar->config->strip_workspace_name) {
free(ws->label);
ws->label = malloc(len_offset + 1);
snprintf(ws->label, len_offset + 1, "%d", ws->num);
} else if (bar->config->strip_workspace_numbers) {
len_offset += ws->label[len_offset] == ':';
if (ws->name[len_offset] != '\0') {
free(ws->label);
// Strip number prefix [1-?:] using len_offset.
ws->label = strdup(ws->name + len_offset);
}
}
}
ws->visible = json_object_get_boolean(visible);
ws->focused = json_object_get_boolean(focused);
if (ws->focused) {
output->focused = true;
}
ws->urgent = json_object_get_boolean(urgent);
if (ws->urgent) {
bar->visible_by_urgency = true;
}
wl_list_insert(output->workspaces.prev, &ws->link);
}
}
}
json_object_put(results);
free(res);
return determine_bar_visibility(bar, false);
}
void ipc_execute_binding(struct swaybar *bar, struct swaybar_binding *bind) {
sway_log(SWAY_DEBUG, "Executing binding for button %u (release=%d): `%s`",
bind->button, bind->release, bind->command);
uint32_t len = strlen(bind->command);
free(ipc_single_command(bar->ipc_socketfd,
IPC_COMMAND, bind->command, &len));
}
bool ipc_initialize(struct swaybar *bar) {
uint32_t len = strlen(bar->id);
char *res = ipc_single_command(bar->ipc_socketfd,
IPC_GET_BAR_CONFIG, bar->id, &len);
if (!ipc_parse_config(bar->config, res)) {
free(res);
return false;
}
free(res);
char *subscribe =
"[ \"barconfig_update\", \"bar_state_update\", \"mode\", \"workspace\" ]";
len = strlen(subscribe);
free(ipc_single_command(bar->ipc_event_socketfd,
IPC_SUBSCRIBE, subscribe, &len));
return true;
}
static bool handle_bar_state_update(struct swaybar *bar, json_object *event) {
json_object *json_id;
json_object_object_get_ex(event, "id", &json_id);
const char *id = json_object_get_string(json_id);
if (strcmp(id, bar->id) != 0) {
return false;
}
json_object *visible_by_modifier;
json_object_object_get_ex(event, "visible_by_modifier", &visible_by_modifier);
bar->visible_by_modifier = json_object_get_boolean(visible_by_modifier);
if (bar->visible_by_modifier) {
// If the bar is visible by modifier, clear both visible by mode and
// urgency as modifier has precedence and the bar should be hidden
// again when it is no longer visible by modifier.
bar->visible_by_mode = false;
bar->visible_by_urgency = false;
}
return determine_bar_visibility(bar, false);
}
static bool handle_barconfig_update(struct swaybar *bar, const char *payload,
json_object *json_config) {
json_object *json_id = json_object_object_get(json_config, "id");
const char *id = json_object_get_string(json_id);
if (strcmp(id, bar->id) != 0) {
return false;
}
struct swaybar_config *newcfg = init_config();
ipc_parse_config(newcfg, payload);
struct swaybar_config *oldcfg = bar->config;
bar->config = newcfg;
struct swaybar_output *output, *tmp_output;
wl_list_for_each_safe(output, tmp_output, &bar->outputs, link) {
bool found = wl_list_empty(&newcfg->outputs);
struct config_output *coutput;
wl_list_for_each(coutput, &newcfg->outputs, link) {
if (strcmp(coutput->name, output->name) == 0 ||
strcmp(coutput->name, output->identifier) == 0) {
found = true;
break;
}
}
if (!found) {
destroy_layer_surface(output);
wl_list_remove(&output->link);
wl_list_insert(&bar->unused_outputs, &output->link);
} else if (!pango_font_description_equal(oldcfg->font_description, newcfg->font_description)) {
output->height = 0; // force update height
}
}
wl_list_for_each_safe(output, tmp_output, &bar->unused_outputs, link) {
bool found = wl_list_empty(&newcfg->outputs);
struct config_output *coutput;
wl_list_for_each(coutput, &newcfg->outputs, link) {
if (strcmp(coutput->name, output->name) == 0 ||
strcmp(coutput->name, output->identifier) == 0) {
found = true;
break;
}
}
if (found) {
wl_list_remove(&output->link);
wl_list_insert(&bar->outputs, &output->link);
}
}
if (bar->status && (!newcfg->status_command ||
strcmp(newcfg->status_command, oldcfg->status_command) != 0)) {
status_line_free(bar->status);
bar->status = NULL;
}
if (!bar->status && newcfg->status_command) {
bar->status = status_line_init(newcfg->status_command);
bar->status->bar = bar;
loop_add_fd(bar->eventloop, bar->status->read_fd, POLLIN,
status_in, bar);
}
#if HAVE_TRAY
if (oldcfg->tray_hidden && !newcfg->tray_hidden) {
bar->tray = create_tray(bar);
loop_add_fd(bar->eventloop, bar->tray->fd, POLLIN, tray_in, bar);
} else if (bar->tray && newcfg->tray_hidden) {
loop_remove_fd(bar->eventloop, bar->tray->fd);
destroy_tray(bar->tray);
bar->tray = NULL;
}
#endif
if (newcfg->workspace_buttons) {
ipc_get_workspaces(bar);
}
bool moving_layer = strcmp(oldcfg->mode, newcfg->mode) != 0;
free_config(oldcfg);
determine_bar_visibility(bar, moving_layer);
return true;
}
bool handle_ipc_readable(struct swaybar *bar) {
struct ipc_response *resp = ipc_recv_response(bar->ipc_event_socketfd);
if (!resp) {
return false;
}
// The default depth of 32 is too small to represent some nested layouts, but
// we can't pass INT_MAX here because json-c (as of this writing) prefaults
// all the memory for its stack.
json_tokener *tok = json_tokener_new_ex(JSON_MAX_DEPTH);
if (!tok) {
sway_log_errno(SWAY_ERROR, "failed to create tokener");
free_ipc_response(resp);
return false;
}
json_object *result = json_tokener_parse_ex(tok, resp->payload, -1);
enum json_tokener_error err = json_tokener_get_error(tok);
json_tokener_free(tok);
if (err != json_tokener_success) {
sway_log(SWAY_ERROR, "failed to parse payload as json: %s",
json_tokener_error_desc(err));
free_ipc_response(resp);
return false;
}
bool bar_is_dirty = true;
switch (resp->type) {
case IPC_EVENT_WORKSPACE:
bar_is_dirty = ipc_get_workspaces(bar);
break;
case IPC_EVENT_MODE: {
json_object *json_change, *json_pango_markup;
if (json_object_object_get_ex(result, "change", &json_change)) {
const char *change = json_object_get_string(json_change);
free(bar->mode);
bar->mode = strcmp(change, "default") != 0 ? strdup(change) : NULL;
bar->visible_by_mode = bar->mode != NULL;
determine_bar_visibility(bar, false);
} else {
sway_log(SWAY_ERROR, "failed to parse response");
bar_is_dirty = false;
break;
}
if (json_object_object_get_ex(result,
"pango_markup", &json_pango_markup)) {
bar->mode_pango_markup = json_object_get_boolean(json_pango_markup);
}
break;
}
case IPC_EVENT_BARCONFIG_UPDATE:
bar_is_dirty = handle_barconfig_update(bar, resp->payload, result);
break;
case IPC_EVENT_BAR_STATE_UPDATE:
bar_is_dirty = handle_bar_state_update(bar, result);
break;
default:
bar_is_dirty = false;
break;
}
json_object_put(result);
free_ipc_response(resp);
return bar_is_dirty;
}