Start port of swaybar to layer shell

This starts up the event loop and wayland display and shims out the
basic top level rendering concepts. Also includes some changes to
incorporate pango into the 1.x codebase properly.
This commit is contained in:
Drew DeVault 2018-03-28 23:04:20 -04:00
parent 382e8af418
commit cab1352801
23 changed files with 345 additions and 3787 deletions

View file

@ -1,5 +1,7 @@
deps = [ deps = [
cairo, cairo,
pango,
pangocairo,
wlroots wlroots
] ]
@ -14,6 +16,7 @@ lib_sway_common = static_library(
'ipc-client.c', 'ipc-client.c',
'log.c', 'log.c',
'list.c', 'list.c',
'pango.c',
'readline.c', 'readline.c',
'stringop.c', 'stringop.c',
'util.c' 'util.c'

67
common/pango.c Normal file
View file

@ -0,0 +1,67 @@
#include <cairo/cairo.h>
#include <pango/pangocairo.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
PangoLayout *get_pango_layout(cairo_t *cairo, const char *font,
const char *text, int32_t scale, bool markup) {
PangoLayout *layout = pango_cairo_create_layout(cairo);
PangoAttrList *attrs;
if (markup) {
char *buf;
pango_parse_markup(text, -1, 0, &attrs, &buf, NULL, NULL);
pango_layout_set_markup(layout, buf, -1);
free(buf);
} else {
attrs = pango_attr_list_new();
pango_layout_set_text(layout, text, -1);
}
pango_attr_list_insert(attrs, pango_attr_scale_new(scale));
PangoFontDescription *desc = pango_font_description_from_string(font);
pango_layout_set_font_description(layout, desc);
pango_layout_set_single_paragraph_mode(layout, 1);
pango_layout_set_attributes(layout, attrs);
pango_attr_list_unref(attrs);
pango_font_description_free(desc);
return layout;
}
void get_text_size(cairo_t *cairo, const char *font, int *width, int *height,
int32_t scale, bool markup, const char *fmt, ...) {
char *buf = malloc(2048);
va_list args;
va_start(args, fmt);
if (vsnprintf(buf, 2048, fmt, args) >= 2048) {
strcpy(buf, "[buffer overflow]");
}
va_end(args);
PangoLayout *layout = get_pango_layout(cairo, font, buf, scale, markup);
pango_cairo_update_layout(cairo, layout);
pango_layout_get_pixel_size(layout, width, height);
g_object_unref(layout);
free(buf);
}
void pango_printf(cairo_t *cairo, const char *font,
int32_t scale, bool markup, const char *fmt, ...) {
char *buf = malloc(2048);
va_list args;
va_start(args, fmt);
if (vsnprintf(buf, 2048, fmt, args) >= 2048) {
strcpy(buf, "[buffer overflow]");
}
va_end(args);
PangoLayout *layout = get_pango_layout(cairo, font, buf, scale, markup);
pango_cairo_update_layout(cairo, layout);
pango_cairo_show_layout(cairo, layout);
g_object_unref(layout);
free(buf);
}

16
include/pango.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef _SWAY_PANGO_H
#define _SWAY_PANGO_H
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <cairo/cairo.h>
#include <pango/pangocairo.h>
PangoLayout *get_pango_layout(cairo_t *cairo, const char *font,
const char *text, int32_t scale, bool markup);
void get_text_size(cairo_t *cairo, const char *font, int *width, int *height,
int32_t scale, bool markup, const char *fmt, ...);
void pango_printf(cairo_t *cairo, const char *font,
int32_t scale, bool markup, const char *fmt, ...);
#endif

View file

@ -1,17 +1,16 @@
#ifndef _SWAY_CONFIG_H #ifndef _SWAY_CONFIG_H
#define _SWAY_CONFIG_H #define _SWAY_CONFIG_H
#define PID_WORKSPACE_TIMEOUT 60 #define PID_WORKSPACE_TIMEOUT 60
#include <libinput.h> #include <libinput.h>
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
#include <time.h>
#include <wlr/types/wlr_box.h> #include <wlr/types/wlr_box.h>
#include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbcommon.h>
#include <time.h>
#include "list.h" #include "list.h"
#include "layout.h" #include "layout.h"
#include "container.h" #include "container.h"
#include "wlr-layer-shell-unstable-v1-protocol.h"
/** /**
* Describes a variable created via the `set` command. * Describes a variable created via the `set` command.
@ -152,7 +151,7 @@ struct bar_config {
char *id; char *id;
uint32_t modifier; uint32_t modifier;
list_t *outputs; list_t *outputs;
//enum desktop_shell_panel_position position; // TODO char *position;
list_t *bindings; list_t *bindings;
char *status_command; char *status_command;
bool pango_markup; bool pango_markup;

View file

@ -1,72 +1,45 @@
#ifndef _SWAYBAR_BAR_H #ifndef _SWAYBAR_BAR_H
#define _SWAYBAR_BAR_H #define _SWAYBAR_BAR_H
#include <wayland-client.h>
#include "client/registry.h" #include "pool-buffer.h"
#include "client/window.h"
#include "list.h" #include "list.h"
struct bar { struct swaybar_config;
struct config *config; struct swaybar_output;
struct status_line *status; struct swaybar_workspace;
list_t *outputs;
struct output *focused_output;
int ipc_event_socketfd; struct swaybar {
int ipc_socketfd; struct wl_display *display;
int status_read_fd; struct wl_compositor *compositor;
int status_write_fd; struct zwlr_layer_shell_v1 *layer_shell;
pid_t status_command_pid; struct wl_shm *shm;
struct swaybar_config *config;
struct swaybar_output *focused_output;
struct wl_list outputs;
}; };
struct output { struct swaybar_output {
struct window *window; struct wl_list link;
struct registry *registry; struct swaybar *bar;
list_t *workspaces; struct wl_output *output;
#ifdef ENABLE_TRAY struct wl_surface *surface;
list_t *items; struct zwlr_layer_surface_v1 *layer_surface;
#endif
char *name; char *name;
int idx; int idx;
bool focused; bool focused;
uint32_t width, height;
struct pool_buffer buffers[2];
struct pool_buffer *current_buffer;
}; };
struct workspace { void bar_setup(struct swaybar *bar,
int num; const char *socket_path,
char *name; const char *bar_id);
bool focused; void bar_run(struct swaybar *bar);
bool visible; void bar_teardown(struct swaybar *bar);
bool urgent;
};
/** Global bar state */ #endif
extern struct bar swaybar;
/** True if sway needs to render */
extern bool dirty;
/**
* Setup bar.
*/
void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id);
/**
* Create new output struct from name.
*/
struct output *new_output(const char *name);
/**
* Bar mainloop.
*/
void bar_run(struct bar *bar);
/**
* free workspace list.
*/
void free_workspaces(list_t *workspaces);
/**
* Teardown bar.
*/
void bar_teardown(struct bar *bar);
#endif /* _SWAYBAR_BAR_H */

View file

@ -1,9 +1,7 @@
#ifndef _SWAYBAR_CONFIG_H #ifndef _SWAYBAR_CONFIG_H
#define _SWAYBAR_CONFIG_H #define _SWAYBAR_CONFIG_H
#include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
#include "list.h" #include "list.h"
#include "util.h" #include "util.h"
@ -19,10 +17,10 @@ struct box_colors {
/** /**
* Swaybar config. * Swaybar config.
*/ */
struct config { struct swaybar_config {
char *status_command; char *status_command;
bool pango_markup; bool pango_markup;
uint32_t position; uint32_t position; // zwlr_layer_surface_v1_anchor
char *font; char *font;
char *sep_symbol; char *sep_symbol;
char *mode; char *mode;
@ -32,18 +30,6 @@ struct config {
bool workspace_buttons; bool workspace_buttons;
bool all_outputs; bool all_outputs;
list_t *outputs; list_t *outputs;
#ifdef ENABLE_TRAY
// Tray
char *tray_output;
char *icon_theme;
uint32_t tray_padding;
uint32_t activate_button;
uint32_t context_button;
uint32_t secondary_button;
#endif
int height; int height;
struct { struct {
@ -63,24 +49,7 @@ struct config {
} colors; } colors;
}; };
/** struct swaybar_config *init_config();
* Parse position top|bottom|left|right. void free_config(struct swaybar_config *config);
*/
uint32_t parse_position(const char *position);
/** #endif
* Parse font.
*/
char *parse_font(const char *font);
/**
* Initialize default sway config.
*/
struct config *init_config();
/**
* Free config struct.
*/
void free_config(struct config *config);
#endif /* _SWAYBAR_CONFIG_H */

View file

@ -1,6 +1,5 @@
#ifndef _SWAYBAR_EVENT_LOOP_H #ifndef _SWAYBAR_EVENT_LOOP_H
#define _SWAYBAR_EVENT_LOOP_H #define _SWAYBAR_EVENT_LOOP_H
#include <stdbool.h> #include <stdbool.h>
#include <time.h> #include <time.h>
@ -23,4 +22,5 @@ bool remove_timer(timer_t timer);
void event_loop_poll(); void event_loop_poll();
void init_event_loop(); void init_event_loop();
#endif /*_SWAYBAR_EVENT_LOOP_H */
#endif

View file

@ -1,23 +1,9 @@
#ifndef _SWAYBAR_IPC_H #ifndef _SWAYBAR_IPC_H
#define _SWAYBAR_IPC_H #define _SWAYBAR_IPC_H
#include "swaybar/bar.h"
#include "bar.h" void ipc_bar_init(struct swaybar *bar, const char *bar_id);
bool handle_ipc_event(struct swaybar *bar);
/**
* Initialize ipc connection to sway and get sway state, outputs, bar_config.
*/
void ipc_bar_init(struct bar *bar, const char *bar_id);
/**
* Handle ipc event from sway.
*/
bool handle_ipc_event(struct bar *bar);
/**
* Send workspace command to sway
*/
void ipc_send_workspace_command(const char *workspace_name); void ipc_send_workspace_command(const char *workspace_name);
#endif /* _SWAYBAR_IPC_H */ #endif

View file

@ -1,22 +1,10 @@
#ifndef _SWAYBAR_RENDER_H #ifndef _SWAYBAR_RENDER_H
#define _SWAYBAR_RENDER_H #define _SWAYBAR_RENDER_H
#include "config.h" struct swaybar;
#include "bar.h" struct swaybar_output;
struct swaybar_config;
/** void render_frame(struct swaybar *bar, struct swaybar_output *output);
* Render swaybar.
*/
void render(struct output *output, struct config *config, struct status_line *line);
/** #endif
* Set window height and modify internal spacing accordingly.
*/
void set_window_height(struct window *window, int height);
/**
* Compute the size of a workspace name
*/
void workspace_button_size(struct window *window, const char *workspace_name, int *width, int *height);
#endif /* _SWAYBAR_RENDER_H */

View file

@ -35,6 +35,7 @@ pixman = dependency('pixman-1')
libcap = dependency('libcap') libcap = dependency('libcap')
libinput = dependency('libinput') libinput = dependency('libinput')
math = cc.find_library('m') math = cc.find_library('m')
rt = cc.find_library('rt')
git = find_program('git', required: false) git = find_program('git', required: false)
a2x = find_program('a2x', required: false) a2x = find_program('a2x', required: false)
@ -99,8 +100,10 @@ subdir('protocols')
subdir('common') subdir('common')
subdir('sway') subdir('sway')
subdir('swaymsg') subdir('swaymsg')
subdir('client') subdir('client')
subdir('swaybg') subdir('swaybg')
subdir('swaybar')
config = configuration_data() config = configuration_data()
config.set('sysconfdir', join_paths(prefix, sysconfdir)) config.set('sysconfdir', join_paths(prefix, sysconfdir))

View file

@ -1,390 +1,146 @@
#define _XOPEN_SOURCE 500 #define _XOPEN_SOURCE 500
#include <stdlib.h> #include <assert.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h> #include <errno.h>
#include <sys/wait.h> #include <fcntl.h>
#include <signal.h>
#include <poll.h> #include <poll.h>
#ifdef __FreeBSD__ #include <signal.h>
#include <dev/evdev/input-event-codes.h> #include <stdlib.h>
#else #include <string.h>
#include <linux/input-event-codes.h> #include <sys/wait.h>
#endif #include <unistd.h>
#ifdef ENABLE_TRAY #include <wayland-client.h>
#include <dbus/dbus.h> #include <wlr/util/log.h>
#include "swaybar/tray/sni_watcher.h"
#include "swaybar/tray/tray.h"
#include "swaybar/tray/sni.h"
#endif
#include "swaybar/ipc.h"
#include "swaybar/render.h" #include "swaybar/render.h"
#include "swaybar/config.h" #include "swaybar/config.h"
#include "swaybar/status_line.h"
#include "swaybar/event_loop.h" #include "swaybar/event_loop.h"
#include "swaybar/bar.h" #include "swaybar/bar.h"
#include "ipc-client.h"
#include "list.h" #include "list.h"
#include "log.h" #include "pango.h"
#include "pool-buffer.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
static void bar_init(struct bar *bar) { static void bar_init(struct swaybar *bar) {
bar->config = init_config(); bar->config = init_config();
bar->status = init_status_line(); wl_list_init(&bar->outputs);
bar->outputs = create_list();
} }
static void spawn_status_cmd_proc(struct bar *bar) { struct swaybar_output *new_output(const char *name) {
if (bar->config->status_command) { struct swaybar_output *output = malloc(sizeof(struct swaybar_output));
int pipe_read_fd[2];
int pipe_write_fd[2];
if (pipe(pipe_read_fd) != 0) {
sway_log(L_ERROR, "Unable to create pipes for status_command fork");
return;
}
if (pipe(pipe_write_fd) != 0) {
sway_log(L_ERROR, "Unable to create pipe for status_command fork (write)");
close(pipe_read_fd[0]);
close(pipe_read_fd[1]);
return;
}
bar->status_command_pid = fork();
if (bar->status_command_pid == 0) {
close(pipe_read_fd[0]);
dup2(pipe_read_fd[1], STDOUT_FILENO);
close(pipe_read_fd[1]);
dup2(pipe_write_fd[0], STDIN_FILENO);
close(pipe_write_fd[0]);
close(pipe_write_fd[1]);
char *const cmd[] = {
"sh",
"-c",
bar->config->status_command,
NULL,
};
execvp(cmd[0], cmd);
return;
}
close(pipe_read_fd[1]);
bar->status_read_fd = pipe_read_fd[0];
fcntl(bar->status_read_fd, F_SETFL, O_NONBLOCK);
close(pipe_write_fd[0]);
bar->status_write_fd = pipe_write_fd[1];
fcntl(bar->status_write_fd, F_SETFL, O_NONBLOCK);
}
}
struct output *new_output(const char *name) {
struct output *output = malloc(sizeof(struct output));
output->name = strdup(name); output->name = strdup(name);
output->window = NULL;
output->registry = NULL;
output->workspaces = create_list();
#ifdef ENABLE_TRAY
output->items = create_list();
#endif
return output; return output;
} }
static void mouse_button_notify(struct window *window, int x, int y, static void layer_surface_configure(void *data,
uint32_t button, uint32_t state_w) { struct zwlr_layer_surface_v1 *surface,
sway_log(L_DEBUG, "Mouse button %d clicked at %d %d %d", button, x, y, state_w); uint32_t serial, uint32_t width, uint32_t height) {
if (!state_w) { struct swaybar_output *output = data;
return; output->width = width;
} output->height = height;
zwlr_layer_surface_v1_ack_configure(surface, serial);
struct output *clicked_output = NULL; render_frame(output->bar, output);
for (int i = 0; i < swaybar.outputs->length; i++) {
struct output *output = swaybar.outputs->items[i];
if (window == output->window) {
clicked_output = output;
break;
}
}
if (!sway_assert(clicked_output != NULL, "Got pointer event for non-existing output")) {
return;
}
double button_x = 0.5;
for (int i = 0; i < clicked_output->workspaces->length; i++) {
struct workspace *workspace = clicked_output->workspaces->items[i];
int button_width, button_height;
workspace_button_size(window, workspace->name, &button_width, &button_height);
button_x += button_width;
if (x <= button_x) {
ipc_send_workspace_command(workspace->name);
break;
}
}
switch (button) {
case BTN_LEFT:
status_line_mouse_event(&swaybar, x, y, 1);
break;
case BTN_MIDDLE:
status_line_mouse_event(&swaybar, x, y, 2);
break;
case BTN_RIGHT:
status_line_mouse_event(&swaybar, x, y, 3);
break;
}
#ifdef ENABLE_TRAY
tray_mouse_event(clicked_output, x, y, button, state_w);
#endif
} }
static void mouse_scroll_notify(struct window *window, enum scroll_direction direction) { static void layer_surface_closed(void *_output,
sway_log(L_DEBUG, "Mouse wheel scrolled %s", direction == SCROLL_UP ? "up" : "down"); struct zwlr_layer_surface_v1 *surface) {
// TODO: Deal with hotplugging
// If there are status blocks and click_events are enabled struct swaybar_output *output = output;
// check if the position is within the status area and if so zwlr_layer_surface_v1_destroy(output->layer_surface);
// tell the status line to output the event and skip workspace wl_surface_destroy(output->surface);
// switching below.
int num_blocks = swaybar.status->block_line->length;
if (swaybar.status->click_events && num_blocks > 0) {
struct status_block *first_block = swaybar.status->block_line->items[0];
int x = window->pointer_input.last_x;
int y = window->pointer_input.last_y;
if (x > first_block->x) {
if (direction == SCROLL_UP) {
status_line_mouse_event(&swaybar, x, y, 4);
} else {
status_line_mouse_event(&swaybar, x, y, 5);
}
return;
}
}
if (!swaybar.config->wrap_scroll) {
// Find output this window lives on
int i;
struct output *output = NULL;
for (i = 0; i < swaybar.outputs->length; ++i) {
output = swaybar.outputs->items[i];
if (output->window == window) {
break;
}
}
if (!sway_assert(i != swaybar.outputs->length, "Unknown window in scroll event")) {
return;
}
int focused = -1;
for (i = 0; i < output->workspaces->length; ++i) {
struct workspace *ws = output->workspaces->items[i];
if (ws->focused) {
focused = i;
break;
}
}
if (!sway_assert(focused != -1, "Scroll wheel event received on inactive output")) {
return;
}
if ((focused == 0 && direction == SCROLL_UP) ||
(focused == output->workspaces->length - 1 && direction == SCROLL_DOWN)) {
// Do not wrap
return;
}
}
const char *workspace_name = direction == SCROLL_UP ? "prev_on_output" : "next_on_output";
ipc_send_workspace_command(workspace_name);
} }
void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id) { struct zwlr_layer_surface_v1_listener layer_surface_listener = {
/* initialize bar with default values */ .configure = layer_surface_configure,
.closed = layer_surface_closed,
};
static void handle_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version) {
struct swaybar *bar = data;
if (strcmp(interface, wl_compositor_interface.name) == 0) {
bar->compositor = wl_registry_bind(registry, name,
&wl_compositor_interface, 1);
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
bar->shm = wl_registry_bind(registry, name,
&wl_shm_interface, 1);
} else if (strcmp(interface, wl_output_interface.name) == 0) {
static int idx = 0;
struct swaybar_output *output =
calloc(1, sizeof(struct swaybar_output));
output->bar = bar;
output->output = wl_registry_bind(registry, name,
&wl_output_interface, 1);
output->idx = idx++;
wl_list_insert(&bar->outputs, &output->link);
} else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
bar->layer_shell = wl_registry_bind(
registry, name, &zwlr_layer_shell_v1_interface, 1);
}
}
static void handle_global_remove(void *data, struct wl_registry *registry,
uint32_t name) {
// who cares
}
static const struct wl_registry_listener registry_listener = {
.global = handle_global,
.global_remove = handle_global_remove,
};
void bar_setup(struct swaybar *bar,
const char *socket_path, const char *bar_id) {
bar_init(bar); bar_init(bar);
/* Initialize event loop lists */
init_event_loop(); init_event_loop();
/* connect to sway ipc */ assert(bar->display = wl_display_connect(NULL));
bar->ipc_socketfd = ipc_open_socket(socket_path);
bar->ipc_event_socketfd = ipc_open_socket(socket_path);
ipc_bar_init(bar, bar_id); struct wl_registry *registry = wl_display_get_registry(bar->display);
wl_registry_add_listener(registry, &registry_listener, bar);
wl_display_roundtrip(bar->display);
assert(bar->compositor && bar->layer_shell && bar->shm);
int i; // TODO: we might not necessarily be meant to do all of the outputs
for (i = 0; i < bar->outputs->length; ++i) { struct swaybar_output *output;
struct output *bar_output = bar->outputs->items[i]; wl_list_for_each(output, &bar->outputs, link) {
assert(output->surface = wl_compositor_create_surface(bar->compositor));
bar_output->registry = registry_poll(); output->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
bar->layer_shell, output->surface, output->output,
if (!bar_output->registry->desktop_shell) { ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, "panel");
sway_abort("swaybar requires the compositor to support the desktop-shell extension."); assert(output->layer_surface);
} zwlr_layer_surface_v1_add_listener(output->layer_surface,
&layer_surface_listener, output);
struct output_state *output = bar_output->registry->outputs->items[bar_output->idx]; zwlr_layer_surface_v1_set_anchor(output->layer_surface,
bar_output->window = window_setup(bar_output->registry,
output->width / output->scale, 30, output->scale, false);
if (!bar_output->window) {
sway_abort("Failed to create window.");
}
desktop_shell_set_panel(bar_output->registry->desktop_shell,
output->output, bar_output->window->surface);
desktop_shell_set_panel_position(bar_output->registry->desktop_shell,
bar->config->position); bar->config->position);
render_frame(bar, output);
window_make_shell(bar_output->window);
/* set font */
bar_output->window->font = bar->config->font;
/* set mouse event callbacks */
bar_output->window->pointer_input.notify_button = mouse_button_notify;
bar_output->window->pointer_input.notify_scroll = mouse_scroll_notify;
/* set window height */
set_window_height(bar_output->window, bar->config->height);
}
/* spawn status command */
spawn_status_cmd_proc(bar);
#ifdef ENABLE_TRAY
init_tray(bar);
#endif
}
bool dirty = true;
static void respond_ipc(int fd, short mask, void *_bar) {
struct bar *bar = (struct bar *)_bar;
sway_log(L_DEBUG, "Got IPC event.");
dirty = handle_ipc_event(bar);
}
static void respond_command(int fd, short mask, void *_bar) {
struct bar *bar = (struct bar *)_bar;
dirty = handle_status_line(bar);
}
static void respond_output(int fd, short mask, void *_output) {
struct output *output = (struct output *)_output;
if (wl_display_dispatch(output->registry->display) == -1) {
sway_log(L_ERROR, "failed to dispatch wl: %d", errno);
} }
} }
void bar_run(struct bar *bar) { static void display_in(int fd, short mask, void *_bar) {
add_event(bar->ipc_event_socketfd, POLLIN, respond_ipc, bar); struct swaybar *bar = (struct swaybar *)_bar;
add_event(bar->status_read_fd, POLLIN, respond_command, bar); if (wl_display_dispatch(bar->display) == -1) {
wlr_log(L_ERROR, "failed to dispatch wl: %d", errno);
int i;
for (i = 0; i < bar->outputs->length; ++i) {
struct output *output = bar->outputs->items[i];
add_event(wl_display_get_fd(output->registry->display),
POLLIN, respond_output, output);
} }
}
void bar_run(struct swaybar *bar) {
add_event(wl_display_get_fd(bar->display), POLLIN, display_in, bar);
while (1) { while (1) {
if (dirty) {
int i;
for (i = 0; i < bar->outputs->length; ++i) {
struct output *output = bar->outputs->items[i];
if (window_prerender(output->window) && output->window->cairo) {
render(output, bar->config, bar->status);
window_render(output->window);
wl_display_flush(output->registry->display);
}
}
}
dirty = false;
event_loop_poll(); event_loop_poll();
#ifdef ENABLE_TRAY
dispatch_dbus();
#endif
} }
} }
void free_workspaces(list_t *workspaces) { static void free_outputs(struct wl_list *list) {
int i; struct swaybar_output *output, *tmp;
for (i = 0; i < workspaces->length; ++i) { wl_list_for_each_safe(output, tmp, list, link) {
struct workspace *ws = workspaces->items[i]; wl_list_remove(&output->link);
free(ws->name); free(output->name);
free(ws); free(output);
}
list_free(workspaces);
}
static void free_output(struct output *output) {
window_teardown(output->window);
if (output->registry) {
registry_teardown(output->registry);
}
free(output->name);
if (output->workspaces) {
free_workspaces(output->workspaces);
}
free(output);
}
static void free_outputs(list_t *outputs) {
int i;
for (i = 0; i < outputs->length; ++i) {
free_output(outputs->items[i]);
}
list_free(outputs);
}
static void terminate_status_command(pid_t pid) {
if (pid) {
// terminate status_command process
int ret = kill(pid, SIGTERM);
if (ret != 0) {
sway_log(L_ERROR, "Unable to terminate status_command [pid: %d]", pid);
} else {
int status;
waitpid(pid, &status, 0);
}
} }
} }
void bar_teardown(struct bar *bar) { void bar_teardown(struct swaybar *bar) {
free_outputs(&bar->outputs);
if (bar->config) { if (bar->config) {
free_config(bar->config); free_config(bar->config);
} }
if (bar->outputs) {
free_outputs(bar->outputs);
}
if (bar->status) {
free_status_line(bar->status);
}
/* close sockets/pipes */
if (bar->status_read_fd) {
close(bar->status_read_fd);
}
if (bar->status_write_fd) {
close(bar->status_write_fd);
}
if (bar->ipc_socketfd) {
close(bar->ipc_socketfd);
}
if (bar->ipc_event_socketfd) {
close(bar->ipc_event_socketfd);
}
/* terminate status command process */
terminate_status_command(bar->status_command_pid);
} }

View file

@ -1,21 +1,24 @@
#define _XOPEN_SOURCE 500 #define _XOPEN_SOURCE 500
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "wayland-desktop-shell-client-protocol.h"
#include "log.h"
#include "swaybar/config.h" #include "swaybar/config.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
uint32_t parse_position(const char *position) { uint32_t parse_position(const char *position) {
uint32_t horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
uint32_t vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
if (strcmp("top", position) == 0) { if (strcmp("top", position) == 0) {
return DESKTOP_SHELL_PANEL_POSITION_TOP; return ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | horiz;
} else if (strcmp("bottom", position) == 0) { } else if (strcmp("bottom", position) == 0) {
return DESKTOP_SHELL_PANEL_POSITION_BOTTOM; return ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | horiz;
} else if (strcmp("left", position) == 0) { } else if (strcmp("left", position) == 0) {
return DESKTOP_SHELL_PANEL_POSITION_LEFT; return ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | vert;
} else if (strcmp("right", position) == 0) { } else if (strcmp("right", position) == 0) {
return DESKTOP_SHELL_PANEL_POSITION_RIGHT; return ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | vert;
} else { } else {
return DESKTOP_SHELL_PANEL_POSITION_BOTTOM; return ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | horiz;
} }
} }
@ -30,11 +33,11 @@ char *parse_font(const char *font) {
return new_font; return new_font;
} }
struct config *init_config() { struct swaybar_config *init_config() {
struct config *config = calloc(1, sizeof(struct config)); struct swaybar_config *config = calloc(1, sizeof(struct swaybar_config));
config->status_command = NULL; config->status_command = NULL;
config->pango_markup = false; config->pango_markup = false;
config->position = DESKTOP_SHELL_PANEL_POSITION_BOTTOM; config->position = parse_position("bottom");
config->font = strdup("monospace 10"); config->font = strdup("monospace 10");
config->mode = NULL; config->mode = NULL;
config->sep_symbol = NULL; config->sep_symbol = NULL;
@ -48,18 +51,6 @@ struct config *init_config() {
/* height */ /* height */
config->height = 0; config->height = 0;
#ifdef ENABLE_TRAY
config->tray_output = NULL;
config->icon_theme = NULL;
config->tray_padding = 2;
/**
* These constants are used by wayland and are defined in
* linux/input-event-codes.h
*/
config->activate_button = 0x110; /* BTN_LEFT */
config->context_button = 0x111; /* BTN_RIGHT */
#endif
/* colors */ /* colors */
config->colors.background = 0x000000FF; config->colors.background = 0x000000FF;
config->colors.statusline = 0xFFFFFFFF; config->colors.statusline = 0xFFFFFFFF;
@ -88,7 +79,7 @@ struct config *init_config() {
return config; return config;
} }
void free_config(struct config *config) { void free_config(struct swaybar_config *config) {
free(config->status_command); free(config->status_command);
free(config->font); free(config->font);
free(config->mode); free(config->mode);

View file

@ -4,19 +4,18 @@
#include <string.h> #include <string.h>
#include <strings.h> #include <strings.h>
#include <poll.h> #include <poll.h>
#include "swaybar/bar.h" #include <time.h>
#include "swaybar/event_loop.h" #include "swaybar/event_loop.h"
#include "list.h" #include "list.h"
#include "log.h"
struct event_item { struct event_item {
void(*cb)(int fd, short mask, void *data); void (*cb)(int fd, short mask, void *data);
void *data; void *data;
}; };
struct timer_item { struct timer_item {
timer_t timer; timer_t timer;
void(*cb)(timer_t timer, void *data); void (*cb)(timer_t timer, void *data);
void *data; void *data;
}; };
@ -138,7 +137,8 @@ void event_loop_poll() {
void init_event_loop() { void init_event_loop() {
event_loop.fds.length = 0; event_loop.fds.length = 0;
event_loop.fds.capacity = 10; event_loop.fds.capacity = 10;
event_loop.fds.items = malloc(event_loop.fds.capacity * sizeof(struct pollfd)); event_loop.fds.items = malloc(
event_loop.fds.capacity * sizeof(struct pollfd));
event_loop.items = create_list(); event_loop.items = create_list();
event_loop.timers = create_list(); event_loop.timers = create_list();
} }

View file

@ -1,410 +0,0 @@
#define _XOPEN_SOURCE 500
#include <string.h>
#include <strings.h>
#include <json-c/json.h>
#include "swaybar/config.h"
#include "swaybar/ipc.h"
#include "ipc-client.h"
#include "list.h"
#include "log.h"
void ipc_send_workspace_command(const char *workspace_name) {
uint32_t size = strlen("workspace \"\"") + strlen(workspace_name) + 1;
char command[size];
sprintf(command, "workspace \"%s\"", workspace_name);
ipc_single_command(swaybar.ipc_socketfd, IPC_COMMAND, command, &size);
}
static void ipc_parse_config(struct config *config, const char *payload) {
json_object *bar_config = json_tokener_parse(payload);
json_object *markup, *mode, *hidden_bar, *position, *status_command;
json_object *font, *bar_height, *wrap_scroll, *workspace_buttons, *strip_workspace_numbers;
json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol, *outputs;
#ifdef ENABLE_TRAY
json_object *tray_output, *icon_theme, *tray_padding, *activate_button, *context_button;
json_object *secondary_button;
json_object_object_get_ex(bar_config, "tray_output", &tray_output);
json_object_object_get_ex(bar_config, "icon_theme", &icon_theme);
json_object_object_get_ex(bar_config, "tray_padding", &tray_padding);
json_object_object_get_ex(bar_config, "activate_button", &activate_button);
json_object_object_get_ex(bar_config, "context_button", &context_button);
json_object_object_get_ex(bar_config, "secondary_button", &secondary_button);
#endif
json_object_object_get_ex(bar_config, "mode", &mode);
json_object_object_get_ex(bar_config, "hidden_bar", &hidden_bar);
json_object_object_get_ex(bar_config, "position", &position);
json_object_object_get_ex(bar_config, "status_command", &status_command);
json_object_object_get_ex(bar_config, "font", &font);
json_object_object_get_ex(bar_config, "bar_height", &bar_height);
json_object_object_get_ex(bar_config, "wrap_scroll", &wrap_scroll);
json_object_object_get_ex(bar_config, "workspace_buttons", &workspace_buttons);
json_object_object_get_ex(bar_config, "strip_workspace_numbers", &strip_workspace_numbers);
json_object_object_get_ex(bar_config, "binding_mode_indicator", &binding_mode_indicator);
json_object_object_get_ex(bar_config, "verbose", &verbose);
json_object_object_get_ex(bar_config, "separator_symbol", &sep_symbol);
json_object_object_get_ex(bar_config, "colors", &colors);
json_object_object_get_ex(bar_config, "outputs", &outputs);
json_object_object_get_ex(bar_config, "pango_markup", &markup);
if (status_command) {
free(config->status_command);
config->status_command = strdup(json_object_get_string(status_command));
}
if (position) {
config->position = parse_position(json_object_get_string(position));
}
if (font) {
free(config->font);
config->font = parse_font(json_object_get_string(font));
}
if (sep_symbol) {
free(config->sep_symbol);
config->sep_symbol = strdup(json_object_get_string(sep_symbol));
}
if (strip_workspace_numbers) {
config->strip_workspace_numbers = json_object_get_boolean(strip_workspace_numbers);
}
if (binding_mode_indicator) {
config->binding_mode_indicator = json_object_get_boolean(binding_mode_indicator);
}
if (wrap_scroll) {
config->wrap_scroll = json_object_get_boolean(wrap_scroll);
}
if (workspace_buttons) {
config->workspace_buttons = json_object_get_boolean(workspace_buttons);
}
if (bar_height) {
config->height = json_object_get_int(bar_height);
}
if (markup) {
config->pango_markup = json_object_get_boolean(markup);
}
#ifdef ENABLE_TRAY
if (tray_output) {
free(config->tray_output);
config->tray_output = strdup(json_object_get_string(tray_output));
}
if (icon_theme) {
free(config->icon_theme);
config->icon_theme = strdup(json_object_get_string(icon_theme));
}
if (tray_padding) {
config->tray_padding = json_object_get_int(tray_padding);
}
if (activate_button) {
config->activate_button = json_object_get_int(activate_button);
}
if (context_button) {
config->context_button = json_object_get_int(context_button);
}
if (secondary_button) {
config->secondary_button = json_object_get_int(secondary_button);
}
#endif
// free previous outputs list
int i;
for (i = 0; i < config->outputs->length; ++i) {
free(config->outputs->items[i]);
}
list_free(config->outputs);
config->outputs = create_list();
if (outputs) {
int length = json_object_array_length(outputs);
json_object *output;
const char *output_str;
for (i = 0; i < length; ++i) {
output = json_object_array_get_idx(outputs, i);
output_str = json_object_get_string(output);
if (strcmp("*", output_str) == 0) {
config->all_outputs = true;
break;
}
list_add(config->outputs, strdup(output_str));
}
} else {
config->all_outputs = true;
}
if (colors) {
json_object *background, *statusline, *separator;
json_object *focused_background, *focused_statusline, *focused_separator;
json_object *focused_workspace_border, *focused_workspace_bg, *focused_workspace_text;
json_object *inactive_workspace_border, *inactive_workspace_bg, *inactive_workspace_text;
json_object *active_workspace_border, *active_workspace_bg, *active_workspace_text;
json_object *urgent_workspace_border, *urgent_workspace_bg, *urgent_workspace_text;
json_object *binding_mode_border, *binding_mode_bg, *binding_mode_text;
json_object_object_get_ex(colors, "background", &background);
json_object_object_get_ex(colors, "statusline", &statusline);
json_object_object_get_ex(colors, "separator", &separator);
json_object_object_get_ex(colors, "focused_background", &focused_background);
json_object_object_get_ex(colors, "focused_statusline", &focused_statusline);
json_object_object_get_ex(colors, "focused_separator", &focused_separator);
json_object_object_get_ex(colors, "focused_workspace_border", &focused_workspace_border);
json_object_object_get_ex(colors, "focused_workspace_bg", &focused_workspace_bg);
json_object_object_get_ex(colors, "focused_workspace_text", &focused_workspace_text);
json_object_object_get_ex(colors, "active_workspace_border", &active_workspace_border);
json_object_object_get_ex(colors, "active_workspace_bg", &active_workspace_bg);
json_object_object_get_ex(colors, "active_workspace_text", &active_workspace_text);
json_object_object_get_ex(colors, "inactive_workspace_border", &inactive_workspace_border);
json_object_object_get_ex(colors, "inactive_workspace_bg", &inactive_workspace_bg);
json_object_object_get_ex(colors, "inactive_workspace_text", &inactive_workspace_text);
json_object_object_get_ex(colors, "urgent_workspace_border", &urgent_workspace_border);
json_object_object_get_ex(colors, "urgent_workspace_bg", &urgent_workspace_bg);
json_object_object_get_ex(colors, "urgent_workspace_text", &urgent_workspace_text);
json_object_object_get_ex(colors, "binding_mode_border", &binding_mode_border);
json_object_object_get_ex(colors, "binding_mode_bg", &binding_mode_bg);
json_object_object_get_ex(colors, "binding_mode_text", &binding_mode_text);
if (background) {
config->colors.background = parse_color(json_object_get_string(background));
}
if (statusline) {
config->colors.statusline = parse_color(json_object_get_string(statusline));
}
if (separator) {
config->colors.separator = parse_color(json_object_get_string(separator));
}
if (focused_background) {
config->colors.focused_background = parse_color(json_object_get_string(focused_background));
}
if (focused_statusline) {
config->colors.focused_statusline = parse_color(json_object_get_string(focused_statusline));
}
if (focused_separator) {
config->colors.focused_separator = parse_color(json_object_get_string(focused_separator));
}
if (focused_workspace_border) {
config->colors.focused_workspace.border = parse_color(json_object_get_string(focused_workspace_border));
}
if (focused_workspace_bg) {
config->colors.focused_workspace.background = parse_color(json_object_get_string(focused_workspace_bg));
}
if (focused_workspace_text) {
config->colors.focused_workspace.text = parse_color(json_object_get_string(focused_workspace_text));
}
if (active_workspace_border) {
config->colors.active_workspace.border = parse_color(json_object_get_string(active_workspace_border));
}
if (active_workspace_bg) {
config->colors.active_workspace.background = parse_color(json_object_get_string(active_workspace_bg));
}
if (active_workspace_text) {
config->colors.active_workspace.text = parse_color(json_object_get_string(active_workspace_text));
}
if (inactive_workspace_border) {
config->colors.inactive_workspace.border = parse_color(json_object_get_string(inactive_workspace_border));
}
if (inactive_workspace_bg) {
config->colors.inactive_workspace.background = parse_color(json_object_get_string(inactive_workspace_bg));
}
if (inactive_workspace_text) {
config->colors.inactive_workspace.text = parse_color(json_object_get_string(inactive_workspace_text));
}
if (binding_mode_border) {
config->colors.binding_mode.border = parse_color(json_object_get_string(binding_mode_border));
}
if (binding_mode_bg) {
config->colors.binding_mode.background = parse_color(json_object_get_string(binding_mode_bg));
}
if (binding_mode_text) {
config->colors.binding_mode.text = parse_color(json_object_get_string(binding_mode_text));
}
}
json_object_put(bar_config);
}
static void ipc_update_workspaces(struct bar *bar) {
int i;
for (i = 0; i < bar->outputs->length; ++i) {
struct output *output = bar->outputs->items[i];
if (output->workspaces) {
free_workspaces(output->workspaces);
}
output->workspaces = create_list();
}
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;
}
int length = json_object_array_length(results);
json_object *ws_json;
json_object *num, *name, *visible, *focused, *out, *urgent;
for (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);
int j;
for (j = 0; j < bar->outputs->length; ++j) {
struct output *output = bar->outputs->items[j];
if (strcmp(json_object_get_string(out), output->name) == 0) {
struct workspace *ws = malloc(sizeof(struct workspace));
ws->num = json_object_get_int(num);
ws->name = strdup(json_object_get_string(name));
ws->visible = json_object_get_boolean(visible);
ws->focused = json_object_get_boolean(focused);
if (ws->focused) {
if (bar->focused_output) {
bar->focused_output->focused = false;
}
bar->focused_output = output;
output->focused = true;
}
ws->urgent = json_object_get_boolean(urgent);
list_add(output->workspaces, ws);
}
}
}
json_object_put(results);
free(res);
}
void ipc_bar_init(struct bar *bar, const char *bar_id) {
// Get bar config
uint32_t len = strlen(bar_id);
char *res = ipc_single_command(bar->ipc_socketfd, IPC_GET_BAR_CONFIG, bar_id, &len);
ipc_parse_config(bar->config, res);
free(res);
// Get outputs
len = 0;
res = ipc_single_command(bar->ipc_socketfd, IPC_GET_OUTPUTS, NULL, &len);
json_object *outputs = json_tokener_parse(res);
int i;
int length = json_object_array_length(outputs);
json_object *output, *output_name, *output_active;
const char *name;
bool active;
for (i = 0; i < length; ++i) {
output = json_object_array_get_idx(outputs, i);
json_object_object_get_ex(output, "name", &output_name);
json_object_object_get_ex(output, "active", &output_active);
name = json_object_get_string(output_name);
active = json_object_get_boolean(output_active);
if (!active) {
continue;
}
bool use_output = false;
if (bar->config->all_outputs) {
use_output = true;
} else {
int j = 0;
for (j = 0; j < bar->config->outputs->length; ++j) {
const char *conf_name = bar->config->outputs->items[j];
if (strcasecmp(name, conf_name) == 0) {
use_output = true;
break;
}
}
}
if (!use_output) {
continue;
}
// add bar to the output
struct output *bar_output = new_output(name);
bar_output->idx = i;
list_add(bar->outputs, bar_output);
}
free(res);
json_object_put(outputs);
const char *subscribe_json = "[ \"workspace\", \"mode\" ]";
len = strlen(subscribe_json);
res = ipc_single_command(bar->ipc_event_socketfd, IPC_SUBSCRIBE, subscribe_json, &len);
free(res);
ipc_update_workspaces(bar);
}
bool handle_ipc_event(struct bar *bar) {
struct ipc_response *resp = ipc_recv_response(bar->ipc_event_socketfd);
if (!resp) {
return false;
}
switch (resp->type) {
case IPC_EVENT_WORKSPACE:
ipc_update_workspaces(bar);
break;
case IPC_EVENT_MODE: {
json_object *result = json_tokener_parse(resp->payload);
if (!result) {
free_ipc_response(resp);
sway_log(L_ERROR, "failed to parse payload as json");
return false;
}
json_object *json_change;
if (json_object_object_get_ex(result, "change", &json_change)) {
const char *change = json_object_get_string(json_change);
free(bar->config->mode);
if (strcmp(change, "default") == 0) {
bar->config->mode = NULL;
} else {
bar->config->mode = strdup(change);
}
} else {
sway_log(L_ERROR, "failed to parse response");
}
json_object_put(result);
break;
}
default:
free_ipc_response(resp);
return false;
}
free_ipc_response(resp);
return true;
}

View file

@ -4,23 +4,22 @@
#include <string.h> #include <string.h>
#include <stdbool.h> #include <stdbool.h>
#include <getopt.h> #include <getopt.h>
#include <wlr/util/log.h>
#include "swaybar/bar.h" #include "swaybar/bar.h"
#include "ipc-client.h" #include "ipc-client.h"
#include "log.h"
/* global bar state */ static struct swaybar swaybar;
struct bar swaybar;
void sway_terminate(int exit_code) {
bar_teardown(&swaybar);
exit(exit_code);
}
void sig_handler(int signal) { void sig_handler(int signal) {
bar_teardown(&swaybar); bar_teardown(&swaybar);
exit(0); exit(0);
} }
void sway_terminate(int code) {
bar_teardown(&swaybar);
exit(code);
}
int main(int argc, char **argv) { int main(int argc, char **argv) {
char *socket_path = NULL; char *socket_path = NULL;
char *bar_id = NULL; char *bar_id = NULL;
@ -75,20 +74,23 @@ int main(int argc, char **argv) {
} }
} }
if (!bar_id) { if (debug) {
sway_abort("No bar_id passed. Provide --bar_id or let sway start swaybar"); wlr_log_init(L_DEBUG, NULL);
} else {
wlr_log_init(L_ERROR, NULL);
} }
if (debug) { if (!bar_id) {
init_log(L_DEBUG); wlr_log(L_ERROR, "No bar_id passed. "
} else { "Provide --bar_id or let sway start swaybar");
init_log(L_ERROR); return 1;
} }
if (!socket_path) { if (!socket_path) {
socket_path = get_socketpath(); socket_path = get_socketpath();
if (!socket_path) { if (!socket_path) {
sway_abort("Unable to retrieve socket path"); wlr_log(L_ERROR, "Unable to retrieve socket path");
return 1;
} }
} }
@ -100,9 +102,6 @@ int main(int argc, char **argv) {
free(bar_id); free(bar_id);
bar_run(&swaybar); bar_run(&swaybar);
// gracefully shutdown swaybar and status_command
bar_teardown(&swaybar); bar_teardown(&swaybar);
return 0; return 0;
} }

25
swaybar/meson.build Normal file
View file

@ -0,0 +1,25 @@
executable(
'swaybar',
[
'bar.c',
'config.c',
'event_loop.c',
'main.c',
'render.c',
],
include_directories: [sway_inc],
dependencies: [
cairo,
client_protos,
gdk_pixbuf,
jsonc,
math,
pango,
pangocairo,
rt,
wayland_client,
wlroots,
],
link_with: [lib_sway_common, lib_sway_client],
install: true
)

View file

@ -1,367 +1,63 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
#include <wlr/util/log.h>
#include "client/cairo.h" #include "cairo.h"
#include "client/pango.h" #include "pango.h"
#include "client/window.h" #include "pool-buffer.h"
#include "swaybar/bar.h"
#include "swaybar/config.h" #include "swaybar/config.h"
#include "swaybar/status_line.h"
#include "swaybar/render.h" #include "swaybar/render.h"
#ifdef ENABLE_TRAY #include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include "swaybar/tray/tray.h"
#include "swaybar/tray/sni.h"
#endif
#include "log.h"
static uint32_t render_to_cairo(cairo_t *cairo, struct swaybar *bar,
struct swaybar_output *output) {
struct swaybar_config *config = bar->config;
/* internal spacing */
static int margin = 3;
static int ws_horizontal_padding = 5;
static double ws_vertical_padding = 1.5;
static int ws_spacing = 1;
/**
* Renders a sharp line of any width and height.
*
* The line is drawn from (x,y) to (x+width,y+height) where width/height is 0
* if the line has a width/height of one pixel, respectively.
*/
static void render_sharp_line(cairo_t *cairo, uint32_t color, double x, double y, double width, double height) {
cairo_set_source_u32(cairo, color);
if (width > 1 && height > 1) {
cairo_rectangle(cairo, x, y, width, height);
cairo_fill(cairo);
} else {
if (width == 1) {
x += 0.5;
height += y;
width = x;
}
if (height == 1) {
y += 0.5;
width += x;
height = y;
}
cairo_move_to(cairo, x, y);
cairo_set_line_width(cairo, 1.0);
cairo_line_to(cairo, width, height);
cairo_stroke(cairo);
}
}
static void render_block(struct window *window, struct config *config, struct status_block *block, double *x, bool edge, bool is_focused) {
int width, height, sep_width;
get_text_size(window->cairo, window->font, &width, &height,
window->scale, block->markup, "%s", block->full_text);
int textwidth = width;
double block_width = width;
if (width < block->min_width) {
width = block->min_width;
}
*x -= width;
if (block->border != 0 && block->border_left > 0) {
*x -= (block->border_left + margin);
block_width += block->border_left + margin;
}
if (block->border != 0 && block->border_right > 0) {
*x -= (block->border_right + margin);
block_width += block->border_right + margin;
}
// Add separator
if (!edge) {
if (config->sep_symbol) {
get_text_size(window->cairo, window->font, &sep_width, &height,
window->scale, false, "%s", config->sep_symbol);
if (sep_width > block->separator_block_width) {
block->separator_block_width = sep_width + margin * 2;
}
}
*x -= block->separator_block_width;
} else {
*x -= margin;
}
double pos = *x;
block->x = (int)pos;
block->width = (int)block_width;
// render background
if (block->background != 0x0) {
cairo_set_source_u32(window->cairo, block->background);
cairo_rectangle(window->cairo, pos - 0.5, 1, block_width, (window->height * window->scale) - 2);
cairo_fill(window->cairo);
}
// render top border
if (block->border != 0 && block->border_top > 0) {
render_sharp_line(window->cairo, block->border,
pos - 0.5,
1,
block_width,
block->border_top);
}
// render bottom border
if (block->border != 0 && block->border_bottom > 0) {
render_sharp_line(window->cairo, block->border,
pos - 0.5,
(window->height * window->scale) - 1 - block->border_bottom,
block_width,
block->border_bottom);
}
// render left border
if (block->border != 0 && block->border_left > 0) {
render_sharp_line(window->cairo, block->border,
pos - 0.5,
1,
block->border_left,
(window->height * window->scale) - 2);
pos += block->border_left + margin;
}
// render text
double offset = 0;
if (strncmp(block->align, "left", 5) == 0) {
offset = pos;
} else if (strncmp(block->align, "right", 5) == 0) {
offset = pos + width - textwidth;
} else if (strncmp(block->align, "center", 6) == 0) {
offset = pos + (width - textwidth) / 2;
}
cairo_move_to(window->cairo, offset, margin);
cairo_set_source_u32(window->cairo, block->color);
pango_printf(window->cairo, window->font, window->scale,
block->markup, "%s", block->full_text);
pos += width;
// render right border
if (block->border != 0 && block->border_right > 0) {
pos += margin;
render_sharp_line(window->cairo, block->border,
pos - 0.5,
1,
block->border_right,
(window->height * window->scale) - 2);
pos += block->border_right;
}
// render separator
if (!edge && block->separator) {
if (is_focused) {
cairo_set_source_u32(window->cairo, config->colors.focused_separator);
} else {
cairo_set_source_u32(window->cairo, config->colors.separator);
}
if (config->sep_symbol) {
offset = pos + (block->separator_block_width - sep_width) / 2;
cairo_move_to(window->cairo, offset, margin);
pango_printf(window->cairo, window->font, window->scale,
false, "%s", config->sep_symbol);
} else {
cairo_set_line_width(window->cairo, 1);
cairo_move_to(window->cairo, pos + block->separator_block_width/2,
margin);
cairo_line_to(window->cairo, pos + block->separator_block_width/2,
(window->height * window->scale) - margin);
cairo_stroke(window->cairo);
}
}
}
static const char *strip_workspace_name(bool strip_num, const char *ws_name) {
bool strip = false;
int i;
if (strip_num) {
int len = strlen(ws_name);
for (i = 0; i < len; ++i) {
if (!('0' <= ws_name[i] && ws_name[i] <= '9')) {
if (':' == ws_name[i] && i < len-1 && i > 0) {
strip = true;
++i;
}
break;
}
}
}
if (strip) {
return ws_name + i;
}
return ws_name;
}
void workspace_button_size(struct window *window, const char *workspace_name, int *width, int *height) {
const char *stripped_name = strip_workspace_name(swaybar.config->strip_workspace_numbers, workspace_name);
get_text_size(window->cairo, window->font, width, height,
window->scale, true, "%s", stripped_name);
*width += 2 * ws_horizontal_padding;
*height += 2 * ws_vertical_padding;
}
static void render_workspace_button(struct window *window, struct config *config, struct workspace *ws, double *x) {
const char *stripped_name = strip_workspace_name(config->strip_workspace_numbers, ws->name);
struct box_colors box_colors;
if (ws->urgent) {
box_colors = config->colors.urgent_workspace;
} else if (ws->focused) {
box_colors = config->colors.focused_workspace;
} else if (ws->visible) {
box_colors = config->colors.active_workspace;
} else {
box_colors = config->colors.inactive_workspace;
}
int width, height;
workspace_button_size(window, stripped_name, &width, &height);
// background
cairo_set_source_u32(window->cairo, box_colors.background);
cairo_rectangle(window->cairo, *x, 1.5, width - 1, height);
cairo_fill(window->cairo);
// border
cairo_set_source_u32(window->cairo, box_colors.border);
cairo_rectangle(window->cairo, *x, 1.5, width - 1, height);
cairo_stroke(window->cairo);
// text
cairo_set_source_u32(window->cairo, box_colors.text);
cairo_move_to(window->cairo, (int)*x + ws_horizontal_padding, margin);
pango_printf(window->cairo, window->font, window->scale,
true, "%s", stripped_name);
*x += width + ws_spacing;
}
static void render_binding_mode_indicator(struct window *window, struct config *config, double pos) {
int width, height;
get_text_size(window->cairo, window->font, &width, &height,
window->scale, false, "%s", config->mode);
// background
cairo_set_source_u32(window->cairo, config->colors.binding_mode.background);
cairo_rectangle(window->cairo, pos, 1.5, width + ws_horizontal_padding * 2 - 1,
height + ws_vertical_padding * 2);
cairo_fill(window->cairo);
// border
cairo_set_source_u32(window->cairo, config->colors.binding_mode.border);
cairo_rectangle(window->cairo, pos, 1.5, width + ws_horizontal_padding * 2 - 1,
height + ws_vertical_padding * 2);
cairo_stroke(window->cairo);
// text
cairo_set_source_u32(window->cairo, config->colors.binding_mode.text);
cairo_move_to(window->cairo, (int)pos + ws_horizontal_padding, margin);
pango_printf(window->cairo, window->font, window->scale,
false, "%s", config->mode);
}
void render(struct output *output, struct config *config, struct status_line *line) {
int i;
struct window *window = output->window;
cairo_t *cairo = window->cairo;
bool is_focused = output->focused;
// Clear
cairo_save(cairo); cairo_save(cairo);
cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
cairo_paint(cairo); cairo_paint(cairo);
cairo_restore(cairo); cairo_restore(cairo);
cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
if (output->focused) {
// Background
if (is_focused) {
cairo_set_source_u32(cairo, config->colors.focused_background); cairo_set_source_u32(cairo, config->colors.focused_background);
} else { } else {
cairo_set_source_u32(cairo, config->colors.background); cairo_set_source_u32(cairo, config->colors.background);
} }
cairo_paint(cairo); cairo_paint(cairo);
#ifdef ENABLE_TRAY // TODO: use actual height
uint32_t tray_width = tray_render(output, config); return 20;
#else }
const uint32_t tray_width = window->width * window->scale;
#endif
// Command output void render_frame(struct swaybar *bar,
if (is_focused) { struct swaybar_output *output) {
cairo_set_source_u32(cairo, config->colors.focused_statusline); cairo_surface_t *recorder = cairo_recording_surface_create(
CAIRO_CONTENT_COLOR_ALPHA, NULL);
cairo_t *cairo = cairo_create(recorder);
uint32_t height = render_to_cairo(cairo, bar, output);
if (height != output->height) {
// Reconfigure surface
zwlr_layer_surface_v1_set_size(
output->layer_surface, 0, height);
// TODO: this could infinite loop if the compositor assigns us a
// different height than what we asked for
wl_surface_commit(output->surface);
wl_display_roundtrip(bar->display);
} else { } else {
cairo_set_source_u32(cairo, config->colors.statusline); // Replay recording into shm and send it off
} output->current_buffer = get_next_buffer(bar->shm,
output->buffers, output->width, output->height);
int width, height; cairo_t *shm = output->current_buffer->cairo;
cairo_set_source_surface(shm, recorder, 0.0, 0.0);
if (line->protocol == TEXT) { cairo_paint(shm);
get_text_size(window->cairo, window->font, &width, &height, wl_surface_attach(output->surface,
window->scale, config->pango_markup, "%s", line->text_line); output->current_buffer->buffer, 0, 0);
cairo_move_to(cairo, tray_width - margin - width, margin); wl_surface_damage(output->surface, 0, 0, output->width, output->height);
pango_printf(window->cairo, window->font, window->scale, wl_surface_commit(output->surface);
config->pango_markup, "%s", line->text_line); wl_display_roundtrip(bar->display);
} else if (line->protocol == I3BAR && line->block_line) {
double pos = tray_width - 0.5;
bool edge = true;
for (i = line->block_line->length - 1; i >= 0; --i) {
struct status_block *block = line->block_line->items[i];
if (block->full_text && block->full_text[0]) {
render_block(window, config, block, &pos, edge, is_focused);
edge = false;
}
}
}
cairo_set_line_width(cairo, 1.0);
double x = 0.5;
// Workspaces
if (config->workspace_buttons) {
for (i = 0; i < output->workspaces->length; ++i) {
struct workspace *ws = output->workspaces->items[i];
render_workspace_button(window, config, ws, &x);
}
}
// binding mode indicator
if (config->mode && config->binding_mode_indicator) {
render_binding_mode_indicator(window, config, x);
} }
} cairo_surface_destroy(recorder);
cairo_destroy(cairo);
void set_window_height(struct window *window, int height) {
int text_width, text_height;
get_text_size(window->cairo, window->font,
&text_width, &text_height, window->scale, false,
"Test string for measuring purposes");
if (height > 0) {
margin = (height - text_height) / 2;
ws_vertical_padding = margin - 1.5;
}
window->height = (text_height + margin * 2) / window->scale;
} }

View file

@ -1,530 +0,0 @@
#define _XOPEN_SOURCE 700
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <json-c/json.h>
#include "swaybar/config.h"
#include "swaybar/status_line.h"
#include "log.h"
#include "util.h"
#define I3JSON_MAXDEPTH 4
#define I3JSON_UNKNOWN 0
#define I3JSON_ARRAY 1
#define I3JSON_STRING 2
struct {
int bufsize;
char *buffer;
char *line_start;
char *parserpos;
bool escape;
int depth;
int bar[I3JSON_MAXDEPTH+1];
} i3json_state = { 0, NULL, NULL, NULL, false, 0, { I3JSON_UNKNOWN } };
static char line[1024];
static char line_rest[1024];
static char event_buff[1024];
static void free_status_block(void *item) {
if (!item) {
return;
}
struct status_block *sb = (struct status_block*)item;
if (sb->full_text) {
free(sb->full_text);
}
if (sb->short_text) {
free(sb->short_text);
}
if (sb->align) {
free(sb->align);
}
if (sb->name) {
free(sb->name);
}
if (sb->instance) {
free(sb->instance);
}
free(sb);
}
static void parse_json(struct bar *bar, const char *text) {
json_object *results = json_tokener_parse(text);
if (!results) {
sway_log(L_DEBUG, "Failed to parse json");
return;
}
if (json_object_array_length(results) < 1) {
return;
}
if (bar->status->block_line) {
list_foreach(bar->status->block_line, free_status_block);
list_free(bar->status->block_line);
}
bar->status->block_line = create_list();
int i;
for (i = 0; i < json_object_array_length(results); ++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(results, 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 status_block *new = calloc(1, sizeof(struct status_block));
if (full_text) {
new->full_text = strdup(json_object_get_string(full_text));
}
if (short_text) {
new->short_text = strdup(json_object_get_string(short_text));
}
if (color) {
new->color = parse_color(json_object_get_string(color));
} else {
new->color = bar->config->colors.statusline;
}
if (min_width) {
json_type type = json_object_get_type(min_width);
if (type == json_type_int) {
new->min_width = json_object_get_int(min_width);
} else if (type == json_type_string) {
/* the width will be calculated when rendering */
new->min_width = 0;
}
}
if (align) {
new->align = strdup(json_object_get_string(align));
} else {
new->align = strdup("left");
}
if (urgent) {
new->urgent = json_object_get_int(urgent);
}
if (name) {
new->name = strdup(json_object_get_string(name));
}
if (instance) {
new->instance = strdup(json_object_get_string(instance));
}
if (markup) {
new->markup = false;
const char *markup_str = json_object_get_string(markup);
if (strcmp(markup_str, "pango") == 0) {
new->markup = true;
}
}
if (separator) {
new->separator = json_object_get_int(separator);
} else {
new->separator = true; // i3bar spec
}
if (separator_block_width) {
new->separator_block_width = json_object_get_int(separator_block_width);
} else {
new->separator_block_width = 9; // i3bar spec
}
// Airblader features
if (background) {
new->background = parse_color(json_object_get_string(background));
} else {
new->background = 0x0; // transparent
}
if (border) {
new->border = parse_color(json_object_get_string(border));
} else {
new->border = 0x0; // transparent
}
if (border_top) {
new->border_top = json_object_get_int(border_top);
} else {
new->border_top = 1;
}
if (border_bottom) {
new->border_bottom = json_object_get_int(border_bottom);
} else {
new->border_bottom = 1;
}
if (border_left) {
new->border_left = json_object_get_int(border_left);
} else {
new->border_left = 1;
}
if (border_right) {
new->border_right = json_object_get_int(border_right);
} else {
new->border_right = 1;
}
list_add(bar->status->block_line, new);
}
json_object_put(results);
}
// continue parsing from last parserpos
static int i3json_parse(struct bar *bar) {
char *c = i3json_state.parserpos;
int handled = 0;
while (*c) {
if (i3json_state.bar[i3json_state.depth] == I3JSON_STRING) {
if (!i3json_state.escape && *c == '"') {
--i3json_state.depth;
}
i3json_state.escape = !i3json_state.escape && *c == '\\';
} else {
switch (*c) {
case '[':
++i3json_state.depth;
if (i3json_state.depth > I3JSON_MAXDEPTH) {
sway_abort("JSON too deep");
}
i3json_state.bar[i3json_state.depth] = I3JSON_ARRAY;
if (i3json_state.depth == 2) {
i3json_state.line_start = c;
}
break;
case ']':
if (i3json_state.bar[i3json_state.depth] != I3JSON_ARRAY) {
sway_abort("JSON malformed");
}
--i3json_state.depth;
if (i3json_state.depth == 1) {
// c[1] is valid since c[0] != '\0'
char p = c[1];
c[1] = '\0';
parse_json(bar, i3json_state.line_start);
c[1] = p;
++handled;
i3json_state.line_start = c+1;
}
break;
case '"':
++i3json_state.depth;
if (i3json_state.depth > I3JSON_MAXDEPTH) {
sway_abort("JSON too deep");
}
i3json_state.bar[i3json_state.depth] = I3JSON_STRING;
break;
}
}
++c;
}
i3json_state.parserpos = c;
return handled;
}
// Read line from file descriptor, only show the line tail if it is too long.
// In non-blocking mode treat "no more data" as a linebreak.
// If data after a line break has been read, return it in rest.
// If rest is non-empty, then use that as the start of the next line.
static int read_line_tail(int fd, char *buf, int nbyte, char *rest) {
if (fd < 0 || !buf || !nbyte) {
return -1;
}
int l;
char *buffer = malloc(nbyte*2+1);
char *readpos = buffer;
char *lf;
// prepend old data to new line if necessary
if (rest) {
l = strlen(rest);
if (l > nbyte) {
strcpy(buffer, rest + l - nbyte);
readpos += nbyte;
} else if (l) {
strcpy(buffer, rest);
readpos += l;
}
}
// read until a linefeed is found or no more data is available
while ((l = read(fd, readpos, nbyte)) > 0) {
readpos[l] = '\0';
lf = strchr(readpos, '\n');
if (lf) {
// linefeed found, replace with \0
*lf = '\0';
// give data from the end of the line, try to fill the buffer
if (lf-buffer > nbyte) {
strcpy(buf, lf - nbyte + 1);
} else {
strcpy(buf, buffer);
}
// we may have read data from the next line, save it to rest
if (rest) {
rest[0] = '\0';
strcpy(rest, lf + 1);
}
free(buffer);
return strlen(buf);
} else {
// no linefeed found, slide data back.
int overflow = readpos - buffer + l - nbyte;
if (overflow > 0) {
memmove(buffer, buffer + overflow , nbyte + 1);
}
}
}
if (l < 0) {
free(buffer);
return l;
}
readpos[l]='\0';
if (rest) {
rest[0] = '\0';
}
if (nbyte < readpos - buffer + l - 1) {
memcpy(buf, readpos - nbyte + l + 1, nbyte);
} else {
strncpy(buf, buffer, nbyte);
}
buf[nbyte-1] = '\0';
free(buffer);
return strlen(buf);
}
// make sure that enough buffer space is available starting from parserpos
static void i3json_ensure_free(int min_free) {
int _step = 10240;
int r = min_free % _step;
if (r) {
min_free += _step - r;
}
if (!i3json_state.buffer) {
i3json_state.buffer = malloc(min_free);
i3json_state.bufsize = min_free;
i3json_state.parserpos = i3json_state.buffer;
} else {
int len = 0;
int pos = 0;
if (i3json_state.line_start) {
len = strlen(i3json_state.line_start);
pos = i3json_state.parserpos - i3json_state.line_start;
if (i3json_state.line_start != i3json_state.buffer) {
memmove(i3json_state.buffer, i3json_state.line_start, len+1);
}
} else {
len = strlen(i3json_state.buffer);
}
if (i3json_state.bufsize < len+min_free) {
i3json_state.bufsize += min_free;
if (i3json_state.bufsize > 1024000) {
sway_abort("Status line json too long or malformed.");
}
i3json_state.buffer = realloc(i3json_state.buffer, i3json_state.bufsize);
if (!i3json_state.buffer) {
sway_abort("Could not allocate json buffer");
}
}
if (i3json_state.line_start) {
i3json_state.line_start = i3json_state.buffer;
i3json_state.parserpos = i3json_state.buffer + pos;
} else {
i3json_state.parserpos = i3json_state.buffer;
}
}
if (!i3json_state.buffer) {
sway_abort("Could not allocate buffer.");
}
}
// append data and parse it.
static int i3json_handle_data(struct bar *bar, char *data) {
int len = strlen(data);
i3json_ensure_free(len);
strcpy(i3json_state.parserpos, data);
return i3json_parse(bar);
}
// read data from fd and parse it.
static int i3json_handle_fd(struct bar *bar) {
i3json_ensure_free(10240);
// get fresh data at the end of the buffer
int readlen = read(bar->status_read_fd, i3json_state.parserpos, 10239);
if (readlen < 0) {
return readlen;
}
i3json_state.parserpos[readlen] = '\0';
return i3json_parse(bar);
}
bool status_line_mouse_event(struct bar *bar, int x, int y, uint32_t button) {
sway_log(L_DEBUG, "status_line_mouse_event.");
if (!bar->status->click_events) {
sway_log(L_DEBUG, "click_events are not enabled.");
return false;
}
if (bar->status->protocol == I3BAR) {
sway_log(L_DEBUG, "Sending click event.");
// find clicked block
struct status_block *clicked_block = NULL;
struct status_block *current_block = NULL;
int num_blocks = bar->status->block_line->length;
if (num_blocks == 0) {
return false;
} else {
current_block = bar->status->block_line->items[0];
if (x < current_block->x) {
return false;
}
}
for (int i = 0; i < num_blocks; i++) {
current_block = bar->status->block_line->items[i];
if (x < (current_block->x + current_block->width)) {
clicked_block = current_block;
break;
}
}
if (!clicked_block || !clicked_block->name) {
return false;
}
// event example {"name":"capture","instance":"label","button":1,"x":3431,"y":18}
struct json_object *event_json = json_object_new_object();
json_object_object_add(event_json, "name", json_object_new_string(clicked_block->name));
if (clicked_block->instance) {
json_object_object_add(event_json, "instance", json_object_new_string(clicked_block->instance));
}
json_object_object_add(event_json, "button", 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));
int len = snprintf(event_buff, sizeof(event_buff), "%s\n", json_object_to_json_string(event_json));
json_object_put(event_json);
if (len <= (int)sizeof(event_buff)) { // if not truncated
write(bar->status_write_fd, event_buff, len);
return true;
}
}
return false;
}
bool handle_status_line(struct bar *bar) {
bool dirty = false;
switch (bar->status->protocol) {
case I3BAR:
sway_log(L_DEBUG, "Got i3bar protocol.");
if (i3json_handle_fd(bar) > 0) {
dirty = true;
}
break;
case TEXT:
sway_log(L_DEBUG, "Got text protocol.");
read_line_tail(bar->status_read_fd, line, sizeof(line), line_rest);
dirty = true;
bar->status->text_line = line;
break;
case UNDEF:
sway_log(L_DEBUG, "Detecting protocol...");
if (read_line_tail(bar->status_read_fd, line, sizeof(line), line_rest) < 0) {
break;
}
dirty = true;
bar->status->text_line = line;
bar->status->protocol = TEXT;
if (line[0] == '{') {
// detect i3bar json protocol
json_object *proto = json_tokener_parse(line);
if (proto) {
json_object *version;
if (json_object_object_get_ex(proto, "version", &version)
&& json_object_get_int(version) == 1
) {
sway_log(L_DEBUG, "Switched to i3bar protocol.");
bar->status->protocol = I3BAR;
}
json_object *click_events;
if (json_object_object_get_ex(proto, "click_events", &click_events)
&& json_object_get_boolean(click_events)) {
sway_log(L_DEBUG, "Enabling click events.");
bar->status->click_events = true;
const char *events_array = "[\n";
write(bar->status_write_fd, events_array, strlen(events_array));
}
i3json_handle_data(bar, line_rest);
json_object_put(proto);
}
}
break;
}
return dirty;
}
struct status_line *init_status_line() {
struct status_line *line = malloc(sizeof(struct status_line));
line->block_line = create_list();
line->text_line = NULL;
line->protocol = UNDEF;
line->click_events = false;
return line;
}
void free_status_line(struct status_line *line) {
if (line->block_line) {
list_foreach(line->block_line, free_status_block);
list_free(line->block_line);
}
}

View file

@ -1,197 +0,0 @@
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <poll.h>
#include <signal.h>
#include <time.h>
#include <dbus/dbus.h>
#include "swaybar/tray/dbus.h"
#include "swaybar/event_loop.h"
#include "log.h"
DBusConnection *conn = NULL;
static void dispatch_watch(int fd, short mask, void *data) {
sway_log(L_DEBUG, "Dispatching watch");
DBusWatch *watch = data;
if (!dbus_watch_get_enabled(watch)) {
return;
}
uint32_t flags = 0;
if (mask & POLLIN) {
flags |= DBUS_WATCH_READABLE;
} if (mask & POLLOUT) {
flags |= DBUS_WATCH_WRITABLE;
} if (mask & POLLHUP) {
flags |= DBUS_WATCH_HANGUP;
} if (mask & POLLERR) {
flags |= DBUS_WATCH_ERROR;
}
dbus_watch_handle(watch, flags);
}
static dbus_bool_t add_watch(DBusWatch *watch, void *_data) {
if (!dbus_watch_get_enabled(watch)) {
// Watch should not be polled
return TRUE;
}
short mask = 0;
uint32_t flags = dbus_watch_get_flags(watch);
if (flags & DBUS_WATCH_READABLE) {
mask |= POLLIN;
} if (flags & DBUS_WATCH_WRITABLE) {
mask |= POLLOUT;
}
int fd = dbus_watch_get_unix_fd(watch);
sway_log(L_DEBUG, "Adding DBus watch fd: %d", fd);
add_event(fd, mask, dispatch_watch, watch);
return TRUE;
}
static void remove_watch(DBusWatch *watch, void *_data) {
int fd = dbus_watch_get_unix_fd(watch);
remove_event(fd);
}
static void dispatch_timeout(timer_t timer, void *data) {
sway_log(L_DEBUG, "Dispatching DBus timeout");
DBusTimeout *timeout = data;
if (dbus_timeout_get_enabled(timeout)) {
dbus_timeout_handle(timeout);
}
}
static dbus_bool_t add_timeout(DBusTimeout *timeout, void *_data) {
if (!dbus_timeout_get_enabled(timeout)) {
return TRUE;
}
timer_t *timer = malloc(sizeof(timer_t));
if (!timer) {
sway_log(L_ERROR, "Cannot allocate memory");
return FALSE;
}
struct sigevent ev = {
.sigev_notify = SIGEV_NONE,
};
if (timer_create(CLOCK_MONOTONIC, &ev, timer)) {
sway_log(L_ERROR, "Could not create DBus timer");
return FALSE;
}
int interval = dbus_timeout_get_interval(timeout);
int interval_sec = interval / 1000;
int interval_msec = (interval_sec * 1000) - interval;
struct timespec period = {
(time_t) interval_sec,
((long) interval_msec) * 1000 * 1000,
};
struct itimerspec time = {
period,
period,
};
timer_settime(*timer, 0, &time, NULL);
dbus_timeout_set_data(timeout, timer, NULL);
sway_log(L_DEBUG, "Adding DBus timeout. Interval: %ds %dms", interval_sec, interval_msec);
add_timer(*timer, dispatch_timeout, timeout);
return TRUE;
}
static void remove_timeout(DBusTimeout *timeout, void *_data) {
timer_t *timer = (timer_t *) dbus_timeout_get_data(timeout);
sway_log(L_DEBUG, "Removing DBus timeout.");
if (timer) {
remove_timer(*timer);
timer_delete(*timer);
free(timer);
}
}
static bool should_dispatch = true;
static void dispatch_status(DBusConnection *connection, DBusDispatchStatus new_status,
void *_data) {
if (new_status == DBUS_DISPATCH_DATA_REMAINS) {
should_dispatch = true;
}
}
/* Public functions below */
void dispatch_dbus() {
if (!should_dispatch || !conn) {
return;
}
DBusDispatchStatus status;
do {
status = dbus_connection_dispatch(conn);
} while (status == DBUS_DISPATCH_DATA_REMAINS);
if (status != DBUS_DISPATCH_COMPLETE) {
sway_log(L_ERROR, "Cannot dispatch dbus events: %d", status);
}
should_dispatch = false;
}
int dbus_init() {
DBusError error;
dbus_error_init(&error);
conn = dbus_bus_get(DBUS_BUS_SESSION, &error);
if (conn == NULL) {
sway_log(L_INFO, "Compiled with dbus support, but unable to connect to dbus");
sway_log(L_INFO, "swaybar will be unable to display tray icons.");
return -1;
}
dbus_connection_set_exit_on_disconnect(conn, FALSE);
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "Cannot get bus connection: %s\n", error.message);
conn = NULL;
return -1;
}
sway_log(L_INFO, "Unique name: %s\n", dbus_bus_get_unique_name(conn));
// Will be called if dispatch status changes
dbus_connection_set_dispatch_status_function(conn, dispatch_status, NULL, NULL);
if (!dbus_connection_set_watch_functions(conn, add_watch, remove_watch,
NULL, NULL, NULL)) {
dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL);
sway_log(L_ERROR, "Failed to activate DBUS watch functions");
return -1;
}
if (!dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout,
NULL, NULL, NULL)) {
dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL);
dbus_connection_set_timeout_functions(conn, NULL, NULL, NULL, NULL, NULL);
sway_log(L_ERROR, "Failed to activate DBUS timeout functions");
return -1;
}
return 0;
}

View file

@ -1,400 +0,0 @@
#define _XOPEN_SOURCE 700
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <stdint.h>
#include <limits.h>
#include "swaybar/tray/icon.h"
#include "swaybar/bar.h"
#include "swaybar/config.h"
#include "stringop.h"
#include "log.h"
/**
* REVIEW:
* This file repeats lots of "costly" operations that are the same for every
* icon. It's possible to create a dictionary or some other structure to cache
* these, though it may complicate things somewhat.
*
* Also parsing (index.theme) is currently pretty messy, so that could be made
* much better as well. Over all, things work, but are not optimal.
*/
/* Finds all themes that the given theme inherits */
static list_t *find_inherits(const char *theme_dir) {
const char inherits[] = "Inherits";
const char index_name[] = "index.theme";
list_t *themes = create_list();
FILE *index = NULL;
char *path = malloc(strlen(theme_dir) + sizeof(index_name));
if (!path) {
goto fail;
}
if (!themes) {
goto fail;
}
strcpy(path, theme_dir);
strcat(path, index_name);
index = fopen(path, "r");
if (!index) {
goto fail;
}
char *buf = NULL;
size_t n = 0;
while (!feof(index) && getline(&buf, &n, index) != -1) {
if (n <= sizeof(inherits) + 1) {
continue;
}
if (strncmp(inherits, buf, sizeof(inherits) - 1) == 0) {
char *themestr = buf + sizeof(inherits);
themes = split_string(themestr, ",");
break;
}
}
free(buf);
fail:
free(path);
if (index) {
fclose(index);
}
return themes;
}
static bool isdir(const char *path) {
struct stat statbuf;
if (stat(path, &statbuf) != -1) {
if (S_ISDIR(statbuf.st_mode)) {
return true;
}
}
return false;
}
/**
* Returns the directory of a given theme if it exists.
* The returned pointer must be freed.
*/
static char *find_theme_dir(const char *theme) {
char *basedir;
char *icon_dir;
if (!theme) {
return NULL;
}
if (!(icon_dir = malloc(1024))) {
sway_log(L_ERROR, "Out of memory!");
goto fail;
}
if ((basedir = getenv("HOME"))) {
if (snprintf(icon_dir, 1024, "%s/.icons/%s", basedir, theme) >= 1024) {
sway_log(L_ERROR, "Path too long to render");
// XXX perhaps just goto trying in /usr/share? This
// shouldn't happen anyway, but might with a long global
goto fail;
}
if (isdir(icon_dir)) {
return icon_dir;
}
}
if ((basedir = getenv("XDG_DATA_DIRS"))) {
if (snprintf(icon_dir, 1024, "%s/icons/%s", basedir, theme) >= 1024) {
sway_log(L_ERROR, "Path too long to render");
// ditto
goto fail;
}
if (isdir(icon_dir)) {
return icon_dir;
}
}
// Spec says use "/usr/share/pixmaps/", but I see everything in
// "/usr/share/icons/" look it both, I suppose.
if (snprintf(icon_dir, 1024, "/usr/share/pixmaps/%s", theme) >= 1024) {
sway_log(L_ERROR, "Path too long to render");
goto fail;
}
if (isdir(icon_dir)) {
return icon_dir;
}
if (snprintf(icon_dir, 1024, "/usr/share/icons/%s", theme) >= 1024) {
sway_log(L_ERROR, "Path too long to render");
goto fail;
}
if (isdir(icon_dir)) {
return icon_dir;
}
fail:
free(icon_dir);
sway_log(L_ERROR, "Could not find dir for theme: %s", theme);
return NULL;
}
/**
* Returns all theme dirs needed to be looked in for an icon.
* Does not check for duplicates
*/
static list_t *find_all_theme_dirs(const char *theme) {
list_t *dirs = create_list();
if (!dirs) {
return NULL;
}
char *dir = find_theme_dir(theme);
if (dir) {
list_add(dirs, dir);
list_t *inherits = find_inherits(dir);
list_cat(dirs, inherits);
list_free(inherits);
}
dir = find_theme_dir("hicolor");
if (dir) {
list_add(dirs, dir);
}
return dirs;
}
struct subdir {
int size;
char name[];
};
static int subdir_str_cmp(const void *_subdir, const void *_str) {
const struct subdir *subdir = _subdir;
const char *str = _str;
return strcmp(subdir->name, str);
}
/**
* Helper to find_subdirs. Acts similar to `split_string(subdirs, ",")` but
* generates a list of struct subdirs
*/
static list_t *split_subdirs(char *subdir_str) {
list_t *subdir_list = create_list();
char *copy = strdup(subdir_str);
if (!subdir_list || !copy) {
list_free(subdir_list);
free(copy);
return NULL;
}
char *token;
token = strtok(copy, ",");
while(token) {
int len = strlen(token) + 1;
struct subdir *subdir =
malloc(sizeof(struct subdir) + sizeof(char [len]));
if (!subdir) {
// Return what we have
return subdir_list;
}
subdir->size = 0;
strcpy(subdir->name, token);
list_add(subdir_list, subdir);
token = strtok(NULL, ",");
}
free(copy);
return subdir_list;
}
/**
* Returns a list of all subdirectories of a theme.
* Take note: the subdir names are all relative to `theme_dir` and must be
* combined with it to form a valid directory.
*
* Each member of the list is of type (struct subdir *) this struct contains
* the name of the subdir, along with size information. These must be freed
* bye the caller.
*
* This currently ignores min and max sizes of icons.
*/
static list_t* find_theme_subdirs(const char *theme_dir) {
const char index_name[] = "/index.theme";
list_t *dirs = NULL;
char *path = malloc(strlen(theme_dir) + sizeof(index_name));
FILE *index = NULL;
if (!path) {
sway_log(L_ERROR, "Failed to allocate memory");
goto fail;
}
strcpy(path, theme_dir);
strcat(path, index_name);
index = fopen(path, "r");
if (!index) {
sway_log(L_ERROR, "Could not open file: %s", path);
goto fail;
}
char *buf = NULL;
size_t n = 0;
const char directories[] = "Directories";
while (!feof(index) && getline(&buf, &n, index) != -1) {
if (n <= sizeof(directories) + 1) {
continue;
}
if (strncmp(directories, buf, sizeof(directories) - 1) == 0) {
char *dirstr = buf + sizeof(directories);
dirs = split_subdirs(dirstr);
break;
}
}
// Now, find the size of each dir
struct subdir *current_subdir = NULL;
const char size[] = "Size";
while (!feof(index) && getline(&buf, &n, index) != -1) {
if (buf[0] == '[') {
int len = strlen(buf);
if (buf[len-1] == '\n') {
len--;
}
// replace ']'
buf[len-1] = '\0';
int index;
if ((index = list_seq_find(dirs, subdir_str_cmp, buf+1)) != -1) {
current_subdir = (dirs->items[index]);
}
}
if (strncmp(size, buf, sizeof(size) - 1) == 0) {
if (current_subdir) {
current_subdir->size = atoi(buf + sizeof(size));
}
}
}
free(buf);
fail:
free(path);
if (index) {
fclose(index);
}
return dirs;
}
/* Returns the file of an icon given its name and size */
static char *find_icon_file(const char *name, int size) {
int namelen = strlen(name);
list_t *dirs = find_all_theme_dirs(swaybar.config->icon_theme);
if (!dirs) {
return NULL;
}
int min_size_diff = INT_MAX;
char *current_file = NULL;
for (int i = 0; i < dirs->length; ++i) {
char *dir = dirs->items[i];
list_t *subdirs = find_theme_subdirs(dir);
if (!subdirs) {
continue;
}
for (int i = 0; i < subdirs->length; ++i) {
struct subdir *subdir = subdirs->items[i];
// Only use an unsized if we don't already have a
// canidate this should probably change to allow svgs
if (!subdir->size && current_file) {
continue;
}
int size_diff = abs(size - subdir->size);
if (size_diff >= min_size_diff) {
continue;
}
char *path = malloc(strlen(subdir->name) + strlen(dir) + 2);
strcpy(path, dir);
path[strlen(dir)] = '/';
strcpy(path + strlen(dir) + 1, subdir->name);
DIR *icons = opendir(path);
if (!icons) {
free(path);
continue;
}
struct dirent *direntry;
while ((direntry = readdir(icons)) != NULL) {
int len = strlen(direntry->d_name);
if (len <= namelen + 2) { //must have some ext
continue;
}
if (strncmp(direntry->d_name, name, namelen) == 0) {
char *ext = direntry->d_name + namelen + 1;
#ifdef WITH_GDK_PIXBUF
if (strcmp(ext, "png") == 0 ||
strcmp(ext, "xpm") == 0 ||
strcmp(ext, "svg") == 0) {
#else
if (strcmp(ext, "png") == 0) {
#endif
free(current_file);
char *icon_path = malloc(strlen(path) + len + 2);
strcpy(icon_path, path);
icon_path[strlen(path)] = '/';
strcpy(icon_path + strlen(path) + 1, direntry->d_name);
current_file = icon_path;
min_size_diff = size_diff;
}
}
}
free(path);
closedir(icons);
}
free_flat_list(subdirs);
}
free_flat_list(dirs);
return current_file;
}
cairo_surface_t *find_icon(const char *name, int size) {
char *image_path = find_icon_file(name, size);
if (image_path == NULL) {
return NULL;
}
cairo_surface_t *image = NULL;
#ifdef WITH_GDK_PIXBUF
GError *err = NULL;
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(image_path, &err);
if (!pixbuf) {
sway_log(L_ERROR, "Failed to load icon image: %s", err->message);
}
image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf);
g_object_unref(pixbuf);
#else
// TODO make svg work? cairo supports it. maybe remove gdk alltogether
image = cairo_image_surface_create_from_png(image_path);
#endif //WITH_GDK_PIXBUF
if (!image) {
sway_log(L_ERROR, "Could not read icon image");
return NULL;
}
free(image_path);
return image;
}

View file

@ -1,481 +0,0 @@
#define _XOPEN_SOURCE 500
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <dbus/dbus.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "swaybar/tray/dbus.h"
#include "swaybar/tray/sni.h"
#include "swaybar/tray/icon.h"
#include "swaybar/bar.h"
#include "client/cairo.h"
#include "log.h"
// Not sure what this is but cairo needs it.
static const cairo_user_data_key_t cairo_user_data_key;
struct sni_icon_ref *sni_icon_ref_create(struct StatusNotifierItem *item,
int height) {
struct sni_icon_ref *sni_ref = malloc(sizeof(struct sni_icon_ref));
if (!sni_ref) {
return NULL;
}
sni_ref->icon = cairo_image_surface_scale(item->image, height, height);
sni_ref->ref = item;
return sni_ref;
}
void sni_icon_ref_free(struct sni_icon_ref *sni_ref) {
if (!sni_ref) {
return;
}
cairo_surface_destroy(sni_ref->icon);
free(sni_ref);
}
/* Gets the pixmap of an icon */
static void reply_icon(DBusPendingCall *pending, void *_data) {
struct StatusNotifierItem *item = _data;
DBusMessage *reply = dbus_pending_call_steal_reply(pending);
if (!reply) {
sway_log(L_ERROR, "Did not get reply");
goto bail;
}
int message_type = dbus_message_get_type(reply);
if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
char *msg;
dbus_message_get_args(reply, NULL,
DBUS_TYPE_STRING, &msg,
DBUS_TYPE_INVALID);
sway_log(L_ERROR, "Message is error: %s", msg);
goto bail;
}
DBusMessageIter iter;
DBusMessageIter variant; /* v[a(iiay)] */
DBusMessageIter array; /* a(iiay) */
DBusMessageIter d_struct; /* (iiay) */
DBusMessageIter icon; /* ay */
dbus_message_iter_init(reply, &iter);
// Each if here checks the types above before recursing
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
sway_log(L_ERROR, "Relpy type incorrect");
sway_log(L_ERROR, "Should be \"v\", is \"%s\"",
dbus_message_iter_get_signature(&iter));
goto bail;
}
dbus_message_iter_recurse(&iter, &variant);
if (strcmp("a(iiay)", dbus_message_iter_get_signature(&variant)) != 0) {
sway_log(L_ERROR, "Relpy type incorrect");
sway_log(L_ERROR, "Should be \"a(iiay)\", is \"%s\"",
dbus_message_iter_get_signature(&variant));
goto bail;
}
if (dbus_message_iter_get_element_count(&variant) == 0) {
// Can't recurse if there are no items
sway_log(L_INFO, "Item has no icon");
goto bail;
}
dbus_message_iter_recurse(&variant, &array);
dbus_message_iter_recurse(&array, &d_struct);
int width;
dbus_message_iter_get_basic(&d_struct, &width);
dbus_message_iter_next(&d_struct);
int height;
dbus_message_iter_get_basic(&d_struct, &height);
dbus_message_iter_next(&d_struct);
int len = dbus_message_iter_get_element_count(&d_struct);
if (!len) {
sway_log(L_ERROR, "No icon data");
goto bail;
}
// Also implies len % 4 == 0, useful below
if (len != width * height * 4) {
sway_log(L_ERROR, "Incorrect array size passed");
goto bail;
}
dbus_message_iter_recurse(&d_struct, &icon);
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
// FIXME support a variable stride
// (works on my machine though for all tested widths)
if (!sway_assert(stride == width * 4, "Stride must be equal to byte length")) {
goto bail;
}
// Data is by reference, no need to free
uint8_t *message_data;
dbus_message_iter_get_fixed_array(&icon, &message_data, &len);
uint8_t *image_data = malloc(stride * height);
if (!image_data) {
sway_log(L_ERROR, "Could not allocate memory for icon");
goto bail;
}
// Transform from network byte order to host byte order
// Assumptions are safe because the equality above
uint32_t *network = (uint32_t *) message_data;
uint32_t *host = (uint32_t *)image_data;
for (int i = 0; i < width * height; ++i) {
host[i] = ntohl(network[i]);
}
cairo_surface_t *image = cairo_image_surface_create_for_data(
image_data, CAIRO_FORMAT_ARGB32,
width, height, stride);
if (image) {
if (item->image) {
cairo_surface_destroy(item->image);
}
item->image = image;
// Free the image data on surface destruction
cairo_surface_set_user_data(image,
&cairo_user_data_key,
image_data,
free);
item->dirty = true;
dirty = true;
dbus_message_unref(reply);
dbus_pending_call_unref(pending);
return;
} else {
sway_log(L_ERROR, "Could not create image surface");
free(image_data);
}
bail:
if (reply) {
dbus_message_unref(reply);
}
dbus_pending_call_unref(pending);
sway_log(L_ERROR, "Could not get icon from item");
return;
}
static void send_icon_msg(struct StatusNotifierItem *item) {
DBusPendingCall *pending;
DBusMessage *message = dbus_message_new_method_call(
item->name,
"/StatusNotifierItem",
"org.freedesktop.DBus.Properties",
"Get");
const char *iface;
if (item->kde_special_snowflake) {
iface = "org.kde.StatusNotifierItem";
} else {
iface = "org.freedesktop.StatusNotifierItem";
}
const char *prop = "IconPixmap";
dbus_message_append_args(message,
DBUS_TYPE_STRING, &iface,
DBUS_TYPE_STRING, &prop,
DBUS_TYPE_INVALID);
bool status =
dbus_connection_send_with_reply(conn, message, &pending, -1);
dbus_message_unref(message);
if (!(pending || status)) {
sway_log(L_ERROR, "Could not get item icon");
return;
}
dbus_pending_call_set_notify(pending, reply_icon, item, NULL);
}
/* Get an icon by its name */
static void reply_icon_name(DBusPendingCall *pending, void *_data) {
struct StatusNotifierItem *item = _data;
DBusMessage *reply = dbus_pending_call_steal_reply(pending);
if (!reply) {
sway_log(L_INFO, "Got no icon name reply from item");
goto bail;
}
int message_type = dbus_message_get_type(reply);
if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
char *msg;
dbus_message_get_args(reply, NULL,
DBUS_TYPE_STRING, &msg,
DBUS_TYPE_INVALID);
sway_log(L_INFO, "Could not get icon name: %s", msg);
goto bail;
}
DBusMessageIter iter; /* v[s] */
DBusMessageIter variant; /* s */
dbus_message_iter_init(reply, &iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
sway_log(L_ERROR, "Relpy type incorrect");
sway_log(L_ERROR, "Should be \"v\", is \"%s\"",
dbus_message_iter_get_signature(&iter));
goto bail;
}
dbus_message_iter_recurse(&iter, &variant);
if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_STRING) {
sway_log(L_ERROR, "Relpy type incorrect");
sway_log(L_ERROR, "Should be \"s\", is \"%s\"",
dbus_message_iter_get_signature(&iter));
goto bail;
}
char *icon_name;
dbus_message_iter_get_basic(&variant, &icon_name);
cairo_surface_t *image = find_icon(icon_name, 256);
if (image) {
sway_log(L_DEBUG, "Icon for %s found with size %d", icon_name,
cairo_image_surface_get_width(image));
if (item->image) {
cairo_surface_destroy(item->image);
}
item->image = image;
item->dirty = true;
dirty = true;
dbus_message_unref(reply);
dbus_pending_call_unref(pending);
return;
}
bail:
if (reply) {
dbus_message_unref(reply);
}
dbus_pending_call_unref(pending);
// Now try the pixmap
send_icon_msg(item);
return;
}
static void send_icon_name_msg(struct StatusNotifierItem *item) {
DBusPendingCall *pending;
DBusMessage *message = dbus_message_new_method_call(
item->name,
"/StatusNotifierItem",
"org.freedesktop.DBus.Properties",
"Get");
const char *iface;
if (item->kde_special_snowflake) {
iface = "org.kde.StatusNotifierItem";
} else {
iface = "org.freedesktop.StatusNotifierItem";
}
const char *prop = "IconName";
dbus_message_append_args(message,
DBUS_TYPE_STRING, &iface,
DBUS_TYPE_STRING, &prop,
DBUS_TYPE_INVALID);
bool status =
dbus_connection_send_with_reply(conn, message, &pending, -1);
dbus_message_unref(message);
if (!(pending || status)) {
sway_log(L_ERROR, "Could not get item icon name");
return;
}
dbus_pending_call_set_notify(pending, reply_icon_name, item, NULL);
}
void get_icon(struct StatusNotifierItem *item) {
send_icon_name_msg(item);
}
void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
const char *iface =
(item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
: "org.freedesktop.StatusNotifierItem");
DBusMessage *message = dbus_message_new_method_call(
item->name,
"/StatusNotifierItem",
iface,
"Activate");
dbus_message_append_args(message,
DBUS_TYPE_INT32, &x,
DBUS_TYPE_INT32, &y,
DBUS_TYPE_INVALID);
dbus_connection_send(conn, message, NULL);
dbus_message_unref(message);
}
void sni_context_menu(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
const char *iface =
(item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
: "org.freedesktop.StatusNotifierItem");
DBusMessage *message = dbus_message_new_method_call(
item->name,
"/StatusNotifierItem",
iface,
"ContextMenu");
dbus_message_append_args(message,
DBUS_TYPE_INT32, &x,
DBUS_TYPE_INT32, &y,
DBUS_TYPE_INVALID);
dbus_connection_send(conn, message, NULL);
dbus_message_unref(message);
}
void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
const char *iface =
(item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
: "org.freedesktop.StatusNotifierItem");
DBusMessage *message = dbus_message_new_method_call(
item->name,
"/StatusNotifierItem",
iface,
"SecondaryActivate");
dbus_message_append_args(message,
DBUS_TYPE_INT32, &x,
DBUS_TYPE_INT32, &y,
DBUS_TYPE_INVALID);
dbus_connection_send(conn, message, NULL);
dbus_message_unref(message);
}
static void get_unique_name(struct StatusNotifierItem *item) {
// I think that we're fine being sync here becaues the message is
// directly to the message bus. Could be async though.
DBusMessage *message = dbus_message_new_method_call(
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"GetNameOwner");
dbus_message_append_args(message,
DBUS_TYPE_STRING, &item->name,
DBUS_TYPE_INVALID);
DBusMessage *reply = dbus_connection_send_with_reply_and_block(
conn, message, -1, NULL);
dbus_message_unref(message);
if (!reply) {
sway_log(L_ERROR, "Could not get unique name for item: %s",
item->name);
return;
}
char *unique_name;
if (!dbus_message_get_args(reply, NULL,
DBUS_TYPE_STRING, &unique_name,
DBUS_TYPE_INVALID)) {
sway_log(L_ERROR, "Error parsing method args");
} else {
if (item->unique_name) {
free(item->unique_name);
}
item->unique_name = strdup(unique_name);
}
dbus_message_unref(reply);
}
struct StatusNotifierItem *sni_create(const char *name) {
// Make sure `name` is well formed
if (!dbus_validate_bus_name(name, NULL)) {
sway_log(L_INFO, "Name (%s) is not a bus name. We cannot create an item.", name);
return NULL;
}
struct StatusNotifierItem *item = malloc(sizeof(struct StatusNotifierItem));
item->name = strdup(name);
item->unique_name = NULL;
item->image = NULL;
item->dirty = false;
// If it doesn't use this name then assume that it uses the KDE spec
// This is because xembed-sni-proxy uses neither "org.freedesktop" nor
// "org.kde" and just gives us the items "unique name"
//
// We could use this to our advantage and fill out the "unique name"
// field with the given name if it is neither freedesktop or kde, but
// that's makes us rely on KDE hackyness which is bad practice
const char freedesktop_name[] = "org.freedesktop";
if (strncmp(name, freedesktop_name, sizeof(freedesktop_name) - 1) != 0) {
item->kde_special_snowflake = true;
} else {
item->kde_special_snowflake = false;
}
get_icon(item);
get_unique_name(item);
return item;
}
/* Return 0 if `item` has a name of `str` */
int sni_str_cmp(const void *_item, const void *_str) {
const struct StatusNotifierItem *item = _item;
const char *str = _str;
return strcmp(item->name, str);
}
/* Returns 0 if `item` has a unique name of `str` */
int sni_uniq_cmp(const void *_item, const void *_str) {
const struct StatusNotifierItem *item = _item;
const char *str = _str;
if (!item->unique_name) {
return false;
}
return strcmp(item->unique_name, str);
}
void sni_free(struct StatusNotifierItem *item) {
if (!item) {
return;
}
free(item->name);
if (item->unique_name) {
free(item->unique_name);
}
if (item->image) {
cairo_surface_destroy(item->image);
}
free(item);
}

View file

@ -1,497 +0,0 @@
#define _XOPEN_SOURCE 500
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <dbus/dbus.h>
#include "swaybar/tray/dbus.h"
#include "list.h"
#include "log.h"
static list_t *items = NULL;
static list_t *hosts = NULL;
/**
* Describes the function of the StatusNotifierWatcher
* See https://freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierWatcher/
*
* We also implement KDE's special snowflake protocol, it's like this but with
* all occurrences 'freedesktop' replaced with 'kde'. There is no KDE introspect.
*/
static const char *interface_xml =
"<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'"
"'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>"
"<node>"
" <interface name='org.freedesktop.DBus.Introspectable'>"
" <method name='Introspect'>"
" <arg name='xml_data' direction='out' type='s'/>"
" </method>"
" </interface>"
" <interface name='org.freedesktop.DBus.Properties'>"
" <method name='Get'>"
" <arg name='interface' direction='in' type='s'/>"
" <arg name='propname' direction='in' type='s'/>"
" <arg name='value' direction='out' type='v'/>"
" </method>"
" <method name='Set'>"
" <arg name='interface' direction='in' type='s'/>"
" <arg name='propname' direction='in' type='s'/>"
" <arg name='value' direction='in' type='v'/>"
" </method>"
" <method name='GetAll'>"
" <arg name='interface' direction='in' type='s'/>"
" <arg name='props' direction='out' type='a{sv}'/>"
" </method>"
" </interface>"
" <interface name='org.freedesktop.StatusNotifierWatcher'>"
" <method name='RegisterStatusNotifierItem'>"
" <arg type='s' name='service' direction='in'/>"
" </method>"
" <method name='RegisterStatusNotifierHost'>"
" <arg type='s' name='service' direction='in'/>"
" </method>"
" <property name='RegisteredStatusNotifierItems' type='as' access='read'/>"
" <property name='IsStatusNotifierHostRegistered' type='b' access='read'/>"
" <property name='ProtocolVersion' type='i' access='read'/>"
" <signal name='StatusNotifierItemRegistered'>"
" <arg type='s' name='service' direction='out'/>"
" </signal>"
" <signal name='StatusNotifierItemUnregistered'>"
" <arg type='s' name='service' direction='out'/>"
" </signal>"
" <signal name='StatusNotifierHostRegistered'>"
" <arg type='' name='service' direction='out'/>"
" </signal>"
" </interface>"
"</node>";
static void host_registered_signal(DBusConnection *connection) {
// Send one signal for each protocol
DBusMessage *signal = dbus_message_new_signal(
"/StatusNotifierWatcher",
"org.freedesktop.StatusNotifierWatcher",
"StatusNotifierHostRegistered");
dbus_connection_send(connection, signal, NULL);
dbus_message_unref(signal);
signal = dbus_message_new_signal(
"/StatusNotifierWatcher",
"org.kde.StatusNotifierWatcher",
"StatusNotifierHostRegistered");
dbus_connection_send(connection, signal, NULL);
dbus_message_unref(signal);
}
static void item_registered_signal(DBusConnection *connection, const char *name) {
DBusMessage *signal = dbus_message_new_signal(
"/StatusNotifierWatcher",
"org.freedesktop.StatusNotifierWatcher",
"StatusNotifierItemRegistered");
dbus_message_append_args(signal,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID);
dbus_connection_send(connection, signal, NULL);
dbus_message_unref(signal);
signal = dbus_message_new_signal(
"/StatusNotifierWatcher",
"org.kde.StatusNotifierWatcher",
"StatusNotifierItemRegistered");
dbus_message_append_args(signal,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID);
dbus_connection_send(connection, signal, NULL);
dbus_message_unref(signal);
}
static void item_unregistered_signal(DBusConnection *connection, const char *name) {
DBusMessage *signal = dbus_message_new_signal(
"/StatusNotifierWatcher",
"org.freedesktop.StatusNotifierWatcher",
"StatusNotifierItemUnregistered");
dbus_message_append_args(signal,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID);
dbus_connection_send(connection, signal, NULL);
dbus_message_unref(signal);
signal = dbus_message_new_signal(
"/StatusNotifierWatcher",
"org.kde.StatusNotifierWatcher",
"StatusNotifierItemUnregistered");
dbus_message_append_args(signal,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID);
dbus_connection_send(connection, signal, NULL);
dbus_message_unref(signal);
}
static void respond_to_introspect(DBusConnection *connection, DBusMessage *request) {
DBusMessage *reply;
reply = dbus_message_new_method_return(request);
dbus_message_append_args(reply,
DBUS_TYPE_STRING, &interface_xml,
DBUS_TYPE_INVALID);
dbus_connection_send(connection, reply, NULL);
dbus_message_unref(reply);
}
static void register_item(DBusConnection *connection, DBusMessage *message) {
DBusError error;
char *name;
dbus_error_init(&error);
if (!dbus_message_get_args(message, &error,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID)) {
sway_log(L_ERROR, "Error parsing method args: %s\n", error.message);
}
sway_log(L_INFO, "RegisterStatusNotifierItem called with \"%s\"\n", name);
// Don't add duplicate or not real item
if (!dbus_validate_bus_name(name, NULL)) {
sway_log(L_INFO, "This item is not valid, we cannot keep track of it.");
return;
}
if (list_seq_find(items, (int (*)(const void *, const void *))strcmp, name) != -1) {
return;
}
if (!dbus_bus_name_has_owner(connection, name, &error)) {
return;
}
list_add(items, strdup(name));
item_registered_signal(connection, name);
// It's silly, but xembedsniproxy wants a reply for this function
DBusMessage *reply = dbus_message_new_method_return(message);
dbus_connection_send(connection, reply, NULL);
dbus_message_unref(reply);
}
static void register_host(DBusConnection *connection, DBusMessage *message) {
DBusError error;
char *name;
dbus_error_init(&error);
if (!dbus_message_get_args(message, &error,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID)) {
sway_log(L_ERROR, "Error parsing method args: %s\n", error.message);
}
sway_log(L_INFO, "RegisterStatusNotifierHost called with \"%s\"\n", name);
// Don't add duplicate or not real host
if (!dbus_validate_bus_name(name, NULL)) {
sway_log(L_INFO, "This item is not valid, we cannot keep track of it.");
return;
}
if (list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name) != -1) {
return;
}
if (!dbus_bus_name_has_owner(connection, name, &error)) {
return;
}
list_add(hosts, strdup(name));
host_registered_signal(connection);
}
static void get_property(DBusConnection *connection, DBusMessage *message) {
DBusError error;
char *interface;
char *property;
dbus_error_init(&error);
if (!dbus_message_get_args(message, &error,
DBUS_TYPE_STRING, &interface,
DBUS_TYPE_STRING, &property,
DBUS_TYPE_INVALID)) {
sway_log(L_ERROR, "Error parsing prop args: %s\n", error.message);
return;
}
if (strcmp(property, "RegisteredStatusNotifierItems") == 0) {
sway_log(L_INFO, "Replying with items\n");
DBusMessage *reply;
reply = dbus_message_new_method_return(message);
DBusMessageIter iter;
DBusMessageIter sub;
DBusMessageIter subsub;
dbus_message_iter_init_append(reply, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
"as", &sub);
dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY,
"s", &subsub);
for (int i = 0; i < items->length; ++i) {
dbus_message_iter_append_basic(&subsub,
DBUS_TYPE_STRING, &items->items[i]);
}
dbus_message_iter_close_container(&sub, &subsub);
dbus_message_iter_close_container(&iter, &sub);
dbus_connection_send(connection, reply, NULL);
dbus_message_unref(reply);
} else if (strcmp(property, "IsStatusNotifierHostRegistered") == 0) {
DBusMessage *reply;
DBusMessageIter iter;
DBusMessageIter sub;
int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1;
reply = dbus_message_new_method_return(message);
dbus_message_iter_init_append(reply, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
"b", &sub);
dbus_message_iter_append_basic(&sub,
DBUS_TYPE_BOOLEAN, &registered);
dbus_message_iter_close_container(&iter, &sub);
dbus_connection_send(connection, reply, NULL);
dbus_message_unref(reply);
} else if (strcmp(property, "ProtocolVersion") == 0) {
DBusMessage *reply;
DBusMessageIter iter;
DBusMessageIter sub;
const int version = 0;
reply = dbus_message_new_method_return(message);
dbus_message_iter_init_append(reply, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
"i", &sub);
dbus_message_iter_append_basic(&sub,
DBUS_TYPE_INT32, &version);
dbus_message_iter_close_container(&iter, &sub);
dbus_connection_send(connection, reply, NULL);
dbus_message_unref(reply);
}
}
static void set_property(DBusConnection *connection, DBusMessage *message) {
// All properties are read only and we don't allow new properties
return;
}
static void get_all(DBusConnection *connection, DBusMessage *message) {
DBusMessage *reply;
reply = dbus_message_new_method_return(message);
DBusMessageIter iter; /* a{v} */
DBusMessageIter arr;
DBusMessageIter dict;
DBusMessageIter sub;
DBusMessageIter subsub;
int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1;
const int version = 0;
const char *prop;
// Could clean this up with a function for each prop
dbus_message_iter_init_append(reply, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
"{sv}", &arr);
prop = "RegisteredStatusNotifierItems";
dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
NULL, &dict);
dbus_message_iter_append_basic(&dict,
DBUS_TYPE_STRING, &prop);
dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
"as", &sub);
dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY,
"s", &subsub);
for (int i = 0; i < items->length; ++i) {
dbus_message_iter_append_basic(&subsub,
DBUS_TYPE_STRING, &items->items[i]);
}
dbus_message_iter_close_container(&sub, &subsub);
dbus_message_iter_close_container(&dict, &sub);
dbus_message_iter_close_container(&arr, &dict);
prop = "IsStatusNotifierHostRegistered";
dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
NULL, &dict);
dbus_message_iter_append_basic(&dict,
DBUS_TYPE_STRING, &prop);
dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
"b", &sub);
dbus_message_iter_append_basic(&sub,
DBUS_TYPE_BOOLEAN, &registered);
dbus_message_iter_close_container(&dict, &sub);
dbus_message_iter_close_container(&arr, &dict);
prop = "ProtocolVersion";
dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
NULL, &dict);
dbus_message_iter_append_basic(&dict,
DBUS_TYPE_STRING, &prop);
dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
"i", &sub);
dbus_message_iter_append_basic(&sub,
DBUS_TYPE_INT32, &version);
dbus_message_iter_close_container(&dict, &sub);
dbus_message_iter_close_container(&arr, &dict);
dbus_message_iter_close_container(&iter, &arr);
dbus_connection_send(connection, reply, NULL);
dbus_message_unref(reply);
}
static DBusHandlerResult message_handler(DBusConnection *connection,
DBusMessage *message, void *data) {
const char *interface_name = dbus_message_get_interface(message);
const char *member_name = dbus_message_get_member(message);
// In order of the xml above
if (strcmp(interface_name, "org.freedesktop.DBus.Introspectable") == 0 &&
strcmp(member_name, "Introspect") == 0) {
// We don't have an introspect for KDE
respond_to_introspect(connection, message);
return DBUS_HANDLER_RESULT_HANDLED;
} else if (strcmp(interface_name, "org.freedesktop.DBus.Properties") == 0) {
if (strcmp(member_name, "Get") == 0) {
get_property(connection, message);
return DBUS_HANDLER_RESULT_HANDLED;
} else if (strcmp(member_name, "Set") == 0) {
set_property(connection, message);
return DBUS_HANDLER_RESULT_HANDLED;
} else if (strcmp(member_name, "GetAll") == 0) {
get_all(connection, message);
return DBUS_HANDLER_RESULT_HANDLED;
} else {
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
} else if (strcmp(interface_name, "org.freedesktop.StatusNotifierWatcher") == 0 ||
strcmp(interface_name, "org.kde.StatusNotifierWatcher") == 0) {
if (strcmp(member_name, "RegisterStatusNotifierItem") == 0) {
register_item(connection, message);
return DBUS_HANDLER_RESULT_HANDLED;
} else if (strcmp(member_name, "RegisterStatusNotifierHost") == 0) {
register_host(connection, message);
return DBUS_HANDLER_RESULT_HANDLED;
} else {
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static DBusHandlerResult signal_handler(DBusConnection *connection,
DBusMessage *message, void *_data) {
if (dbus_message_is_signal(message, "org.freedesktop.DBus", "NameOwnerChanged")) {
// Only eat the message if it is name that we are watching
const char *name;
const char *old_owner;
const char *new_owner;
int index;
if (!dbus_message_get_args(message, NULL,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_STRING, &old_owner,
DBUS_TYPE_STRING, &new_owner,
DBUS_TYPE_INVALID)) {
sway_log(L_ERROR, "Error getting LostName args");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
if (strcmp(new_owner, "") != 0) {
// Name is not lost
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
if ((index = list_seq_find(items, (int (*)(const void *, const void *))strcmp, name)) != -1) {
sway_log(L_INFO, "Status Notifier Item lost %s", name);
free(items->items[index]);
list_del(items, index);
item_unregistered_signal(connection, name);
return DBUS_HANDLER_RESULT_HANDLED;
}
if ((index = list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name)) != -1) {
sway_log(L_INFO, "Status Notifier Host lost %s", name);
free(hosts->items[index]);
list_del(hosts, index);
return DBUS_HANDLER_RESULT_HANDLED;
}
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static const DBusObjectPathVTable vtable = {
.message_function = message_handler,
.unregister_function = NULL,
};
int init_sni_watcher() {
DBusError error;
dbus_error_init(&error);
if (!conn) {
sway_log(L_ERROR, "Connection is null, cannot initiate StatusNotifierWatcher");
return -1;
}
items = create_list();
hosts = create_list();
int status = dbus_bus_request_name(conn, "org.freedesktop.StatusNotifierWatcher",
DBUS_NAME_FLAG_REPLACE_EXISTING,
&error);
if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
sway_log(L_DEBUG, "Got watcher name");
} else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
sway_log(L_INFO, "Could not get watcher name, it may start later");
}
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus err getting watcher name: %s\n", error.message);
return -1;
}
status = dbus_bus_request_name(conn, "org.kde.StatusNotifierWatcher",
DBUS_NAME_FLAG_REPLACE_EXISTING,
&error);
if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
sway_log(L_DEBUG, "Got kde watcher name");
} else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
sway_log(L_INFO, "Could not get kde watcher name, it may start later");
}
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus err getting kde watcher name: %s\n", error.message);
return -1;
}
dbus_connection_try_register_object_path(conn,
"/StatusNotifierWatcher",
&vtable, NULL, &error);
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus_err: %s\n", error.message);
return -1;
}
dbus_bus_add_match(conn,
"type='signal',\
sender='org.freedesktop.DBus',\
interface='org.freedesktop.DBus',\
member='NameOwnerChanged'",
&error);
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "DBus error getting match args: %s", error.message);
}
dbus_connection_add_filter(conn, signal_handler, NULL, NULL);
return 0;
}

View file

@ -1,398 +0,0 @@
#define _XOPEN_SOURCE 700
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <dbus/dbus.h>
#include "swaybar/bar.h"
#include "swaybar/tray/tray.h"
#include "swaybar/tray/dbus.h"
#include "swaybar/tray/sni.h"
#include "swaybar/tray/sni_watcher.h"
#include "swaybar/bar.h"
#include "swaybar/config.h"
#include "list.h"
#include "log.h"
struct tray *tray;
static void register_host(char *name) {
DBusMessage *message;
message = dbus_message_new_method_call(
"org.freedesktop.StatusNotifierWatcher",
"/StatusNotifierWatcher",
"org.freedesktop.StatusNotifierWatcher",
"RegisterStatusNotifierHost");
if (!message) {
sway_log(L_ERROR, "Cannot allocate dbus method call");
return;
}
dbus_message_append_args(message,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID);
dbus_connection_send(conn, message, NULL);
dbus_message_unref(message);
}
static void get_items_reply(DBusPendingCall *pending, void *_data) {
DBusMessage *reply = dbus_pending_call_steal_reply(pending);
if (!reply) {
sway_log(L_ERROR, "Got no items reply from sni watcher");
goto bail;
}
int message_type = dbus_message_get_type(reply);
if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
char *msg;
dbus_message_get_args(reply, NULL,
DBUS_TYPE_STRING, &msg,
DBUS_TYPE_INVALID);
sway_log(L_ERROR, "Message is error: %s", msg);
goto bail;
}
DBusMessageIter iter;
DBusMessageIter variant;
DBusMessageIter array;
dbus_message_iter_init(reply, &iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
goto bail;
}
dbus_message_iter_recurse(&iter, &variant);
if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_ARRAY ||
dbus_message_iter_get_element_type(&variant) != DBUS_TYPE_STRING) {
sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
goto bail;
}
// Clear list
list_foreach(tray->items, (void (*)(void *))sni_free);
list_free(tray->items);
tray->items = create_list();
// O(n) function, could be faster dynamically reading values
int len = dbus_message_iter_get_element_count(&variant);
dbus_message_iter_recurse(&variant, &array);
for (int i = 0; i < len; i++) {
const char *name;
dbus_message_iter_get_basic(&array, &name);
struct StatusNotifierItem *item = sni_create(name);
if (item) {
sway_log(L_DEBUG, "Item registered with host: %s", name);
list_add(tray->items, item);
dirty = true;
}
}
bail:
dbus_message_unref(reply);
dbus_pending_call_unref(pending);
return;
}
static void get_items() {
DBusPendingCall *pending;
DBusMessage *message = dbus_message_new_method_call(
"org.freedesktop.StatusNotifierWatcher",
"/StatusNotifierWatcher",
"org.freedesktop.DBus.Properties",
"Get");
const char *iface = "org.freedesktop.StatusNotifierWatcher";
const char *prop = "RegisteredStatusNotifierItems";
dbus_message_append_args(message,
DBUS_TYPE_STRING, &iface,
DBUS_TYPE_STRING, &prop,
DBUS_TYPE_INVALID);
bool status =
dbus_connection_send_with_reply(conn, message, &pending, -1);
dbus_message_unref(message);
if (!(pending || status)) {
sway_log(L_ERROR, "Could not get items");
return;
}
dbus_pending_call_set_notify(pending, get_items_reply, NULL, NULL);
}
static DBusHandlerResult signal_handler(DBusConnection *connection,
DBusMessage *message, void *_data) {
if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
"StatusNotifierItemRegistered")) {
const char *name;
if (!dbus_message_get_args(message, NULL,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID)) {
sway_log(L_ERROR, "Error getting StatusNotifierItemRegistered args");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
if (list_seq_find(tray->items, sni_str_cmp, name) == -1) {
struct StatusNotifierItem *item = sni_create(name);
if (item) {
list_add(tray->items, item);
dirty = true;
}
}
return DBUS_HANDLER_RESULT_HANDLED;
} else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
"StatusNotifierItemUnregistered")) {
const char *name;
if (!dbus_message_get_args(message, NULL,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID)) {
sway_log(L_ERROR, "Error getting StatusNotifierItemUnregistered args");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
int index;
if ((index = list_seq_find(tray->items, sni_str_cmp, name)) != -1) {
sni_free(tray->items->items[index]);
list_del(tray->items, index);
dirty = true;
} else {
// If it's not in our list, then our list is incorrect.
// Fetch all items again
sway_log(L_INFO, "Host item list incorrect, refreshing");
get_items();
}
return DBUS_HANDLER_RESULT_HANDLED;
} else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierItem",
"NewIcon") || dbus_message_is_signal(message,
"org.kde.StatusNotifierItem", "NewIcon")) {
const char *name;
int index;
struct StatusNotifierItem *item;
name = dbus_message_get_sender(message);
if ((index = list_seq_find(tray->items, sni_uniq_cmp, name)) != -1) {
item = tray->items->items[index];
sway_log(L_INFO, "NewIcon signal from item %s", item->name);
get_icon(item);
}
return DBUS_HANDLER_RESULT_HANDLED;
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static int init_host() {
tray = (struct tray *)malloc(sizeof(tray));
tray->items = create_list();
DBusError error;
dbus_error_init(&error);
char *name = NULL;
if (!conn) {
sway_log(L_ERROR, "Connection is null, cannot init SNI host");
goto err;
}
name = calloc(sizeof(char), 256);
if (!name) {
sway_log(L_ERROR, "Cannot allocate name");
goto err;
}
pid_t pid = getpid();
if (snprintf(name, 256, "org.freedesktop.StatusNotifierHost-%d", pid)
>= 256) {
sway_log(L_ERROR, "Cannot get host name because string is too short."
"This should not happen");
goto err;
}
// We want to be the sole owner of this name
if (dbus_bus_request_name(conn, name, DBUS_NAME_FLAG_DO_NOT_QUEUE,
&error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
sway_log(L_ERROR, "Cannot get host name and start the tray");
goto err;
}
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "Dbus err getting host name: %s\n", error.message);
goto err;
}
sway_log(L_DEBUG, "Got host name");
register_host(name);
get_items();
// Perhaps use addmatch helper functions like wlc does?
dbus_bus_add_match(conn,
"type='signal',\
sender='org.freedesktop.StatusNotifierWatcher',\
member='StatusNotifierItemRegistered'",
&error);
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus_err: %s", error.message);
goto err;
}
dbus_bus_add_match(conn,
"type='signal',\
sender='org.freedesktop.StatusNotifierWatcher',\
member='StatusNotifierItemUnregistered'",
&error);
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus_err: %s", error.message);
return -1;
}
// SNI matches
dbus_bus_add_match(conn,
"type='signal',\
interface='org.freedesktop.StatusNotifierItem',\
member='NewIcon'",
&error);
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus_err %s", error.message);
goto err;
}
dbus_bus_add_match(conn,
"type='signal',\
interface='org.kde.StatusNotifierItem',\
member='NewIcon'",
&error);
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus_err %s", error.message);
goto err;
}
dbus_connection_add_filter(conn, signal_handler, NULL, NULL);
free(name);
return 0;
err:
// TODO better handle errors
free(name);
return -1;
}
void tray_mouse_event(struct output *output, int x, int y,
uint32_t button, uint32_t state) {
struct window *window = output->window;
uint32_t tray_padding = swaybar.config->tray_padding;
int tray_width = window->width * window->scale;
for (int i = 0; i < output->items->length; ++i) {
struct sni_icon_ref *item =
output->items->items[i];
int icon_width = cairo_image_surface_get_width(item->icon);
tray_width -= tray_padding;
if (x <= tray_width && x >= tray_width - icon_width) {
if (button == swaybar.config->activate_button) {
sni_activate(item->ref, x, y);
} else if (button == swaybar.config->context_button) {
sni_context_menu(item->ref, x, y);
} else if (button == swaybar.config->secondary_button) {
sni_secondary(item->ref, x, y);
}
break;
}
tray_width -= icon_width;
}
}
uint32_t tray_render(struct output *output, struct config *config) {
struct window *window = output->window;
cairo_t *cairo = window->cairo;
// Tray icons
uint32_t tray_padding = config->tray_padding;
uint32_t tray_width = window->width * window->scale;
const int item_size = (window->height * window->scale) - (2 * tray_padding);
if (item_size < 0) {
// Can't render items if the padding is too large
return tray_width;
}
if (config->tray_output && strcmp(config->tray_output, output->name) != 0) {
return tray_width;
}
for (int i = 0; i < tray->items->length; ++i) {
struct StatusNotifierItem *item =
tray->items->items[i];
if (!item->image) {
continue;
}
struct sni_icon_ref *render_item = NULL;
int j;
for (j = i; j < output->items->length; ++j) {
struct sni_icon_ref *ref =
output->items->items[j];
if (ref->ref == item) {
render_item = ref;
break;
} else {
sni_icon_ref_free(ref);
list_del(output->items, j);
}
}
if (!render_item) {
render_item = sni_icon_ref_create(item, item_size);
list_add(output->items, render_item);
} else if (item->dirty) {
// item needs re-render
sni_icon_ref_free(render_item);
output->items->items[j] = render_item =
sni_icon_ref_create(item, item_size);
}
tray_width -= tray_padding;
tray_width -= item_size;
cairo_operator_t op = cairo_get_operator(cairo);
cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
cairo_set_source_surface(cairo, render_item->icon, tray_width, tray_padding);
cairo_rectangle(cairo, tray_width, tray_padding, item_size, item_size);
cairo_fill(cairo);
cairo_set_operator(cairo, op);
item->dirty = false;
}
if (tray_width != window->width * window->scale) {
tray_width -= tray_padding;
}
return tray_width;
}
void init_tray(struct bar *bar) {
if (!bar->config->tray_output || strcmp(bar->config->tray_output, "none") != 0) {
/* Connect to the D-Bus */
dbus_init();
/* Start the SNI watcher */
init_sni_watcher();
/* Start the SNI host */
init_host();
}
}