Merge pull request #3249 from ianyfan/tray

Swaybar tray
This commit is contained in:
Drew DeVault 2018-12-31 15:43:23 -05:00 committed by GitHub
commit 3d7c20f062
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 1965 additions and 165 deletions

View file

@ -182,11 +182,9 @@ sway_cmd cmd_workspace;
sway_cmd cmd_ws_auto_back_and_forth;
sway_cmd cmd_workspace_layout;
sway_cmd bar_cmd_activate_button;
sway_cmd bar_cmd_binding_mode_indicator;
sway_cmd bar_cmd_bindsym;
sway_cmd bar_cmd_colors;
sway_cmd bar_cmd_context_button;
sway_cmd bar_cmd_font;
sway_cmd bar_cmd_gaps;
sway_cmd bar_cmd_mode;
@ -197,13 +195,13 @@ sway_cmd bar_cmd_hidden_state;
sway_cmd bar_cmd_icon_theme;
sway_cmd bar_cmd_id;
sway_cmd bar_cmd_position;
sway_cmd bar_cmd_secondary_button;
sway_cmd bar_cmd_separator_symbol;
sway_cmd bar_cmd_status_command;
sway_cmd bar_cmd_pango_markup;
sway_cmd bar_cmd_strip_workspace_numbers;
sway_cmd bar_cmd_strip_workspace_name;
sway_cmd bar_cmd_swaybar_command;
sway_cmd bar_cmd_tray_bindsym;
sway_cmd bar_cmd_tray_output;
sway_cmd bar_cmd_tray_padding;
sway_cmd bar_cmd_wrap_scroll;

View file

@ -6,6 +6,7 @@
#include <time.h>
#include <wlr/types/wlr_box.h>
#include <xkbcommon/xkbcommon.h>
#include "../include/config.h"
#include "list.h"
#include "swaynag.h"
#include "tree/container.h"
@ -253,6 +254,13 @@ struct bar_config {
char *binding_mode_bg;
char *binding_mode_text;
} colors;
#if HAVE_TRAY
char *icon_theme;
const char *tray_bindings[10]; // mouse buttons 0-9
list_t *tray_outputs; // char *
int tray_padding;
#endif
};
struct bar_binding {

View file

@ -1,6 +1,7 @@
#ifndef _SWAYBAR_BAR_H
#define _SWAYBAR_BAR_H
#include <wayland-client.h>
#include "config.h"
#include "input.h"
#include "pool-buffer.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
@ -8,6 +9,9 @@
struct swaybar_config;
struct swaybar_output;
#if HAVE_TRAY
struct swaybar_tray;
#endif
struct swaybar_workspace;
struct loop;
@ -38,6 +42,10 @@ struct swaybar {
int ipc_socketfd;
struct wl_list outputs; // swaybar_output::link
#if HAVE_TRAY
struct swaybar_tray *tray;
#endif
};
struct swaybar_output {
@ -62,6 +70,8 @@ struct swaybar_output {
struct pool_buffer *current_buffer;
bool dirty;
bool frame_scheduled;
uint32_t output_height, output_width, output_x, output_y;
};
struct swaybar_workspace {
@ -78,6 +88,8 @@ bool bar_setup(struct swaybar *bar, const char *socket_path);
void bar_run(struct swaybar *bar);
void bar_teardown(struct swaybar *bar);
void set_bar_dirty(struct swaybar *bar);
/*
* Determines whether the bar should be visible and changes it to be so.
* If the current visibility of the bar is the different to what it should be,

View file

@ -3,6 +3,7 @@
#include <stdbool.h>
#include <stdint.h>
#include <wayland-client.h>
#include "../include/config.h"
#include "list.h"
#include "util.h"
@ -64,6 +65,14 @@ struct swaybar_config {
struct box_colors urgent_workspace;
struct box_colors binding_mode;
} colors;
#if HAVE_TRAY
char *icon_theme;
char *tray_bindings[10]; // mouse buttons 0-9
bool tray_hidden;
list_t *tray_outputs; // char *
int tray_padding;
#endif
};
struct swaybar_config *init_config(void);

View file

@ -0,0 +1,17 @@
#ifndef _SWAYBAR_TRAY_HOST_H
#define _SWAYBAR_TRAY_HOST_H
#include <stdbool.h>
struct swaybar_tray;
struct swaybar_host {
struct swaybar_tray *tray;
char *service;
char *watcher_interface;
};
bool init_host(struct swaybar_host *host, char *protocol, struct swaybar_tray *tray);
void finish_host(struct swaybar_host *host);
#endif

View file

@ -1,16 +1,44 @@
#ifndef _SWAYBAR_ICON_H
#define _SWAYBAR_ICON_H
#ifndef _SWAYBAR_TRAY_ICON_H
#define _SWAYBAR_TRAY_ICON_H
#include <stdint.h>
#include <stdbool.h>
#include <client/cairo.h>
#include "list.h"
/**
* Returns the image found by `name` that is closest to `size`
enum subdir_type {
THRESHOLD,
SCALABLE,
FIXED
};
struct icon_theme_subdir {
char *name;
int size;
enum subdir_type type;
int max_size;
int min_size;
int threshold;
};
struct icon_theme {
char *name;
char *comment;
char *inherits;
list_t *directories; // char *
char *dir;
list_t *subdirs; // struct icon_theme_subdir *
};
void init_themes(list_t **themes, list_t **basedirs);
void finish_themes(list_t *themes, list_t *basedirs);
/*
* Finds an icon of a specified size given a list of themes and base directories.
* If the icon is found, the pointers min_size & max_size are set to minimum &
* maximum size that the icon can be scaled to, respectively.
* Returns: path of icon (which should be freed), or NULL if the icon is not found.
*/
cairo_surface_t *find_icon(const char *name, int size);
char *find_icon(list_t *themes, list_t *basedirs, char *name, int size,
char *theme, int *min_size, int *max_size);
char *find_icon_in_dir(char *name, char *dir, int *min_size, int *max_size);
/* Struct used internally only */
struct subdir;
#endif /* _SWAYBAR_ICON_H */
#endif

View file

@ -0,0 +1,45 @@
#ifndef _SWAYBAR_TRAY_ITEM_H
#define _SWAYBAR_TRAY_ITEM_H
#include <cairo.h>
#include <stdbool.h>
#include <stdint.h>
#include "swaybar/tray/tray.h"
#include "list.h"
struct swaybar_output;
struct swaybar_pixmap {
int size;
unsigned char pixels[];
};
struct swaybar_sni {
// icon properties
struct swaybar_tray *tray;
cairo_surface_t *icon;
int min_size;
int max_size;
// dbus properties
char *watcher_id;
char *service;
char *path;
char *interface;
char *status;
char *icon_name;
list_t *icon_pixmap; // struct swaybar_pixmap *
char *attention_icon_name;
list_t *attention_icon_pixmap; // struct swaybar_pixmap *
bool item_is_menu;
char *menu;
char *icon_theme_path; // non-standard KDE property
};
struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray);
void destroy_sni(struct swaybar_sni *sni);
uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x,
struct swaybar_sni *sni);
#endif

View file

@ -1,82 +0,0 @@
#ifndef _SWAYBAR_SNI_H
#define _SWAYBAR_SNI_H
#include <stdbool.h>
#include <client/cairo.h>
struct StatusNotifierItem {
/* Name registered to sni watcher */
char *name;
/* Unique bus name, needed for determining signal origins */
char *unique_name;
bool kde_special_snowflake;
cairo_surface_t *image;
bool dirty;
};
/* Each output holds an sni_icon_ref of each item to render */
struct sni_icon_ref {
cairo_surface_t *icon;
struct StatusNotifierItem *ref;
};
struct sni_icon_ref *sni_icon_ref_create(struct StatusNotifierItem *item,
int height);
void sni_icon_ref_free(struct sni_icon_ref *sni_ref);
/**
* Will return a new item and get its icon. (see warning below)
* May return `NULL` if `name` is not valid.
*/
struct StatusNotifierItem *sni_create(const char *name);
/**
* `item` must be a struct StatusNotifierItem *
* `str` must be a NUL terminated char *
*
* Returns 0 if `item` has a name of `str`
*/
int sni_str_cmp(const void *item, const void *str);
/**
* Returns 0 if `item` has a unique name of `str` or if
* `item->unique_name == NULL`
*/
int sni_uniq_cmp(const void *item, const void *str);
/**
* Gets an icon for the given item if found.
*
* XXX
* This function keeps a reference to the item until it gets responses, make
* sure that the reference and item are valid during this time.
*/
void get_icon(struct StatusNotifierItem *item);
/**
* Calls the "activate" method on the given StatusNotifierItem
*
* x and y should be where the item was clicked
*/
void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y);
/**
* Asks the item to draw a context menu at the given x and y coords
*/
void sni_context_menu(struct StatusNotifierItem *item, uint32_t x, uint32_t y);
/**
* Calls the "secondary activate" method on the given StatusNotifierItem
*
* x and y should be where the item was clicked
*/
void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y);
/**
* Deconstructs `item`
*/
void sni_free(struct StatusNotifierItem *item);
#endif /* _SWAYBAR_SNI_H */

View file

@ -0,0 +1,40 @@
#ifndef _SWAYBAR_TRAY_TRAY_H
#define _SWAYBAR_TRAY_TRAY_H
#include "config.h"
#ifdef HAVE_SYSTEMD
#include <systemd/sd-bus.h>
#elif HAVE_ELOGIND
#include <elogind/sd-bus.h>
#endif
#include <cairo.h>
#include <stdint.h>
#include "swaybar/tray/host.h"
#include "list.h"
struct swaybar;
struct swaybar_output;
struct swaybar_watcher;
struct swaybar_tray {
struct swaybar *bar;
int fd;
sd_bus *bus;
struct swaybar_host host_xdg;
struct swaybar_host host_kde;
list_t *items; // struct swaybar_sni *
struct swaybar_watcher *watcher_xdg;
struct swaybar_watcher *watcher_kde;
list_t *basedirs; // char *
list_t *themes; // struct swaybar_theme *
};
struct swaybar_tray *create_tray(struct swaybar *bar);
void destroy_tray(struct swaybar_tray *tray);
void tray_in(int fd, short mask, void *data);
uint32_t render_tray(cairo_t *cairo, struct swaybar_output *output, double *x);
#endif

View file

@ -0,0 +1,18 @@
#ifndef _SWAYBAR_TRAY_WATCHER_H
#define _SWAYBAR_TRAY_WATCHER_H
#include "swaybar/tray/tray.h"
#include "list.h"
struct swaybar_watcher {
char *interface;
sd_bus *bus;
list_t *hosts;
list_t *items;
int version;
};
struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus);
void destroy_watcher(struct swaybar_watcher *watcher);
#endif

View file

@ -66,6 +66,7 @@ endif
conf_data.set10('HAVE_GDK_PIXBUF', gdk_pixbuf.found())
conf_data.set10('HAVE_SYSTEMD', systemd.found())
conf_data.set10('HAVE_ELOGIND', elogind.found())
conf_data.set10('HAVE_TRAY', get_option('enable-tray') and (systemd.found() or elogind.found()))
if not systemd.found() and not elogind.found()
warning('The sway binary must be setuid when compiled without (e)logind')

View file

@ -6,3 +6,4 @@ option('zsh-completions', type: 'boolean', value: true, description: 'Install zs
option('bash-completions', type: 'boolean', value: true, description: 'Install bash shell completions.')
option('fish-completions', type: 'boolean', value: true, description: 'Install fish shell completions.')
option('enable-xwayland', type: 'boolean', value: true, description: 'Enable support for X11 applications')
option('enable-tray', type: 'boolean', value: false, description: 'Enable support for swaybar tray')

View file

@ -8,11 +8,9 @@
// Must be in alphabetical order for bsearch
static struct cmd_handler bar_handlers[] = {
{ "activate_button", bar_cmd_activate_button },
{ "binding_mode_indicator", bar_cmd_binding_mode_indicator },
{ "bindsym", bar_cmd_bindsym },
{ "colors", bar_cmd_colors },
{ "context_button", bar_cmd_context_button },
{ "font", bar_cmd_font },
{ "gaps", bar_cmd_gaps },
{ "height", bar_cmd_height },
@ -23,11 +21,11 @@ static struct cmd_handler bar_handlers[] = {
{ "output", bar_cmd_output },
{ "pango_markup", bar_cmd_pango_markup },
{ "position", bar_cmd_position },
{ "secondary_button", bar_cmd_secondary_button },
{ "separator_symbol", bar_cmd_separator_symbol },
{ "status_command", bar_cmd_status_command },
{ "strip_workspace_name", bar_cmd_strip_workspace_name },
{ "strip_workspace_numbers", bar_cmd_strip_workspace_numbers },
{ "tray_bindsym", bar_cmd_tray_bindsym },
{ "tray_output", bar_cmd_tray_output },
{ "tray_padding", bar_cmd_tray_padding },
{ "workspace_buttons", bar_cmd_workspace_buttons },

View file

@ -1,8 +0,0 @@
#include <stdlib.h>
#include "sway/commands.h"
#include "log.h"
struct cmd_results *bar_cmd_activate_button(int argc, char **argv) {
// TODO TRAY
return cmd_results_new(CMD_INVALID, "activate_button", "TODO TRAY");
}

View file

@ -1,8 +0,0 @@
#include <stdlib.h>
#include "sway/commands.h"
#include "log.h"
struct cmd_results *bar_cmd_context_button(int argc, char **argv) {
// TODO TRAY
return cmd_results_new(CMD_INVALID, "context_button", "TODO TRAY");
}

View file

@ -1,7 +1,28 @@
#define _POSIX_C_SOURCE 200809L
#include <string.h>
#include "config.h"
#include "sway/commands.h"
#include "sway/config.h"
#include "log.h"
struct cmd_results *bar_cmd_icon_theme(int argc, char **argv) {
// TODO TRAY
return cmd_results_new(CMD_INVALID, "icon_theme", "TODO TRAY");
#if HAVE_TRAY
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "icon_theme", EXPECTED_EQUAL_TO, 1))) {
return error;
}
if (!config->current_bar) {
return cmd_results_new(CMD_FAILURE, "tray_padding", "No bar defined.");
}
wlr_log(WLR_DEBUG, "[Bar %s] Setting icon theme to %s",
config->current_bar->id, argv[0]);
free(config->current_bar->icon_theme);
config->current_bar->icon_theme = strdup(argv[0]);
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
#else
return cmd_results_new(CMD_INVALID, "icon_theme",
"Sway has been compiled without tray support");
#endif
}

View file

@ -1,8 +0,0 @@
#include <stdlib.h>
#include "sway/commands.h"
#include "log.h"
struct cmd_results *bar_cmd_secondary_button(int argc, char **argv) {
// TODO TRAY
return cmd_results_new(CMD_INVALID, "secondary_button", "TODO TRAY");
}

View file

@ -0,0 +1,55 @@
#include <strings.h>
#include "config.h"
#include "sway/commands.h"
#include "sway/config.h"
#include "log.h"
struct cmd_results *bar_cmd_tray_bindsym(int argc, char **argv) {
#if HAVE_TRAY
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "tray_bindsym", EXPECTED_EQUAL_TO, 2))) {
return error;
}
if (!config->current_bar) {
return cmd_results_new(CMD_FAILURE, "tray_bindsym", "No bar defined.");
}
int button = 0;
if (strncasecmp(argv[0], "button", strlen("button")) == 0 &&
strlen(argv[0]) == strlen("button0")) {
button = argv[0][strlen("button")] - '0';
}
if (button < 1 || button > 9) {
return cmd_results_new(CMD_FAILURE, "tray_bindsym",
"[Bar %s] Only buttons 1 to 9 are supported",
config->current_bar->id);
}
static const char *commands[] = {
"ContextMenu",
"Activate",
"SecondaryActivate",
"ScrollDown",
"ScrollLeft",
"ScrollRight",
"ScrollUp",
"nop"
};
for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); ++i) {
if (strcasecmp(argv[1], commands[i]) == 0) {
wlr_log(WLR_DEBUG, "[Bar %s] Binding button %d to %s",
config->current_bar->id, button, commands[i]);
config->current_bar->tray_bindings[button] = commands[i];
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
}
}
return cmd_results_new(CMD_INVALID, "tray_bindsym",
"[Bar %s] Invalid command %s", config->current_bar->id, argv[1]);
#else
return cmd_results_new(CMD_INVALID, "tray_bindsym",
"Sway has been compiled without tray support");
#endif
}

View file

@ -1,7 +1,42 @@
#define _POSIX_C_SOURCE 200809L
#include <string.h>
#include "config.h"
#include "sway/commands.h"
#include "sway/config.h"
#include "list.h"
#include "log.h"
struct cmd_results *bar_cmd_tray_output(int argc, char **argv) {
// TODO TRAY
return cmd_results_new(CMD_INVALID, "tray_output", "TODO TRAY");
#if HAVE_TRAY
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "tray_output", EXPECTED_EQUAL_TO, 1))) {
return error;
}
if (!config->current_bar) {
return cmd_results_new(CMD_FAILURE, "tray_output", "No bar defined.");
}
list_t *outputs = config->current_bar->tray_outputs;
if (!outputs) {
config->current_bar->tray_outputs = outputs = create_list();
}
if (strcmp(argv[0], "none") == 0) {
wlr_log(WLR_DEBUG, "Hiding tray on bar: %s", config->current_bar->id);
for (int i = 0; i < outputs->length; ++i) {
free(outputs->items[i]);
}
outputs->length = 0;
} else {
wlr_log(WLR_DEBUG, "Showing tray on output '%s' for bar: %s", argv[0],
config->current_bar->id);
}
list_add(outputs, strdup(argv[0]));
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
#else
return cmd_results_new(CMD_INVALID, "tray_output",
"Sway has been compiled without tray support");
#endif
}

View file

@ -1,9 +1,42 @@
#include <stdlib.h>
#include <strings.h>
#include "config.h"
#include "sway/commands.h"
#include "sway/config.h"
#include "log.h"
struct cmd_results *bar_cmd_tray_padding(int argc, char **argv) {
// TODO TRAY
return cmd_results_new(CMD_INVALID, "tray_padding", "TODO TRAY");
#if HAVE_TRAY
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "tray_padding", EXPECTED_AT_LEAST, 1))) {
return error;
}
if ((error = checkarg(argc, "tray_padding", EXPECTED_AT_MOST, 2))) {
return error;
}
if (!config->current_bar) {
return cmd_results_new(CMD_FAILURE, "tray_padding", "No bar defined.");
}
struct bar_config *bar = config->current_bar;
char *end;
int padding = strtol(argv[0], &end, 10);
if (padding < 0 || (*end != '\0' && strcasecmp(end, "px") != 0)) {
return cmd_results_new(CMD_INVALID, "tray_padding",
"[Bar %s] Invalid tray padding value: %s", bar->id, argv[0]);
}
if (argc == 2 && strcasecmp(argv[1], "px") != 0) {
return cmd_results_new(CMD_INVALID, "tray_padding",
"Expected 'tray_padding <px> [px]'");
}
wlr_log(WLR_DEBUG, "[Bar %s] Setting tray padding to %d", bar->id, padding);
config->current_bar->tray_padding = padding;
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
#else
return cmd_results_new(CMD_INVALID, "tray_padding",
"Sway has been compiled without tray support");
#endif
}

View file

@ -12,6 +12,7 @@
#include <signal.h>
#include "sway/config.h"
#include "sway/output.h"
#include "config.h"
#include "stringop.h"
#include "list.h"
#include "log.h"
@ -77,6 +78,10 @@ void free_bar_config(struct bar_config *bar) {
free(bar->colors.binding_mode_border);
free(bar->colors.binding_mode_bg);
free(bar->colors.binding_mode_text);
#if HAVE_TRAY
list_free_items_and_destroy(bar->tray_outputs);
free(bar->icon_theme);
#endif
free(bar);
}
@ -165,6 +170,10 @@ struct bar_config *default_bar_config(void) {
bar->colors.binding_mode_bg = NULL;
bar->colors.binding_mode_text = NULL;
#if HAVE_TRAY
bar->tray_padding = 2;
#endif
list_add(config->bars, bar);
return bar;
cleanup:

View file

@ -1,6 +1,7 @@
#include <json-c/json.h>
#include <stdio.h>
#include <ctype.h>
#include "config.h"
#include "log.h"
#include "sway/config.h"
#include "sway/ipc-json.h"
@ -785,5 +786,41 @@ json_object *ipc_json_describe_bar_config(struct bar_config *bar) {
}
json_object_object_add(json, "outputs", outputs);
}
#if HAVE_TRAY
// Add tray outputs if defined
if (bar->tray_outputs && bar->tray_outputs->length > 0) {
json_object *tray_outputs = json_object_new_array();
for (int i = 0; i < bar->tray_outputs->length; ++i) {
const char *name = bar->tray_outputs->items[i];
json_object_array_add(tray_outputs, json_object_new_string(name));
}
json_object_object_add(json, "tray_outputs", tray_outputs);
}
json_object *tray_bindings = json_object_new_array();
for (int i = 0; i < 10; ++i) {
if (bar->tray_bindings[i]) {
json_object *bind = json_object_new_object();
json_object_object_add(bind, "input_code",
json_object_new_int(i));
json_object_object_add(bind, "command",
json_object_new_string(bar->tray_bindings[i]));
json_object_array_add(tray_bindings, bind);
}
}
if (json_object_array_length(tray_bindings) > 0) {
json_object_object_add(json, "tray_bindings", tray_bindings);
} else {
json_object_put(tray_bindings);
}
if (bar->icon_theme) {
json_object_object_add(json, "icon_theme",
json_object_new_string(bar->icon_theme));
}
json_object_object_add(json, "tray_padding",
json_object_new_int(bar->tray_padding));
#endif
return json;
}

View file

@ -99,11 +99,9 @@ sway_sources = files(
'commands/workspace_layout.c',
'commands/ws_auto_back_and_forth.c',
'commands/bar/activate_button.c',
'commands/bar/binding_mode_indicator.c',
'commands/bar/bindsym.c',
'commands/bar/colors.c',
'commands/bar/context_button.c',
'commands/bar/font.c',
'commands/bar/gaps.c',
'commands/bar/height.c',
@ -115,12 +113,12 @@ sway_sources = files(
'commands/bar/output.c',
'commands/bar/pango_markup.c',
'commands/bar/position.c',
'commands/bar/secondary_button.c',
'commands/bar/separator_symbol.c',
'commands/bar/status_command.c',
'commands/bar/strip_workspace_numbers.c',
'commands/bar/strip_workspace_name.c',
'commands/bar/swaybar_command.c',
'commands/bar/tray_bindsym.c',
'commands/bar/tray_output.c',
'commands/bar/tray_padding.c',
'commands/bar/workspace_buttons.c',

View file

@ -100,27 +100,20 @@ The following commands configure the tray.
The _button_ argument in all cases is a platform-specific button code. On Linux
you can find a list of these at linux/input-event-codes.h.
*activate\_button* <button>
Sets the button to be used for the _activate_ (primary click) tray item
event. The default is BTN\_LEFT (0x110).
*context\_button* <button>
Sets the button to be used for the _context menu_ (right click) tray item
event. The default is BTN\_RIGHT (0x111).
*secondary\_button* <button>
Sets the button to be used for the _secondary_ (middle click) tray item
event. The default is BTN\_MIDDLE (0x112).
*tray\_output* none|all|<output>
Sets the output that the tray will appear on or none. Unlike i3bar, swaybar
is able to show icons on any number of bars and outputs without races.
The default is _all_.
*tray\_bindsym* button<n> ContextMenu|Activate|SecondaryActivate|ScrollDown|ScrollLeft|ScrollRight|ScrollUp|nop
Binds mouse button _n_ (1 to 9) to the specified action. Use the command
_nop_ to disable the default action (Activate for button 1, ContextMenu for
button 2 and SecondaryActivate for button 3).
*tray\_padding* <px> [px]
Sets the pixel padding of the system tray. This padding will surround the
tray on all sides and between each item. The default value for _px_ is 2.
*tray\_output* none|<output>
Restrict the tray to a certain output, can be specified multiple times. If
omitted, the tray will be displayed on all outputs. Unlike i3bar, swaybar
can show icons on any number of bars and outputs without races.
*icon\_theme* <name>
Sets the icon theme that sway will look for item icons in. This option has
no default value, because sway will always default to the fallback theme,

View file

@ -11,6 +11,7 @@
#include <wayland-client.h>
#include <wayland-cursor.h>
#include <wlr/util/log.h>
#include "config.h"
#include "swaybar/bar.h"
#include "swaybar/config.h"
#include "swaybar/i3bar.h"
@ -18,6 +19,9 @@
#include "swaybar/ipc.h"
#include "swaybar/status_line.h"
#include "swaybar/render.h"
#if HAVE_TRAY
#include "swaybar/tray/tray.h"
#endif
#include "ipc-client.h"
#include "list.h"
#include "log.h"
@ -120,7 +124,7 @@ static void destroy_layer_surface(struct swaybar_output *output) {
output->frame_scheduled = false;
}
static void set_bar_dirty(struct swaybar *bar) {
void set_bar_dirty(struct swaybar *bar) {
struct swaybar_output *output;
wl_list_for_each(output, &bar->outputs, link) {
set_output_dirty(output);
@ -211,12 +215,16 @@ struct wl_output_listener output_listener = {
static void xdg_output_handle_logical_position(void *data,
struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) {
// Who cares
struct swaybar_output *output = data;
output->output_x = x;
output->output_y = y;
}
static void xdg_output_handle_logical_size(void *data,
struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) {
// Who cares
struct swaybar_output *output = data;
output->output_height = height;
output->output_width = width;
}
static void xdg_output_handle_done(void *data,
@ -362,6 +370,12 @@ bool bar_setup(struct swaybar *bar, const char *socket_path) {
pointer->cursor_surface = wl_compositor_create_surface(bar->compositor);
assert(pointer->cursor_surface);
#if HAVE_TRAY
if (!bar->config->tray_hidden) {
bar->tray = create_tray(bar);
}
#endif
if (bar->config->workspace_buttons) {
ipc_get_workspaces(bar);
}
@ -403,6 +417,11 @@ void bar_run(struct swaybar *bar) {
loop_add_fd(bar->eventloop, bar->status->read_fd, POLLIN,
status_in, bar);
}
#if HAVE_TRAY
if (bar->tray) {
loop_add_fd(bar->eventloop, bar->tray->fd, POLLIN, tray_in, bar->tray->bus);
}
#endif
while (1) {
errno = 0;
if (wl_display_flush(bar->display) == -1 && errno != EAGAIN) {
@ -420,6 +439,9 @@ static void free_outputs(struct wl_list *list) {
}
void bar_teardown(struct swaybar *bar) {
#if HAVE_TRAY
destroy_tray(bar->tray);
#endif
free_outputs(&bar->outputs);
if (bar->config) {
free_config(bar->config);

View file

@ -4,6 +4,7 @@
#include <wlr/util/log.h>
#include "swaybar/config.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include "config.h"
#include "stringop.h"
#include "list.h"
@ -73,6 +74,10 @@ struct swaybar_config *init_config(void) {
config->colors.binding_mode.background = 0x900000FF;
config->colors.binding_mode.text = 0xFFFFFFFF;
#if HAVE_TRAY
config->tray_padding = 2;
#endif
return config;
}
@ -102,5 +107,12 @@ void free_config(struct swaybar_config *config) {
free(coutput->name);
free(coutput);
}
#if HAVE_TRAY
list_free_items_and_destroy(config->tray_outputs);
for (int i = 0; i < 10; ++i) {
free(config->tray_bindings[i]);
}
free(config->icon_theme);
#endif
free(config);
}

View file

@ -6,6 +6,7 @@
#include <wlr/util/log.h>
#include "swaybar/config.h"
#include "swaybar/ipc.h"
#include "config.h"
#include "ipc-client.h"
#include "list.h"
@ -282,6 +283,40 @@ static bool ipc_parse_config(
ipc_parse_colors(config, colors);
}
#if HAVE_TRAY
json_object *tray_outputs, *tray_padding, *tray_bindings, *icon_theme;
if ((json_object_object_get_ex(bar_config, "tray_outputs", &tray_outputs))) {
config->tray_outputs = create_list();
int length = json_object_array_length(tray_outputs);
for (int i = 0; i < length; ++i) {
json_object *o = json_object_array_get_idx(tray_outputs, i);
list_add(config->tray_outputs, strdup(json_object_get_string(o)));
}
config->tray_hidden = strcmp(config->tray_outputs->items[0], "none") == 0;
}
if ((json_object_object_get_ex(bar_config, "tray_padding", &tray_padding))) {
config->tray_padding = json_object_get_int(tray_padding);
}
if ((json_object_object_get_ex(bar_config, "tray_bindings", &tray_bindings))) {
int length = json_object_array_length(tray_bindings);
for (int i = 0; i < length; ++i) {
json_object *bind = json_object_array_get_idx(tray_bindings, i);
json_object *button, *command;
json_object_object_get_ex(bind, "input_code", &button);
json_object_object_get_ex(bind, "command", &command);
config->tray_bindings[json_object_get_int(button)] =
strdup(json_object_get_string(command));
}
}
if ((json_object_object_get_ex(bar_config, "icon_theme", &icon_theme))) {
config->icon_theme = strdup(json_object_get_string(icon_theme));
}
#endif
json_object_put(bar_config);
return true;
}

View file

@ -1,3 +1,32 @@
tray_files = get_option('enable-tray') ? [
'tray/host.c',
'tray/icon.c',
'tray/item.c',
'tray/tray.c',
'tray/watcher.c'
] : []
swaybar_deps = [
cairo,
client_protos,
gdk_pixbuf,
jsonc,
math,
pango,
pangocairo,
rt,
wayland_client,
wayland_cursor,
wlroots,
]
if get_option('enable-tray')
if systemd.found()
swaybar_deps += systemd
elif elogind.found()
swaybar_deps += elogind
endif
endif
executable(
'swaybar', [
'bar.c',
@ -8,21 +37,10 @@ executable(
'main.c',
'render.c',
'status_line.c',
tray_files
],
include_directories: [sway_inc],
dependencies: [
cairo,
client_protos,
gdk_pixbuf,
jsonc,
math,
pango,
pangocairo,
rt,
wayland_client,
wayland_cursor,
wlroots,
],
dependencies: swaybar_deps,
link_with: [lib_sway_common, lib_sway_client],
install_rpath : rpathdir,
install: true

View file

@ -14,6 +14,9 @@
#include "swaybar/ipc.h"
#include "swaybar/render.h"
#include "swaybar/status_line.h"
#if HAVE_TRAY
#include "swaybar/tray/tray.h"
#endif
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
static const int WS_HORIZONTAL_PADDING = 5;
@ -453,6 +456,12 @@ static uint32_t render_to_cairo(cairo_t *cairo, struct swaybar_output *output) {
* utilize the available space.
*/
double x = output->width * output->scale;
#if HAVE_TRAY
if (bar->tray) {
uint32_t h = render_tray(cairo, output, &x);
max_height = h > max_height ? h : max_height;
}
#endif
if (bar->status) {
uint32_t h = render_status_line(cairo, output, &x);
max_height = h > max_height ? h : max_height;

210
swaybar/tray/host.c Normal file
View file

@ -0,0 +1,210 @@
#define _POSIX_C_SOURCE 200809L
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "swaybar/bar.h"
#include "swaybar/tray/host.h"
#include "swaybar/tray/item.h"
#include "swaybar/tray/tray.h"
#include "list.h"
#include "log.h"
static const char *watcher_path = "/StatusNotifierWatcher";
static int cmp_sni_id(const void *item, const void *cmp_to) {
const struct swaybar_sni *sni = item;
return strcmp(sni->watcher_id, cmp_to);
}
static void add_sni(struct swaybar_tray *tray, char *id) {
int idx = list_seq_find(tray->items, cmp_sni_id, id);
if (idx == -1) {
wlr_log(WLR_DEBUG, "Registering Status Notifier Item '%s'", id);
struct swaybar_sni *sni = create_sni(id, tray);
if (sni) {
list_add(tray->items, sni);
}
}
}
static int handle_sni_registered(sd_bus_message *msg, void *data,
sd_bus_error *error) {
char *id;
int ret = sd_bus_message_read(msg, "s", &id);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to parse register SNI message: %s", strerror(-ret));
}
struct swaybar_tray *tray = data;
add_sni(tray, id);
return ret;
}
static int handle_sni_unregistered(sd_bus_message *msg, void *data,
sd_bus_error *error) {
char *id;
int ret = sd_bus_message_read(msg, "s", &id);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to parse unregister SNI message: %s", strerror(-ret));
}
struct swaybar_tray *tray = data;
int idx = list_seq_find(tray->items, cmp_sni_id, id);
if (idx != -1) {
wlr_log(WLR_DEBUG, "Unregistering Status Notifier Item '%s'", id);
destroy_sni(tray->items->items[idx]);
list_del(tray->items, idx);
set_bar_dirty(tray->bar);
}
return ret;
}
static int get_registered_snis_callback(sd_bus_message *msg, void *data,
sd_bus_error *error) {
if (sd_bus_message_is_method_error(msg, NULL)) {
sd_bus_error err = *sd_bus_message_get_error(msg);
wlr_log(WLR_ERROR, "Failed to get registered SNIs: %s", err.message);
return -sd_bus_error_get_errno(&err);
}
int ret = sd_bus_message_enter_container(msg, 'v', NULL);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to read registered SNIs: %s", strerror(-ret));
return ret;
}
char **ids;
ret = sd_bus_message_read_strv(msg, &ids);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to read registered SNIs: %s", strerror(-ret));
return ret;
}
if (ids) {
struct swaybar_tray *tray = data;
for (char **id = ids; *id; ++id) {
add_sni(tray, *id);
}
}
return ret;
}
static bool register_to_watcher(struct swaybar_host *host) {
// this is called asynchronously in case the watcher is owned by this process
int ret = sd_bus_call_method_async(host->tray->bus, NULL,
host->watcher_interface, watcher_path, host->watcher_interface,
"RegisterStatusNotifierHost", NULL, NULL, "s", host->service);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to send register call: %s", strerror(-ret));
return false;
}
ret = sd_bus_call_method_async(host->tray->bus, NULL,
host->watcher_interface, watcher_path,
"org.freedesktop.DBus.Properties", "Get",
get_registered_snis_callback, host->tray, "ss",
host->watcher_interface, "RegisteredStatusNotifierItems");
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to get registered SNIs: %s", strerror(-ret));
}
return ret >= 0;
}
static int handle_new_watcher(sd_bus_message *msg,
void *data, sd_bus_error *error) {
char *service, *old_owner, *new_owner;
int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret));
return ret;
}
if (!*old_owner) {
struct swaybar_host *host = data;
if (strcmp(service, host->watcher_interface) == 0) {
register_to_watcher(host);
}
}
return 0;
}
bool init_host(struct swaybar_host *host, char *protocol,
struct swaybar_tray *tray) {
size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1;
host->watcher_interface = malloc(len);
if (!host->watcher_interface) {
return false;
}
snprintf(host->watcher_interface, len, "org.%s.StatusNotifierWatcher", protocol);
sd_bus_slot *reg_slot = NULL, *unreg_slot = NULL, *watcher_slot = NULL;
int ret = sd_bus_match_signal(tray->bus, &reg_slot, host->watcher_interface,
watcher_path, host->watcher_interface,
"StatusNotifierItemRegistered", handle_sni_registered, tray);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to subscribe to registering events: %s",
strerror(-ret));
goto error;
}
ret = sd_bus_match_signal(tray->bus, &unreg_slot, host->watcher_interface,
watcher_path, host->watcher_interface,
"StatusNotifierItemUnregistered", handle_sni_unregistered, tray);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
strerror(-ret));
goto error;
}
ret = sd_bus_match_signal(tray->bus, &watcher_slot, "org.freedesktop.DBus",
"/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged",
handle_new_watcher, host);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
strerror(-ret));
goto error;
}
pid_t pid = getpid();
size_t service_len = snprintf(NULL, 0, "org.%s.StatusNotifierHost-%d",
protocol, pid) + 1;
host->service = malloc(service_len);
if (!host->service) {
goto error;
}
snprintf(host->service, service_len, "org.%s.StatusNotifierHost-%d", protocol, pid);
ret = sd_bus_request_name(tray->bus, host->service, 0);
if (ret < 0) {
wlr_log(WLR_DEBUG, "Failed to acquire service name: %s", strerror(-ret));
goto error;
}
host->tray = tray;
if (!register_to_watcher(host)) {
goto error;
}
sd_bus_slot_set_floating(reg_slot, 1);
sd_bus_slot_set_floating(unreg_slot, 1);
sd_bus_slot_set_floating(watcher_slot, 1);
wlr_log(WLR_DEBUG, "Registered %s", host->service);
return true;
error:
sd_bus_slot_unref(reg_slot);
sd_bus_slot_unref(unreg_slot);
sd_bus_slot_unref(watcher_slot);
finish_host(host);
return false;
}
void finish_host(struct swaybar_host *host) {
sd_bus_release_name(host->tray->bus, host->service);
free(host->service);
free(host->watcher_interface);
}

462
swaybar/tray/icon.c Normal file
View file

@ -0,0 +1,462 @@
#define _POSIX_C_SOURCE 200809L
#include <ctype.h>
#include <dirent.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <wordexp.h>
#include "swaybar/tray/icon.h"
#include "config.h"
#include "list.h"
#include "log.h"
#include "stringop.h"
static bool dir_exists(char *path) {
struct stat sb;
return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode);
}
static list_t *get_basedirs(void) {
list_t *basedirs = create_list();
list_add(basedirs, strdup("$HOME/.icons")); // deprecated
char *data_home = getenv("XDG_DATA_HOME");
list_add(basedirs, strdup(data_home && *data_home ?
"$XDG_DATA_HOME/icons" : "$HOME/.local/share/icons"));
list_add(basedirs, strdup("/usr/share/pixmaps"));
char *data_dirs = getenv("XDG_DATA_DIRS");
if (!(data_dirs && *data_dirs)) {
data_dirs = "/usr/local/share:/usr/share";
}
data_dirs = strdup(data_dirs);
char *dir = strtok(data_dirs, ":");
do {
size_t path_len = snprintf(NULL, 0, "%s/icons", dir) + 1;
char *path = malloc(path_len);
snprintf(path, path_len, "%s/icons", dir);
list_add(basedirs, path);
} while ((dir = strtok(NULL, ":")));
free(data_dirs);
list_t *basedirs_expanded = create_list();
for (int i = 0; i < basedirs->length; ++i) {
wordexp_t p;
if (wordexp(basedirs->items[i], &p, WRDE_UNDEF) == 0) {
if (dir_exists(p.we_wordv[0])) {
list_add(basedirs_expanded, strdup(p.we_wordv[0]));
}
wordfree(&p);
}
}
list_free_items_and_destroy(basedirs);
return basedirs_expanded;
}
static void destroy_theme(struct icon_theme *theme) {
if (!theme) {
return;
}
free(theme->name);
free(theme->comment);
free(theme->inherits);
list_free_items_and_destroy(theme->directories);
free(theme->dir);
for (int i = 0; i < theme->subdirs->length; ++i) {
struct icon_theme_subdir *subdir = theme->subdirs->items[i];
free(subdir->name);
free(subdir);
}
list_free(theme->subdirs);
free(theme);
}
static int cmp_group(const void *item, const void *cmp_to) {
return strcmp(item, cmp_to);
}
static bool group_handler(char *old_group, char *new_group,
struct icon_theme *theme) {
if (!old_group) { // first group must be "Icon Theme"
return strcmp(new_group, "Icon Theme");
}
if (strcmp(old_group, "Icon Theme") == 0) {
if (!(theme->name && theme->comment && theme->directories)) {
return true;
}
} else {
if (theme->subdirs->length == 0) { // skip
return false;
}
struct icon_theme_subdir *subdir =
theme->subdirs->items[theme->subdirs->length - 1];
if (!subdir->size) return true;
switch (subdir->type) {
case FIXED: subdir->max_size = subdir->min_size = subdir->size;
break;
case SCALABLE: {
if (!subdir->max_size) subdir->max_size = subdir->size;
if (!subdir->min_size) subdir->min_size = subdir->size;
break;
}
case THRESHOLD:
subdir->max_size = subdir->size + subdir->threshold;
subdir->min_size = subdir->size - subdir->threshold;
}
}
if (new_group && list_seq_find(theme->directories, cmp_group, new_group) != -1) {
struct icon_theme_subdir *subdir = calloc(1, sizeof(struct icon_theme_subdir));
if (!subdir) {
return true;
}
subdir->name = strdup(new_group);
subdir->threshold = 2;
list_add(theme->subdirs, subdir);
}
return false;
}
static int entry_handler(char *group, char *key, char *value,
struct icon_theme *theme) {
if (strcmp(group, "Icon Theme") == 0) {
if (strcmp(key, "Name") == 0) {
theme->name = strdup(value);
} else if (strcmp(key, "Comment") == 0) {
theme->comment = strdup(value);
} else if (strcmp(key, "Inherists") == 0) {
theme->inherits = strdup(value);
} else if (strcmp(key, "Directories") == 0) {
theme->directories = split_string(value, ",");
} // Ignored: ScaledDirectories, Hidden, Example
} else {
if (theme->subdirs->length == 0) { // skip
return false;
}
struct icon_theme_subdir *subdir =
theme->subdirs->items[theme->subdirs->length - 1];
if (strcmp(subdir->name, group) != 0) { // skip
return false;
}
char *end;
int n = strtol(value, &end, 10);
if (strcmp(key, "Size") == 0) {
subdir->size = n;
return *end != '\0';
} else if (strcmp(key, "Type") == 0) {
if (strcmp(value, "Fixed") == 0) {
subdir->type = FIXED;
} else if (strcmp(value, "Scalable") == 0) {
subdir->type = SCALABLE;
} else if (strcmp(value, "Threshold") == 0) {
subdir->type = THRESHOLD;
} else {
return true;
}
} else if (strcmp(key, "MaxSize") == 0) {
subdir->max_size = n;
return *end != '\0';
} else if (strcmp(key, "MinSize") == 0) {
subdir->min_size = n;
return *end != '\0';
} else if (strcmp(key, "Threshold") == 0) {
subdir->threshold = n;
return *end != '\0';
} // Ignored: Scale, Applications
}
return false;
}
/*
* This is a Freedesktop Desktop Entry parser (essentially INI)
* It calls entry_handler for every entry
* and group_handler between every group (as well as at both ends)
* Handlers return whether an error occured, which stops parsing
*/
static struct icon_theme *read_theme_file(char *basedir, char *theme_name) {
// look for index.theme file
size_t path_len = snprintf(NULL, 0, "%s/%s/index.theme", basedir,
theme_name) + 1;
char *path = malloc(path_len);
snprintf(path, path_len, "%s/%s/index.theme", basedir, theme_name);
FILE *theme_file = fopen(path, "r");
if (!theme_file) {
return NULL;
}
struct icon_theme *theme = calloc(1, sizeof(struct icon_theme));
if (!theme) {
return NULL;
}
theme->subdirs = create_list();
bool error = false;
char *group = NULL;
char *full_line = NULL;
size_t full_len = 0;
ssize_t nread;
while ((nread = getline(&full_line, &full_len, theme_file)) != -1) {
char *line = full_line - 1;
while (isspace(*++line)) {} // remove leading whitespace
if (!*line || line[0] == '#') continue; // ignore blank lines & comments
int len = nread - (line - full_line);
while (isspace(line[--len])) {}
line[++len] = '\0'; // remove trailing whitespace
if (line[0] == '[') { // group header
// check well-formed
if (line[--len] != ']') {
error = true;
break;
}
int i = 1;
for (; !iscntrl(line[i]) && line[i] != '[' && line[i] != ']'; ++i) {}
if (i < len) {
error = true;
break;
}
// call handler
line[len] = '\0';
error = group_handler(group, &line[1], theme);
if (error) {
break;
}
free(group);
group = strdup(&line[1]);
} else { // key-value pair
// check well-formed
int eok = 0;
for (; isalnum(line[eok]) || line[eok] == '-'; ++eok) {} // TODO locale?
int i = eok - 1;
while (isspace(line[++i])) {}
if (line[i] != '=') {
error = true;
break;
}
line[eok] = '\0'; // split into key-value pair
char *value = &line[i];
while (isspace(*++value)) {}
// TODO unescape value
error = entry_handler(group, line, value, theme);
if (error) {
break;
}
}
}
if (!error && group) {
error = group_handler(group, NULL, theme);
}
free(group);
free(full_line);
fclose(theme_file);
if (!error) {
theme->dir = strdup(theme_name);
return theme;
} else {
destroy_theme(theme);
return NULL;
}
}
static list_t *load_themes_in_dir(char *basedir) {
DIR *dir;
if (!(dir = opendir(basedir))) {
return NULL;
}
list_t *themes = create_list();
struct dirent *entry;
while ((entry = readdir(dir))) {
if (entry->d_name[0] == '.') continue;
struct icon_theme *theme = read_theme_file(basedir, entry->d_name);
if (theme) {
list_add(themes, theme);
}
}
return themes;
}
void init_themes(list_t **themes, list_t **basedirs) {
*basedirs = get_basedirs();
*themes = create_list();
for (int i = 0; i < (*basedirs)->length; ++i) {
list_t *dir_themes = load_themes_in_dir((*basedirs)->items[i]);
list_cat(*themes, dir_themes);
list_free(dir_themes);
}
list_t *theme_names = create_list();
for (int i = 0; i < (*themes)->length; ++i) {
struct icon_theme *theme = (*themes)->items[i];
list_add(theme_names, theme->name);
}
wlr_log(WLR_DEBUG, "Loaded themes: %s", join_list(theme_names, ", "));
list_free(theme_names);
}
void finish_themes(list_t *themes, list_t *basedirs) {
for (int i = 0; i < themes->length; ++i) {
destroy_theme(themes->items[i]);
}
list_free(themes);
list_free_items_and_destroy(basedirs);
}
static char *find_icon_in_subdir(char *name, char *basedir, char *theme,
char *subdir) {
static const char *extensions[] = {
#if HAVE_GDK_PIXBUF
"svg",
#endif
"png",
#if HAVE_GDK_PIXBUF
"xpm"
#endif
};
size_t path_len = snprintf(NULL, 0, "%s/%s/%s/%s.EXT", basedir, theme,
subdir, name) + 1;
char *path = malloc(path_len);
for (size_t i = 0; i < sizeof(extensions) / sizeof(*extensions); ++i) {
snprintf(path, path_len, "%s/%s/%s/%s.%s", basedir, theme, subdir,
name, extensions[i]);
if (access(path, R_OK) == 0) {
return path;
}
}
free(path);
return NULL;
}
static bool theme_exists_in_basedir(char *theme, char *basedir) {
size_t path_len = snprintf(NULL, 0, "%s/%s", basedir, theme) + 1;
char *path = malloc(path_len);
snprintf(path, path_len, "%s/%s", basedir, theme);
bool ret = dir_exists(path);
free(path);
return ret;
}
static char *find_icon_with_theme(list_t *basedirs, list_t *themes, char *name,
int size, char *theme_name, int *min_size, int *max_size) {
struct icon_theme *theme = NULL;
for (int i = 0; i < themes->length; ++i) {
theme = themes->items[i];
if (strcmp(theme->name, theme_name) == 0) {
break;
}
theme = NULL;
}
if (!theme) return NULL;
char *icon = NULL;
for (int i = 0; i < basedirs->length; ++i) {
if (!theme_exists_in_basedir(theme->dir, basedirs->items[i])) {
continue;
}
// search backwards to hopefully hit scalable/larger icons first
for (int j = theme->subdirs->length - 1; j >= 0; --j) {
struct icon_theme_subdir *subdir = theme->subdirs->items[j];
if (size >= subdir->min_size && size <= subdir->max_size) {
if ((icon = find_icon_in_subdir(name, basedirs->items[i],
theme->dir, subdir->name))) {
*min_size = subdir->min_size;
*max_size = subdir->max_size;
return icon;
}
}
}
}
// inexact match
unsigned smallest_error = -1; // UINT_MAX
for (int i = 0; i < basedirs->length; ++i) {
if (!theme_exists_in_basedir(theme->dir, basedirs->items[i])) {
continue;
}
for (int j = theme->subdirs->length - 1; j >= 0; --j) {
struct icon_theme_subdir *subdir = theme->subdirs->items[j];
unsigned error = (size > subdir->max_size ? size - subdir->max_size : 0)
+ (size < subdir->min_size ? subdir->min_size - size : 0);
if (error < smallest_error) {
char *test_icon = find_icon_in_subdir(name, basedirs->items[i],
theme->dir, subdir->name);
if (test_icon) {
icon = test_icon;
smallest_error = error;
*min_size = subdir->min_size;
*max_size = subdir->max_size;
}
}
}
}
if (!icon && theme->inherits) {
icon = find_icon_with_theme(basedirs, themes, name, size,
theme->inherits, min_size, max_size);
}
return icon;
}
char *find_icon_in_dir(char *name, char *dir, int *min_size, int *max_size) {
char *icon = find_icon_in_subdir(name, dir, "", "");
if (icon) {
*min_size = 1;
*max_size = 512;
}
return icon;
}
static char *find_fallback_icon(list_t *basedirs, char *name, int *min_size,
int *max_size) {
for (int i = 0; i < basedirs->length; ++i) {
char *icon = find_icon_in_dir(name, basedirs->items[i], min_size, max_size);
if (icon) {
return icon;
}
}
return NULL;
}
char *find_icon(list_t *themes, list_t *basedirs, char *name, int size,
char *theme, int *min_size, int *max_size) {
// TODO https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#implementation_notes
char *icon = NULL;
if (theme) {
icon = find_icon_with_theme(basedirs, themes, name, size, theme,
min_size, max_size);
}
if (!icon) {
icon = find_icon_with_theme(basedirs, themes, name, size, "Hicolor",
min_size, max_size);
}
if (!icon) {
icon = find_fallback_icon(basedirs, name, min_size, max_size);
}
return icon;
}

443
swaybar/tray/item.c Normal file
View file

@ -0,0 +1,443 @@
#define _POSIX_C_SOURCE 200809L
#include <cairo.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "swaybar/bar.h"
#include "swaybar/config.h"
#include "swaybar/input.h"
#include "swaybar/tray/host.h"
#include "swaybar/tray/icon.h"
#include "swaybar/tray/item.h"
#include "swaybar/tray/tray.h"
#include "background-image.h"
#include "cairo.h"
#include "list.h"
#include "log.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
// TODO menu
static bool sni_ready(struct swaybar_sni *sni) {
return sni->status && (sni->status[0] == 'N' ?
sni->attention_icon_name || sni->attention_icon_pixmap :
sni->icon_name || sni->icon_pixmap);
}
static void set_sni_dirty(struct swaybar_sni *sni) {
if (sni_ready(sni)) {
sni->min_size = sni->max_size = 0; // invalidate previous icon
set_bar_dirty(sni->tray->bar);
}
}
static int read_pixmap(sd_bus_message *msg, struct swaybar_sni *sni,
const char *prop, list_t **dest) {
int ret = sd_bus_message_enter_container(msg, 'a', "(iiay)");
if (ret < 0) {
wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
return ret;
}
if (sd_bus_message_at_end(msg, 0)) {
return ret;
}
list_t *pixmaps = create_list();
if (!pixmaps) {
return -12; // -ENOMEM
}
while (!sd_bus_message_at_end(msg, 0)) {
ret = sd_bus_message_enter_container(msg, 'r', "iiay");
if (ret < 0) {
wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
goto error;
}
int size;
ret = sd_bus_message_read(msg, "ii", NULL, &size);
if (ret < 0) {
wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
goto error;
}
const void *pixels;
size_t npixels;
ret = sd_bus_message_read_array(msg, 'y', &pixels, &npixels);
if (ret < 0) {
wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
goto error;
}
struct swaybar_pixmap *pixmap =
malloc(sizeof(struct swaybar_pixmap) + npixels);
pixmap->size = size;
memcpy(pixmap->pixels, pixels, npixels);
list_add(pixmaps, pixmap);
sd_bus_message_exit_container(msg);
}
*dest = pixmaps;
return ret;
error:
list_free_items_and_destroy(pixmaps);
return ret;
}
struct get_property_data {
struct swaybar_sni *sni;
const char *prop;
const char *type;
void *dest;
};
static int get_property_callback(sd_bus_message *msg, void *data,
sd_bus_error *error) {
struct get_property_data *d = data;
struct swaybar_sni *sni = d->sni;
const char *prop = d->prop;
const char *type = d->type;
void *dest = d->dest;
int ret;
if (sd_bus_message_is_method_error(msg, NULL)) {
sd_bus_error err = *sd_bus_message_get_error(msg);
wlr_log(WLR_DEBUG, "Failed to get property %s: %s", prop, err.message);
ret = -sd_bus_error_get_errno(&err);
goto cleanup;
}
ret = sd_bus_message_enter_container(msg, 'v', type);
if (ret < 0) {
wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
goto cleanup;
}
if (!type) {
ret = read_pixmap(msg, sni, prop, dest);
if (ret < 0) {
goto cleanup;
}
} else {
ret = sd_bus_message_read(msg, type, dest);
if (ret < 0) {
wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop,
strerror(-ret));
goto cleanup;
} else if (*type == 's' || *type == 'o') {
char **str = dest;
*str = strdup(*str);
}
}
if (strcmp(prop, "Status") == 0 || (sni->status && (sni->status[0] == 'N' ?
prop[0] == 'A' : strncmp(prop, "Icon", 4) == 0))) {
set_sni_dirty(sni);
}
cleanup:
free(data);
return ret;
}
static void sni_get_property_async(struct swaybar_sni *sni, const char *prop,
const char *type, void *dest) {
struct get_property_data *data = malloc(sizeof(struct get_property_data));
data->sni = sni;
data->prop = prop;
data->type = type;
data->dest = dest;
int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service,
sni->path, "org.freedesktop.DBus.Properties", "Get",
get_property_callback, data, "ss", sni->interface, prop);
if (ret < 0) {
wlr_log(WLR_DEBUG, "Failed to get property %s: %s", prop, strerror(-ret));
}
}
static int handle_new_icon(sd_bus_message *msg, void *data, sd_bus_error *error) {
struct swaybar_sni *sni = data;
wlr_log(WLR_DEBUG, "%s has new IconName", sni->watcher_id);
free(sni->icon_name);
sni->icon_name = NULL;
sni_get_property_async(sni, "IconName", "s", &sni->icon_name);
list_free_items_and_destroy(sni->icon_pixmap);
sni->icon_pixmap = NULL;
sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap);
return 0;
}
static int handle_new_attention_icon(sd_bus_message *msg, void *data,
sd_bus_error *error) {
struct swaybar_sni *sni = data;
wlr_log(WLR_DEBUG, "%s has new AttentionIconName", sni->watcher_id);
free(sni->attention_icon_name);
sni->attention_icon_name = NULL;
sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name);
list_free_items_and_destroy(sni->attention_icon_pixmap);
sni->attention_icon_pixmap = NULL;
sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap);
return 0;
}
static int handle_new_status(sd_bus_message *msg, void *data, sd_bus_error *error) {
char *status;
int ret = sd_bus_message_read(msg, "s", &status);
if (ret < 0) {
wlr_log(WLR_DEBUG, "Failed to read new status message: %s", strerror(-ret));
} else {
struct swaybar_sni *sni = data;
free(sni->status);
sni->status = strdup(status);
wlr_log(WLR_DEBUG, "%s has new Status '%s'", sni->watcher_id, status);
set_sni_dirty(sni);
}
return ret;
}
static void sni_match_signal(struct swaybar_sni *sni, char *signal,
sd_bus_message_handler_t callback) {
int ret = sd_bus_match_signal(sni->tray->bus, NULL, sni->service, sni->path,
sni->interface, signal, callback, sni);
if (ret < 0) {
wlr_log(WLR_DEBUG, "Failed to subscribe to signal %s: %s", signal,
strerror(-ret));
}
}
struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray) {
struct swaybar_sni *sni = calloc(1, sizeof(struct swaybar_sni));
if (!sni) {
return NULL;
}
sni->tray = tray;
sni->watcher_id = strdup(id);
char *path_ptr = strchr(id, '/');
if (!path_ptr) {
sni->service = strdup(id);
sni->path = strdup("/StatusNotifierItem");
sni->interface = "org.freedesktop.StatusNotifierItem";
} else {
sni->service = strndup(id, path_ptr - id);
sni->path = strdup(path_ptr);
sni->interface = "org.kde.StatusNotifierItem";
sni_get_property_async(sni, "IconThemePath", "s", &sni->icon_theme_path);
}
// Ignored: Category, Id, Title, WindowId, OverlayIconName,
// OverlayIconPixmap, AttentionMovieName, ToolTip
sni_get_property_async(sni, "Status", "s", &sni->status);
sni_get_property_async(sni, "IconName", "s", &sni->icon_name);
sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap);
sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name);
sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap);
sni_get_property_async(sni, "ItemIsMenu", "b", &sni->item_is_menu);
sni_get_property_async(sni, "Menu", "o", &sni->menu);
sni_match_signal(sni, "NewIcon", handle_new_icon);
sni_match_signal(sni, "NewAttentionIcon", handle_new_attention_icon);
sni_match_signal(sni, "NewStatus", handle_new_status);
return sni;
}
void destroy_sni(struct swaybar_sni *sni) {
if (!sni) {
return;
}
free(sni->watcher_id);
free(sni->service);
free(sni->path);
free(sni->status);
free(sni->icon_name);
free(sni->icon_pixmap);
free(sni->attention_icon_name);
free(sni->menu);
free(sni);
}
static void handle_click(struct swaybar_sni *sni, int x, int y,
enum x11_button button, int delta) {
const char *method = sni->tray->bar->config->tray_bindings[button];
if (!method) {
static const char *default_bindings[10] = {
"nop",
"Activate",
"SecondaryActivate",
"ContextMenu",
"ScrollUp",
"ScrollDown",
"ScrollLeft",
"ScrollRight",
"nop",
"nop"
};
method = default_bindings[button];
}
if (strcmp(method, "nop") == 0) {
return;
}
if (sni->item_is_menu && strcmp(method, "Activate") == 0) {
method = "ContextMenu";
}
if (strncmp(method, "Scroll", strlen("Scroll")) == 0) {
char dir = method[strlen("Scroll")];
char *orientation = (dir = 'U' || dir == 'D') ? "vertical" : "horizontal";
int sign = (dir == 'U' || dir == 'L') ? -1 : 1;
int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service,
sni->path, sni->interface, "Scroll", NULL, NULL, "is",
delta*sign, orientation);
if (ret < 0) {
wlr_log(WLR_DEBUG, "Failed to scroll on SNI: %s", strerror(-ret));
}
} else {
int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service,
sni->path, sni->interface, method, NULL, NULL, "ii", x, y);
if (ret < 0) {
wlr_log(WLR_DEBUG, "Failed to click on SNI: %s", strerror(-ret));
}
}
}
static int cmp_sni_id(const void *item, const void *cmp_to) {
const struct swaybar_sni *sni = item;
return strcmp(sni->watcher_id, cmp_to);
}
static enum hotspot_event_handling icon_hotspot_callback(
struct swaybar_output *output, struct swaybar_hotspot *hotspot,
int x, int y, enum x11_button button, void *data) {
wlr_log(WLR_DEBUG, "Clicked on Status Notifier Item '%s'", (char *)data);
struct swaybar_tray *tray = output->bar->tray;
int idx = list_seq_find(tray->items, cmp_sni_id, data);
if (idx != -1) {
struct swaybar_sni *sni = tray->items->items[idx];
// guess global position since wayland doesn't expose it
struct swaybar_config *config = tray->bar->config;
int global_x = output->output_x + config->gaps.left + x;
bool top_bar = config->position & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
int global_y = output->output_y + (top_bar ? config->gaps.top + y:
(int) output->output_height - config->gaps.bottom - y);
wlr_log(WLR_DEBUG, "Guessing click at (%d, %d)", global_x, global_y);
handle_click(sni, global_x, global_y, button, 1); // TODO get delta from event
return HOTSPOT_IGNORE;
} else {
wlr_log(WLR_DEBUG, "but it doesn't exist");
}
return HOTSPOT_PROCESS;
}
uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x,
struct swaybar_sni *sni) {
uint32_t height = output->height * output->scale;
int padding = output->bar->config->tray_padding;
int ideal_size = height - 2*padding;
if ((ideal_size < sni->min_size || ideal_size > sni->max_size) && sni_ready(sni)) {
bool icon_found = false;
char *icon_name = sni->status[0] == 'N' ?
sni->attention_icon_name : sni->icon_name;
if (icon_name) {
char *icon_path = find_icon(sni->tray->themes, sni->tray->basedirs,
icon_name, ideal_size, output->bar->config->icon_theme,
&sni->min_size, &sni->max_size);
if (!icon_path && sni->icon_theme_path) {
icon_path = find_icon_in_dir(icon_name, sni->icon_theme_path,
&sni->min_size, &sni->max_size);
}
if (icon_path) {
cairo_surface_destroy(sni->icon);
sni->icon = load_background_image(icon_path);
free(icon_path);
icon_found = true;
}
}
if (!icon_found) {
list_t *pixmaps = sni->status[0] == 'N' ?
sni->attention_icon_pixmap : sni->icon_pixmap;
if (pixmaps) {
int idx = -1;
unsigned smallest_error = -1; // UINT_MAX
for (int i = 0; i < pixmaps->length; ++i) {
struct swaybar_pixmap *pixmap = pixmaps->items[i];
unsigned error = (ideal_size - pixmap->size) *
(ideal_size < pixmap->size ? -1 : 1);
if (error < smallest_error) {
smallest_error = error;
idx = i;
}
}
struct swaybar_pixmap *pixmap = pixmaps->items[idx];
cairo_surface_destroy(sni->icon);
sni->icon = cairo_image_surface_create_for_data(pixmap->pixels,
CAIRO_FORMAT_ARGB32, pixmap->size, pixmap->size,
cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pixmap->size));
}
}
}
int icon_size;
cairo_surface_t *icon;
if (sni->icon) {
int actual_size = cairo_image_surface_get_height(sni->icon);
icon_size = actual_size < ideal_size ?
actual_size*(ideal_size/actual_size) : ideal_size;
icon = cairo_image_surface_scale(sni->icon, icon_size, icon_size);
} else { // draw a sad face
icon_size = ideal_size*0.8;
icon = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, icon_size, icon_size);
cairo_t *cairo_icon = cairo_create(icon);
cairo_set_source_u32(cairo_icon, 0xFF0000FF);
cairo_translate(cairo_icon, icon_size/2, icon_size/2);
cairo_scale(cairo_icon, icon_size/2, icon_size/2);
cairo_arc(cairo_icon, 0, 0, 1, 0, 7);
cairo_fill(cairo_icon);
cairo_set_operator(cairo_icon, CAIRO_OPERATOR_CLEAR);
cairo_arc(cairo_icon, 0.35, -0.3, 0.1, 0, 7);
cairo_fill(cairo_icon);
cairo_arc(cairo_icon, -0.35, -0.3, 0.1, 0, 7);
cairo_fill(cairo_icon);
cairo_arc(cairo_icon, 0, 0.75, 0.5, 3.71238898038469, 5.71238898038469);
cairo_set_line_width(cairo_icon, 0.1);
cairo_stroke(cairo_icon);
cairo_destroy(cairo_icon);
}
int padded_size = icon_size + 2*padding;
*x -= padded_size;
int y = floor((height - padded_size) / 2.0);
cairo_operator_t op = cairo_get_operator(cairo);
cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
cairo_set_source_surface(cairo, icon, *x + padding, y + padding);
cairo_rectangle(cairo, *x, y, padded_size, padded_size);
cairo_fill(cairo);
cairo_set_operator(cairo, op);
cairo_surface_destroy(icon);
struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
hotspot->x = *x;
hotspot->y = 0;
hotspot->width = height;
hotspot->height = height;
hotspot->callback = icon_hotspot_callback;
hotspot->destroy = free;
hotspot->data = strdup(sni->watcher_id);
wl_list_insert(&output->hotspots, &hotspot->link);
return output->height;
}

127
swaybar/tray/tray.c Normal file
View file

@ -0,0 +1,127 @@
#include <cairo.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "swaybar/config.h"
#include "swaybar/bar.h"
#include "swaybar/tray/icon.h"
#include "swaybar/tray/host.h"
#include "swaybar/tray/item.h"
#include "swaybar/tray/tray.h"
#include "swaybar/tray/watcher.h"
#include "list.h"
#include "log.h"
static int handle_lost_watcher(sd_bus_message *msg,
void *data, sd_bus_error *error) {
char *service, *old_owner, *new_owner;
int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret));
return ret;
}
if (!*new_owner) {
struct swaybar_tray *tray = data;
if (strcmp(service, "org.freedesktop.StatusNotifierWatcher") == 0) {
tray->watcher_xdg = create_watcher("freedesktop", tray->bus);
} else if (strcmp(service, "org.kde.StatusNotifierWatcher") == 0) {
tray->watcher_kde = create_watcher("kde", tray->bus);
}
}
return 0;
}
struct swaybar_tray *create_tray(struct swaybar *bar) {
wlr_log(WLR_DEBUG, "Initializing tray");
sd_bus *bus;
int ret = sd_bus_open_user(&bus);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to connect to user bus: %s", strerror(-ret));
return NULL;
}
struct swaybar_tray *tray = calloc(1, sizeof(struct swaybar_tray));
if (!tray) {
return NULL;
}
tray->bar = bar;
tray->bus = bus;
tray->fd = sd_bus_get_fd(tray->bus);
tray->watcher_xdg = create_watcher("freedesktop", tray->bus);
tray->watcher_kde = create_watcher("kde", tray->bus);
ret = sd_bus_match_signal(bus, NULL, "org.freedesktop.DBus",
"/org/freedesktop/DBus", "org.freedesktop.DBus",
"NameOwnerChanged", handle_lost_watcher, tray);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
strerror(-ret));
}
tray->items = create_list();
init_host(&tray->host_xdg, "freedesktop", tray);
init_host(&tray->host_kde, "kde", tray);
init_themes(&tray->themes, &tray->basedirs);
return tray;
}
void destroy_tray(struct swaybar_tray *tray) {
if (!tray) {
return;
}
finish_host(&tray->host_xdg);
finish_host(&tray->host_kde);
for (int i = 0; i < tray->items->length; ++i) {
destroy_sni(tray->items->items[0]);
}
list_free(tray->items);
destroy_watcher(tray->watcher_xdg);
destroy_watcher(tray->watcher_kde);
sd_bus_flush_close_unref(tray->bus);
finish_themes(tray->themes, tray->basedirs);
free(tray);
}
void tray_in(int fd, short mask, void *data) {
sd_bus *bus = data;
int ret;
while ((ret = sd_bus_process(bus, NULL)) > 0) {
// This space intentionally left blank
}
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to process bus: %s", strerror(-ret));
}
}
static int cmp_output(const void *item, const void *cmp_to) {
return strcmp(item, cmp_to);
}
uint32_t render_tray(cairo_t *cairo, struct swaybar_output *output, double *x) {
struct swaybar_config *config = output->bar->config;
if (config->tray_outputs) {
if (list_seq_find(config->tray_outputs, cmp_output, output->name) == -1) {
return 0;
}
} // else display on all
if ((int) output->height*output->scale <= 2*config->tray_padding) {
return 2*config->tray_padding + 1;
}
uint32_t max_height = 0;
struct swaybar_tray *tray = output->bar->tray;
for (int i = 0; i < tray->items->length; ++i) {
uint32_t h = render_sni(cairo, output, x, tray->items->items[i]);
max_height = h > max_height ? h : max_height;
}
return max_height;
}

212
swaybar/tray/watcher.c Normal file
View file

@ -0,0 +1,212 @@
#define _POSIX_C_SOURCE 200809L
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"
#include "log.h"
#include "swaybar/tray/watcher.h"
static const char *obj_path = "/StatusNotifierWatcher";
static bool using_standard_protocol(struct swaybar_watcher *watcher) {
return watcher->interface[strlen("org.")] == 'f'; // freedesktop
}
static int cmp_id(const void *item, const void *cmp_to) {
return strcmp(item, cmp_to);
}
static int cmp_service(const void *item, const void *cmp_to) {
return strncmp(item, cmp_to, strlen(cmp_to));
}
static int handle_lost_service(sd_bus_message *msg,
void *data, sd_bus_error *error) {
char *service, *old_owner, *new_owner;
int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret));
return ret;
}
if (!*new_owner) {
struct swaybar_watcher *watcher = data;
int idx = list_seq_find(watcher->items,
using_standard_protocol(watcher) ? cmp_id : cmp_service, service);
if (idx != -1) {
char *id = watcher->items->items[idx];
wlr_log(WLR_DEBUG, "Unregistering Status Notifier Item '%s'", id);
list_del(watcher->items, idx);
sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
"StatusNotifierItemUnregistered", "s", id);
free(id);
}
idx = list_seq_find(watcher->hosts, cmp_id, service);
if (idx != -1) {
wlr_log(WLR_DEBUG, "Unregistering Status Notifier Host '%s'", service);
free(watcher->hosts->items[idx]);
list_del(watcher->hosts, idx);
}
}
return 0;
}
static int register_sni(sd_bus_message *msg, void *data, sd_bus_error *error) {
char *service_or_path, *id;
int ret = sd_bus_message_read(msg, "s", &service_or_path);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to parse register SNI message: %s", strerror(-ret));
return ret;
}
struct swaybar_watcher *watcher = data;
if (using_standard_protocol(watcher)) {
id = strdup(service_or_path);
} else {
const char *service, *path;
if (service_or_path[0] == '/') {
service = sd_bus_message_get_sender(msg);
path = service_or_path;
} else {
service = service_or_path;
path = "/StatusNotifierItem";
}
size_t id_len = snprintf(NULL, 0, "%s%s", service, path) + 1;
id = malloc(id_len);
snprintf(id, id_len, "%s%s", service, path);
}
if (list_seq_find(watcher->items, cmp_id, id) == -1) {
wlr_log(WLR_DEBUG, "Registering Status Notifier Item '%s'", id);
list_add(watcher->items, id);
sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
"StatusNotifierItemRegistered", "s", id);
} else {
wlr_log(WLR_DEBUG, "Status Notifier Item '%s' already registered", id);
free(id);
}
return sd_bus_reply_method_return(msg, "");
}
static int register_host(sd_bus_message *msg, void *data, sd_bus_error *error) {
char *service;
int ret = sd_bus_message_read(msg, "s", &service);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to parse register host message: %s", strerror(-ret));
return ret;
}
struct swaybar_watcher *watcher = data;
if (list_seq_find(watcher->hosts, cmp_id, service) == -1) {
wlr_log(WLR_DEBUG, "Registering Status Notifier Host '%s'", service);
list_add(watcher->hosts, strdup(service));
sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
"StatusNotifierHostRegistered", "s", service);
} else {
wlr_log(WLR_DEBUG, "Status Notifier Host '%s' already registered", service);
}
return sd_bus_reply_method_return(msg, "");
}
static int get_registered_snis(sd_bus *bus, const char *obj_path,
const char *interface, const char *property, sd_bus_message *reply,
void *data, sd_bus_error *error) {
struct swaybar_watcher *watcher = data;
list_add(watcher->items, NULL); // strv expects NULL-terminated string array
int ret = sd_bus_message_append_strv(reply, (char **)watcher->items->items);
list_del(watcher->items, watcher->items->length - 1);
return ret;
}
static int is_host_registered(sd_bus *bus, const char *obj_path,
const char *interface, const char *property, sd_bus_message *reply,
void *data, sd_bus_error *error) {
struct swaybar_watcher *watcher = data;
int val = watcher->hosts->length > 0; // dbus expects int rather than bool
return sd_bus_message_append_basic(reply, 'b', &val);
}
static const sd_bus_vtable watcher_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("RegisterStatusNotifierItem", "s", "", register_sni,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("RegisterStatusNotifierHost", "s", "", register_host,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_PROPERTY("RegisteredStatusNotifierItems", "as", get_registered_snis,
0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("IsStatusNotifierHostRegistered", "b", is_host_registered,
0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("ProtocolVersion", "i", NULL,
offsetof(struct swaybar_watcher, version),
SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_SIGNAL("StatusNotifierItemRegistered", "s", 0),
SD_BUS_SIGNAL("StatusNotifierItemUnregistered", "s", 0),
SD_BUS_SIGNAL("StatusNotifierHostRegistered", NULL, 0),
SD_BUS_VTABLE_END
};
struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus) {
struct swaybar_watcher *watcher =
calloc(1, sizeof(struct swaybar_watcher));
if (!watcher) {
return NULL;
}
size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1;
watcher->interface = malloc(len);
snprintf(watcher->interface, len, "org.%s.StatusNotifierWatcher", protocol);
sd_bus_slot *signal_slot = NULL, *vtable_slot = NULL;
int ret = sd_bus_add_object_vtable(bus, &vtable_slot, obj_path,
watcher->interface, watcher_vtable, watcher);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to add object vtable: %s", strerror(-ret));
goto error;
}
ret = sd_bus_match_signal(bus, &signal_slot, "org.freedesktop.DBus",
"/org/freedesktop/DBus", "org.freedesktop.DBus",
"NameOwnerChanged", handle_lost_service, watcher);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
strerror(-ret));
goto error;
}
ret = sd_bus_request_name(bus, watcher->interface, 0);
if (ret < 0) {
wlr_log(WLR_ERROR, "Failed to acquire service name: %s", strerror(-ret));
goto error;
}
sd_bus_slot_set_floating(signal_slot, 0);
sd_bus_slot_set_floating(vtable_slot, 0);
watcher->bus = bus;
watcher->hosts = create_list();
watcher->items = create_list();
watcher->version = 0;
wlr_log(WLR_DEBUG, "Registered %s", watcher->interface);
return watcher;
error:
sd_bus_slot_unref(signal_slot);
sd_bus_slot_unref(vtable_slot);
destroy_watcher(watcher);
return NULL;
}
void destroy_watcher(struct swaybar_watcher *watcher) {
if (!watcher) {
return;
}
list_free_items_and_destroy(watcher->hosts);
list_free_items_and_destroy(watcher->items);
free(watcher->interface);
free(watcher);
}