mirror of
https://github.com/swaywm/sway.git
synced 2024-12-29 08:26:45 +01:00
commit
3d7c20f062
34 changed files with 1965 additions and 165 deletions
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
17
include/swaybar/tray/host.h
Normal file
17
include/swaybar/tray/host.h
Normal 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
|
|
@ -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
|
||||
|
|
45
include/swaybar/tray/item.h
Normal file
45
include/swaybar/tray/item.h
Normal 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
|
|
@ -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 */
|
40
include/swaybar/tray/tray.h
Normal file
40
include/swaybar/tray/tray.h
Normal 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
|
18
include/swaybar/tray/watcher.h
Normal file
18
include/swaybar/tray/watcher.h
Normal 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
|
|
@ -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')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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");
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
55
sway/commands/bar/tray_bindsym.c
Normal file
55
sway/commands/bar/tray_bindsym.c
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
210
swaybar/tray/host.c
Normal 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, ®_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
462
swaybar/tray/icon.c
Normal 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
443
swaybar/tray/item.c
Normal 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
127
swaybar/tray/tray.c
Normal 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
212
swaybar/tray/watcher.c
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue