1
1
mirror of https://github.com/swaywm/sway synced 2024-11-23 13:22:17 +01:00

Implement Tray Icons

This commit implements the StatusNotifierItem protocol, and enables
swaybar to show tray icons. It also uses `xembedsniproxy` in order to
communicate with xembed applications.
The tray is completely optional, and can be disabled on compile time
with the `enable-tray` option. Or on runtime with the bar config option
`tray_output none`.

Overview of changes:
In swaybar very little is changed outside the tray subfolder except
that all events are now polled in `event_loop.c`, this creates no
functional difference.

Six bar configuration options were added, these are detailed in
sway-bar(5)

The tray subfolder is where all protocol implementation takes place and
is organised as follows:

tray/sni_watcher.c:
	This file contains the StatusNotifierWatcher. It keeps track of
	items and hosts and reports when they come or go.
tray/tray.c
	This file contains the StatusNotifierHost. It keeps track of
	sway's version of the items and represents the tray itself.
tray/sni.c
	This file contains the StatusNotifierItem struct and all
	communication with individual items.
tray/icon.c
	This file implements the icon theme protocol. It allows for
	finding icons by name, rather than by pixmap.
tray/dbus.c
	This file allows for asynchronous DBus communication.

See #986 #343
This commit is contained in:
Calvin Lee 2017-06-07 16:45:28 -07:00
parent fd47a30e75
commit 843ad38b3c
35 changed files with 2714 additions and 58 deletions

59
CMake/FindDBus.cmake Normal file

@ -0,0 +1,59 @@
# - Try to find DBus
# Once done, this will define
#
# DBUS_FOUND - system has DBus
# DBUS_INCLUDE_DIRS - the DBus include directories
# DBUS_LIBRARIES - link these to use DBus
#
# Copyright (C) 2012 Raphael Kubo da Costa <rakuco@webkit.org>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS
# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
FIND_PACKAGE(PkgConfig)
PKG_CHECK_MODULES(PC_DBUS QUIET dbus-1)
FIND_LIBRARY(DBUS_LIBRARIES
NAMES dbus-1
HINTS ${PC_DBUS_LIBDIR}
${PC_DBUS_LIBRARY_DIRS}
)
FIND_PATH(DBUS_INCLUDE_DIR
NAMES dbus/dbus.h
HINTS ${PC_DBUS_INCLUDEDIR}
${PC_DBUS_INCLUDE_DIRS}
)
GET_FILENAME_COMPONENT(_DBUS_LIBRARY_DIR ${DBUS_LIBRARIES} PATH)
FIND_PATH(DBUS_ARCH_INCLUDE_DIR
NAMES dbus/dbus-arch-deps.h
HINTS ${PC_DBUS_INCLUDEDIR}
${PC_DBUS_INCLUDE_DIRS}
${_DBUS_LIBRARY_DIR}
${DBUS_INCLUDE_DIR}
PATH_SUFFIXES include
)
SET(DBUS_INCLUDE_DIRS ${DBUS_INCLUDE_DIR} ${DBUS_ARCH_INCLUDE_DIR})
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(DBUS REQUIRED_VARS DBUS_INCLUDE_DIRS DBUS_LIBRARIES)

@ -47,6 +47,7 @@ option(enable-swaybar "Enables the swaybar utility" YES)
option(enable-swaygrab "Enables the swaygrab utility" YES)
option(enable-swaymsg "Enables the swaymsg utility" YES)
option(enable-gdk-pixbuf "Use Pixbuf to support more image formats" YES)
option(enable-tray "Enables the swaybar tray" YES)
option(zsh-completions "Zsh shell completions" NO)
option(default-wallpaper "Installs the default wallpaper" YES)
option(LD_LIBRARY_PATH "Configure sway's default LD_LIBRARY_PATH")
@ -64,6 +65,7 @@ find_package(Cairo REQUIRED)
find_package(Pango REQUIRED)
find_package(GdkPixbuf)
find_package(PAM)
find_package(DBus)
find_package(LibInput REQUIRED)
@ -90,6 +92,17 @@ else()
message(STATUS "Building without gdk-pixbuf, only png images supported.")
endif()
if (enable-tray)
if (DBUS_FOUND)
set(ENABLE_TRAY)
add_definitions(-DENABLE_TRAY)
else()
message(WARNING "Tray required but DBus was not found. Tray will not be included")
endif()
else()
message(STATUS "Building without the tray.")
endif()
include_directories(include)
add_subdirectory(protocols)

@ -6,6 +6,8 @@
void cairo_set_source_u32(cairo_t *cairo, uint32_t color);
cairo_surface_t *cairo_image_surface_scale(cairo_surface_t *image, int width, int height);
#ifdef WITH_GDK_PIXBUF
#include <gdk-pixbuf/gdk-pixbuf.h>

@ -157,17 +157,21 @@ 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_mode;
sway_cmd bar_cmd_modifier;
sway_cmd bar_cmd_output;
sway_cmd bar_cmd_height;
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;

@ -133,7 +133,17 @@ struct bar_config {
char *swaybar_command;
char *font;
int height; // -1 not defined
int tray_padding;
#ifdef ENABLE_TRAY
// Tray
char *tray_output;
char *icon_theme;
uint32_t tray_padding;
uint32_t activate_button;
uint32_t context_button;
uint32_t secondary_button;
#endif
bool workspace_buttons;
bool wrap_scroll;
char *separator_symbol;

@ -21,6 +21,9 @@ struct output {
struct window *window;
struct registry *registry;
list_t *workspaces;
#ifdef ENABLE_TRAY
list_t *items;
#endif
char *name;
int idx;
bool focused;
@ -37,6 +40,9 @@ struct workspace {
/** Global bar state */
extern struct bar swaybar;
/** True if sway needs to render */
extern bool dirty;
/**
* Setup bar.
*/

@ -33,6 +33,17 @@ struct config {
bool all_outputs;
list_t *outputs;
#ifdef ENABLE_TRAY
// Tray
char *tray_output;
char *icon_theme;
uint32_t tray_padding;
uint32_t activate_button;
uint32_t context_button;
uint32_t secondary_button;
#endif
int height;
struct {

@ -0,0 +1,26 @@
#ifndef _SWAYBAR_EVENT_LOOP_H
#define _SWAYBAR_EVENT_LOOP_H
#include <stdbool.h>
#include <time.h>
void add_event(int fd, short mask,
void(*cb)(int fd, short mask, void *data),
void *data);
// Not guaranteed to notify cb immediately
void add_timer(timer_t timer,
void(*cb)(timer_t timer, void *data),
void *data);
// Returns false if nothing exists, true otherwise
bool remove_event(int fd);
// Returns false if nothing exists, true otherwise
bool remove_timer(timer_t timer);
// Blocks and returns after sending callbacks
void event_loop_poll();
void init_event_loop();
#endif /*_SWAYBAR_EVENT_LOOP_H */

@ -0,0 +1,18 @@
#ifndef _SWAYBAR_DBUS_H
#define _SWAYBAR_DBUS_H
#include <stdbool.h>
#include <dbus/dbus.h>
extern DBusConnection *conn;
/**
* Should be called in main loop to dispatch events
*/
void dispatch_dbus();
/**
* Initializes async dbus communication
*/
int dbus_init();
#endif /* _SWAYBAR_DBUS_H */

@ -0,0 +1,16 @@
#ifndef _SWAYBAR_ICON_H
#define _SWAYBAR_ICON_H
#include <stdint.h>
#include <stdbool.h>
#include <client/cairo.h>
/**
* Returns the image found by `name` that is closest to `size`
*/
cairo_surface_t *find_icon(const char *name, int size);
/* Struct used internally only */
struct subdir;
#endif /* _SWAYBAR_ICON_H */

@ -0,0 +1,81 @@
#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)
*/
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 */

@ -0,0 +1,10 @@
#ifndef _SWAYBAR_SNI_WATCHER_H
#define _SWAYBAR_SNI_WATCHER_H
/**
* Starts the sni_watcher, the watcher is practically a black box and should
* only be accessed though functions described in its spec
*/
int init_sni_watcher();
#endif /* _SWAYBAR_SNI_WATCHER_H */

@ -0,0 +1,26 @@
#ifndef _SWAYBAR_TRAY_H
#define _SWAYBAR_TRAY_H
#include <stdint.h>
#include <stdbool.h>
#include "swaybar/tray/dbus.h"
#include "swaybar/tray/sni.h"
#include "list.h"
extern struct tray *tray;
struct tray {
list_t *items;
};
/**
* Initializes the tray host with D-Bus
*/
int init_tray();
/**
* Returns an item if `x` and `y` collide with it and NULL otherwise
*/
struct StatusNotifierItem *collides_with_sni(int x, int y);
#endif /* _SWAYBAR_TRAY_H */

@ -221,18 +221,22 @@ static struct cmd_handler handlers[] = {
};
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 },
{ "height", bar_cmd_height },
{ "hidden_state", bar_cmd_hidden_state },
{ "icon_theme", bar_cmd_icon_theme },
{ "id", bar_cmd_id },
{ "mode", bar_cmd_mode },
{ "modifier", bar_cmd_modifier },
{ "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_numbers", bar_cmd_strip_workspace_numbers },

@ -0,0 +1,26 @@
#include <stdlib.h>
#include "sway/commands.h"
#include "log.h"
struct cmd_results *bar_cmd_activate_button(int argc, char **argv) {
const char *cmd_name = "activate_button";
#ifndef ENABLE_TRAY
return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command "
"%s called, but sway was compiled without tray support",
cmd_name, cmd_name);
#else
struct cmd_results *error = NULL;
if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) {
return error;
}
if (!config->current_bar) {
return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
}
// User should be able to prefix with 0x or whatever they want
config->current_bar->secondary_button = strtoul(argv[0], NULL, 0);
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
#endif
}

@ -0,0 +1,26 @@
#include <stdlib.h>
#include "sway/commands.h"
#include "log.h"
struct cmd_results *bar_cmd_context_button(int argc, char **argv) {
const char *cmd_name = "context_button";
#ifndef ENABLE_TRAY
return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command "
"%s called, but sway was compiled without tray support",
cmd_name, cmd_name);
#else
struct cmd_results *error = NULL;
if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) {
return error;
}
if (!config->current_bar) {
return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
}
// User should be able to prefix with 0x or whatever they want
config->current_bar->context_button = strtoul(argv[0], NULL, 0);
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
#endif
}

@ -0,0 +1,25 @@
#define _XOPEN_SOURCE 500
#include <string.h>
#include "sway/commands.h"
struct cmd_results *bar_cmd_icon_theme(int argc, char **argv) {
const char *cmd_name = "tray_output";
#ifndef ENABLE_TRAY
return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command "
"%s called, but sway was compiled without tray support",
cmd_name, cmd_name);
#else
struct cmd_results *error = NULL;
if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) {
return error;
}
if (!config->current_bar) {
return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
}
config->current_bar->icon_theme = strdup(argv[0]);
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
#endif
}

@ -0,0 +1,26 @@
#include <stdlib.h>
#include "sway/commands.h"
#include "log.h"
struct cmd_results *bar_cmd_secondary_button(int argc, char **argv) {
const char *cmd_name = "secondary_button";
#ifndef ENABLE_TRAY
return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command "
"%s called, but sway was compiled without tray support",
cmd_name, cmd_name);
#else
struct cmd_results *error = NULL;
if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) {
return error;
}
if (!config->current_bar) {
return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
}
// User should be able to prefix with 0x or whatever they want
config->current_bar->secondary_button = strtoul(argv[0], NULL, 0);
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
#endif
}

@ -1,7 +1,29 @@
#define _XOPEN_SOURCE 500
#include <string.h>
#include "sway/commands.h"
#include "log.h"
struct cmd_results *bar_cmd_tray_output(int argc, char **argv) {
sway_log(L_ERROR, "Warning: tray_output is not supported on wayland");
const char *cmd_name = "tray_output";
#ifndef ENABLE_TRAY
return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command "
"%s called, but sway was compiled without tray support",
cmd_name, cmd_name);
#else
struct cmd_results *error = NULL;
if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) {
return error;
}
if (!config->current_bar) {
return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
}
if (strcmp(argv[0], "all") == 0) {
// Default behaviour
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
}
config->current_bar->tray_output = strdup(argv[0]);
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
#endif
}

@ -1,30 +1,34 @@
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "sway/commands.h"
#include "log.h"
struct cmd_results *bar_cmd_tray_padding(int argc, char **argv) {
const char *cmd_name = "tray_padding";
#ifndef ENABLE_TRAY
return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command"
"%s called, but sway was compiled without tray support",
cmd_name, cmd_name);
#else
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "tray_padding", EXPECTED_AT_LEAST, 1))) {
if ((error = checkarg(argc, cmd_name, EXPECTED_AT_LEAST, 1))) {
return error;
}
if (!config->current_bar) {
return cmd_results_new(CMD_FAILURE, "tray_padding", "No bar defined.");
return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
}
int padding = atoi(argv[0]);
if (padding < 0) {
return cmd_results_new(CMD_INVALID, "tray_padding",
"Invalid padding value %s, minimum is 0", argv[0]);
if (argc == 1 || (argc == 2 && strcasecmp("px", argv[1]) == 0)) {
char *inv;
uint32_t padding = strtoul(argv[0], &inv, 10);
if (*inv == '\0' || strcasecmp(inv, "px") == 0) {
config->current_bar->tray_padding = padding;
sway_log(L_DEBUG, "Enabling tray padding of %d px on bar: %s", padding, config->current_bar->id);
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
}
}
if (argc > 1 && strcasecmp("px", argv[1]) != 0) {
return cmd_results_new(CMD_INVALID, "tray_padding",
"Unknown unit %s", argv[1]);
}
config->current_bar->tray_padding = padding;
sway_log(L_DEBUG, "Enabling tray padding of %d px on bar: %s", padding, config->current_bar->id);
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
return cmd_results_new(CMD_FAILURE, cmd_name,
"Expected 'tray_padding <padding>[px]'");
#endif
}

@ -69,6 +69,10 @@ static void free_bar(struct bar_config *bar) {
}
free(bar->mode);
free(bar->hidden_state);
#ifdef ENABLE_TRAY
free(bar->tray_output);
free(bar->icon_theme);
#endif
free(bar->status_command);
free(bar->font);
free(bar->separator_symbol);
@ -1386,7 +1390,14 @@ struct bar_config *default_bar_config(void) {
bar->separator_symbol = NULL;
bar->strip_workspace_numbers = false;
bar->binding_mode_indicator = true;
#ifdef ENABLE_TRAY
bar->tray_output = NULL;
bar->icon_theme = NULL;
bar->tray_padding = 2;
bar->activate_button = 0x110; /* BTN_LEFT */
bar->context_button = 0x111; /* BTN_RIGHT */
bar->secondary_button = 0x112; /* BTN_MIDDLE */
#endif
bar->verbose = false;
bar->pid = 0;
// set default colors

@ -327,7 +327,22 @@ json_object *ipc_json_describe_bar_config(struct bar_config *bar) {
json_object *json = json_object_new_object();
json_object_object_add(json, "id", json_object_new_string(bar->id));
json_object_object_add(json, "tray_output", NULL);
#ifdef ENABLE_TRAY
if (bar->tray_output) {
json_object_object_add(json, "tray_output", json_object_new_string(bar->tray_output));
} else {
json_object_object_add(json, "tray_output", NULL);
}
if (bar->icon_theme) {
json_object_object_add(json, "icon_theme", json_object_new_string(bar->icon_theme));
} else {
json_object_object_add(json, "icon_theme", NULL);
}
json_object_object_add(json, "tray_padding", json_object_new_int(bar->tray_padding));
json_object_object_add(json, "activate_button", json_object_new_int(bar->activate_button));
json_object_object_add(json, "context_button", json_object_new_int(bar->context_button));
json_object_object_add(json, "secondary_button", json_object_new_int(bar->secondary_button));
#endif
json_object_object_add(json, "mode", json_object_new_string(bar->mode));
json_object_object_add(json, "hidden_state", json_object_new_string(bar->hidden_state));
json_object_object_add(json, "modifier", json_object_new_string(get_modifier_name_by_mask(bar->modifier)));

@ -65,6 +65,42 @@ Commands
**height** <height>::
Sets the height of the bar. Default height will match the font size.
Tray
----
Swaybar provides a system tray where programs such as NetworkManager, VLC,
Pidgin, etc. can place little icons. The following commands configure
interaction with the tray or individual icons.
The _button_ argument in all following commands is a Linux input event code as
defined in linux/input-event-codes.h. This is because wayland defines button
codes in this manner.
**activate_button** <button>::
Sets the button to be used for the _activate_ (primary click) tray item
event. By default is BTN_LEFT (0x110).
**context_button** <button>::
Sets the button to be used for the _context menu_ (right click) tray item
event. By default is BTN_RIGHT (0x111).
**secondary_button** <button>::
Sets the button to be used for the _secondary_ (middle click) tray item
event. By default is BTN_MIDDLE (0x112).
**tray_output** none|all|<name>::
Sets the output that the tray will appear on or none. Unlike i3bar, swaybar
should be able to show icons on any number of bars and outputs without
races. Because of this, the default value for this is _all_.
**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.
**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,
hicolor.
Colors
------

@ -5,7 +5,13 @@ include_directories(
${PANGO_INCLUDE_DIRS}
${JSONC_INCLUDE_DIRS}
${XKBCOMMON_INCLUDE_DIRS}
${DBUS_INCLUDE_DIRS}
)
if (enable-tray)
file(GLOB tray
tray/*.c
)
endif()
add_executable(swaybar
main.c
@ -14,6 +20,8 @@ add_executable(swaybar
bar.c
status_line.c
ipc.c
event_loop.c
${tray}
)
target_link_libraries(swaybar
@ -24,6 +32,7 @@ target_link_libraries(swaybar
${CAIRO_LIBRARIES}
${PANGO_LIBRARIES}
${JSONC_LIBRARIES}
${DBUS_LIBRARIES}
)
if (WITH_GDK_PIXBUF)
@ -32,6 +41,8 @@ if (WITH_GDK_PIXBUF)
)
endif()
target_link_libraries(swaybar rt)
install(
TARGETS swaybar
RUNTIME

@ -7,10 +7,17 @@
#include <sys/wait.h>
#include <signal.h>
#include <poll.h>
#ifdef ENABLE_TRAY
#include <dbus/dbus.h>
#include "swaybar/tray/sni_watcher.h"
#include "swaybar/tray/tray.h"
#include "swaybar/tray/sni.h"
#endif
#include "swaybar/ipc.h"
#include "swaybar/render.h"
#include "swaybar/config.h"
#include "swaybar/status_line.h"
#include "swaybar/event_loop.h"
#include "swaybar/bar.h"
#include "ipc-client.h"
#include "list.h"
@ -50,18 +57,39 @@ static void spawn_status_cmd_proc(struct bar *bar) {
}
}
#ifdef ENABLE_TRAY
static void spawn_xembed_sni_proxy() {
pid_t pid = fork();
if (pid == 0) {
int wstatus;
do {
pid = fork();
if (pid == 0) {
execlp("xembedsniproxy", "xembedsniproxy", NULL);
_exit(EXIT_FAILURE);
}
waitpid(pid, &wstatus, 0);
} while (!WIFEXITED(wstatus));
_exit(EXIT_FAILURE);
}
}
#endif
struct output *new_output(const char *name) {
struct output *output = malloc(sizeof(struct output));
output->name = strdup(name);
output->window = NULL;
output->registry = NULL;
output->workspaces = create_list();
#ifdef ENABLE_TRAY
output->items = create_list();
#endif
return output;
}
static void mouse_button_notify(struct window *window, int x, int y,
uint32_t button, uint32_t state_w) {
sway_log(L_DEBUG, "Mouse button %d clicked at %d %d %d\n", button, x, y, state_w);
sway_log(L_DEBUG, "Mouse button %d clicked at %d %d %d", button, x, y, state_w);
if (!state_w) {
return;
}
@ -92,6 +120,30 @@ static void mouse_button_notify(struct window *window, int x, int y,
break;
}
}
#ifdef ENABLE_TRAY
uint32_t tray_padding = swaybar.config->tray_padding;
int tray_width = window->width * window->scale;
for (int i = 0; i < clicked_output->items->length; ++i) {
struct sni_icon_ref *item =
clicked_output->items->items[i];
int icon_width = cairo_image_surface_get_width(item->icon);
tray_width -= tray_padding;
if (x <= tray_width && x >= tray_width - icon_width) {
if (button == swaybar.config->activate_button) {
sni_activate(item->ref, x, y);
} else if (button == swaybar.config->context_button) {
sni_context_menu(item->ref, x, y);
} else if (button == swaybar.config->secondary_button) {
sni_secondary(item->ref, x, y);
}
break;
}
tray_width -= icon_width;
}
#endif
}
static void mouse_scroll_notify(struct window *window, enum scroll_direction direction) {
@ -136,6 +188,9 @@ void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id) {
/* initialize bar with default values */
bar_init(bar);
/* Initialize event loop lists */
init_event_loop();
/* connect to sway ipc */
bar->ipc_socketfd = ipc_open_socket(socket_path);
bar->ipc_event_socketfd = ipc_open_socket(socket_path);
@ -178,23 +233,54 @@ void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id) {
}
/* spawn status command */
spawn_status_cmd_proc(bar);
#ifdef ENABLE_TRAY
// We should have at least one output to serve the tray to
if (!swaybar.config->tray_output || strcmp(swaybar.config->tray_output, "none") != 0) {
/* Connect to the D-Bus */
dbus_init();
/* Start the SNI watcher */
init_sni_watcher();
/* Start the SNI host */
init_tray();
/* Start xembedsniproxy */
spawn_xembed_sni_proxy();
}
#endif
}
bool dirty = true;
static void respond_ipc(int fd, short mask, void *_bar) {
struct bar *bar = (struct bar *)_bar;
sway_log(L_DEBUG, "Got IPC event.");
dirty = handle_ipc_event(bar);
}
static void respond_command(int fd, short mask, void *_bar) {
struct bar *bar = (struct bar *)_bar;
dirty = handle_status_line(bar);
}
static void respond_output(int fd, short mask, void *_output) {
struct output *output = (struct output *)_output;
if (wl_display_dispatch(output->registry->display) == -1) {
sway_log(L_ERROR, "failed to dispatch wl: %d", errno);
}
}
void bar_run(struct bar *bar) {
int pfds = bar->outputs->length + 2;
struct pollfd *pfd = malloc(pfds * sizeof(struct pollfd));
bool dirty = true;
pfd[0].fd = bar->ipc_event_socketfd;
pfd[0].events = POLLIN;
pfd[1].fd = bar->status_read_fd;
pfd[1].events = POLLIN;
add_event(bar->ipc_event_socketfd, POLLIN, respond_ipc, bar);
add_event(bar->status_read_fd, POLLIN, respond_command, bar);
int i;
for (i = 0; i < bar->outputs->length; ++i) {
struct output *output = bar->outputs->items[i];
pfd[i+2].fd = wl_display_get_fd(output->registry->display);
pfd[i+2].events = POLLIN;
add_event(wl_display_get_fd(output->registry->display),
POLLIN, respond_output, output);
}
while (1) {
@ -212,29 +298,10 @@ void bar_run(struct bar *bar) {
dirty = false;
poll(pfd, pfds, -1);
if (pfd[0].revents & POLLIN) {
sway_log(L_DEBUG, "Got IPC event.");
dirty = handle_ipc_event(bar);
}
if (bar->config->status_command && pfd[1].revents & POLLIN) {
sway_log(L_DEBUG, "Got update from status command.");
dirty = handle_status_line(bar);
}
// dispatch wl_display events
for (i = 0; i < bar->outputs->length; ++i) {
struct output *output = bar->outputs->items[i];
if (pfd[i+2].revents & POLLIN) {
if (wl_display_dispatch(output->registry->display) == -1) {
sway_log(L_ERROR, "failed to dispatch wl: %d", errno);
}
} else {
wl_display_dispatch_pending(output->registry->display);
}
}
event_loop_poll();
#ifdef ENABLE_TRAY
dispatch_dbus();
#endif
}
}

@ -48,6 +48,18 @@ struct config *init_config() {
/* height */
config->height = 0;
#ifdef ENABLE_TRAY
config->tray_output = NULL;
config->icon_theme = NULL;
config->tray_padding = 2;
/**
* These constants are used by wayland and are defined in
* linux/input-event-codes.h
*/
config->activate_button = 0x110; /* BTN_LEFT */
config->context_button = 0x111; /* BTN_RIGHT */
#endif
/* colors */
config->colors.background = 0x000000FF;
config->colors.statusline = 0xFFFFFFFF;

143
swaybar/event_loop.c Normal file

@ -0,0 +1,143 @@
#define _XOPEN_SOURCE 500
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <poll.h>
#include "swaybar/bar.h"
#include "swaybar/event_loop.h"
#include "list.h"
#include "log.h"
struct event_item {
void(*cb)(int fd, short mask, void *data);
void *data;
};
struct timer_item {
timer_t timer;
void(*cb)(timer_t timer, void *data);
void *data;
};
static struct {
// The order of each must be kept consistent
struct { /* pollfd array */
struct pollfd *items;
int capacity;
int length;
} fds;
list_t *items; /* event_item list */
// Timer list
list_t *timers;
} event_loop;
void add_timer(timer_t timer,
void(*cb)(timer_t timer, void *data),
void *data) {
struct timer_item *item = malloc(sizeof(struct timer_item));
item->timer = timer;
item->cb = cb;
item->data = data;
list_add(event_loop.timers, item);
}
void add_event(int fd, short mask,
void(*cb)(int fd, short mask, void *data), void *data) {
struct pollfd pollfd = {
fd,
mask,
0,
};
// Resize
if (event_loop.fds.length == event_loop.fds.capacity) {
event_loop.fds.capacity += 10;
event_loop.fds.items = realloc(event_loop.fds.items,
sizeof(struct pollfd) * event_loop.fds.capacity);
}
event_loop.fds.items[event_loop.fds.length++] = pollfd;
struct event_item *item = malloc(sizeof(struct event_item));
item->cb = cb;
item->data = data;
list_add(event_loop.items, item);
return;
}
bool remove_event(int fd) {
int index = -1;
for (int i = 0; i < event_loop.fds.length; ++i) {
if (event_loop.fds.items[i].fd == fd) {
index = i;
}
}
if (index != -1) {
free(event_loop.items->items[index]);
--event_loop.fds.length;
memmove(&event_loop.fds.items[index], &event_loop.fds.items[index + 1],
sizeof(struct pollfd) * event_loop.fds.length - index);
list_del(event_loop.items, index);
return true;
} else {
return false;
}
}
static int timer_item_timer_cmp(const void *_timer_item, const void *_timer) {
const struct timer_item *timer_item = _timer_item;
const timer_t *timer = _timer;
if (timer_item->timer == timer) {
return 0;
} else {
return -1;
}
}
bool remove_timer(timer_t timer) {
int index = list_seq_find(event_loop.timers, timer_item_timer_cmp, &timer);
if (index != -1) {
list_del(event_loop.timers, index);
return true;
}
return false;
}
void event_loop_poll() {
poll(event_loop.fds.items, event_loop.fds.length, -1);
for (int i = 0; i < event_loop.fds.length; ++i) {
struct pollfd pfd = event_loop.fds.items[i];
struct event_item *item = (struct event_item *)event_loop.items->items[i];
if (pfd.revents & pfd.events) {
item->cb(pfd.fd, pfd.revents, item->data);
}
}
// check timers
// not tested, but seems to work
for (int i = 0; i < event_loop.timers->length; ++i) {
struct timer_item *item = event_loop.timers->items[i];
int overrun = timer_getoverrun(item->timer);
if (overrun && overrun != -1) {
item->cb(item->timer, item->data);
}
}
}
void init_event_loop() {
event_loop.fds.length = 0;
event_loop.fds.capacity = 10;
event_loop.fds.items = malloc(event_loop.fds.capacity * sizeof(struct pollfd));
event_loop.items = create_list();
event_loop.timers = create_list();
}

@ -19,11 +19,19 @@ void ipc_send_workspace_command(const char *workspace_name) {
static void ipc_parse_config(struct config *config, const char *payload) {
json_object *bar_config = json_tokener_parse(payload);
json_object *tray_output, *mode, *hidden_bar, *position, *status_command;
json_object *markup, *mode, *hidden_bar, *position, *status_command;
json_object *font, *bar_height, *wrap_scroll, *workspace_buttons, *strip_workspace_numbers;
json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol, *outputs;
json_object *markup;
#ifdef ENABLE_TRAY
json_object *tray_output, *icon_theme, *tray_padding, *activate_button, *context_button;
json_object *secondary_button;
json_object_object_get_ex(bar_config, "tray_output", &tray_output);
json_object_object_get_ex(bar_config, "icon_theme", &icon_theme);
json_object_object_get_ex(bar_config, "tray_padding", &tray_padding);
json_object_object_get_ex(bar_config, "activate_button", &activate_button);
json_object_object_get_ex(bar_config, "context_button", &context_button);
json_object_object_get_ex(bar_config, "secondary_button", &secondary_button);
#endif
json_object_object_get_ex(bar_config, "mode", &mode);
json_object_object_get_ex(bar_config, "hidden_bar", &hidden_bar);
json_object_object_get_ex(bar_config, "position", &position);
@ -83,6 +91,34 @@ static void ipc_parse_config(struct config *config, const char *payload) {
config->pango_markup = json_object_get_boolean(markup);
}
#ifdef ENABLE_TRAY
if (tray_output) {
free(config->tray_output);
config->tray_output = strdup(json_object_get_string(tray_output));
}
if (icon_theme) {
free(config->icon_theme);
config->icon_theme = strdup(json_object_get_string(icon_theme));
}
if (tray_padding) {
config->tray_padding = json_object_get_int(tray_padding);
}
if (activate_button) {
config->activate_button = json_object_get_int(activate_button);
}
if (context_button) {
config->context_button = json_object_get_int(context_button);
}
if (secondary_button) {
config->secondary_button = json_object_get_int(secondary_button);
}
#endif
// free previous outputs list
int i;
for (i = 0; i < config->outputs->length; ++i) {

@ -8,6 +8,10 @@
#include "swaybar/config.h"
#include "swaybar/status_line.h"
#include "swaybar/render.h"
#ifdef ENABLE_TRAY
#include "swaybar/tray/tray.h"
#include "swaybar/tray/sni.h"
#endif
#include "log.h"
@ -297,6 +301,72 @@ void render(struct output *output, struct config *config, struct status_line *li
}
cairo_paint(cairo);
#ifdef ENABLE_TRAY
// Tray icons
uint32_t tray_padding = config->tray_padding;
unsigned int tray_width = window->width * window->scale;
const int item_size = (window->height * window->scale) - (2 * tray_padding);
if (item_size < 0) {
// Can't render items if the padding is too large
goto no_tray;
}
if (config->tray_output && strcmp(config->tray_output, output->name) != 0) {
goto no_tray;
}
for (int i = 0; i < tray->items->length; ++i) {
struct StatusNotifierItem *item =
tray->items->items[i];
if (!item->image) {
continue;
}
struct sni_icon_ref *render_item = NULL;
int j;
for (j = i; j < output->items->length; ++j) {
struct sni_icon_ref *ref =
output->items->items[j];
if (ref->ref == item) {
render_item = ref;
break;
} else {
sni_icon_ref_free(ref);
list_del(output->items, j);
}
}
if (!render_item) {
render_item = sni_icon_ref_create(item, item_size);
list_add(output->items, render_item);
} else if (item->dirty) {
// item needs re-render
sni_icon_ref_free(render_item);
output->items->items[j] = render_item =
sni_icon_ref_create(item, item_size);
}
tray_width -= tray_padding;
tray_width -= item_size;
cairo_set_source_surface(cairo, render_item->icon, tray_width, tray_padding);
cairo_rectangle(cairo, tray_width, tray_padding, item_size, item_size);
cairo_fill(cairo);
item->dirty = false;
}
if (tray_width != window->width * window->scale) {
tray_width -= tray_padding;
}
no_tray:
#else
const int tray_width = window->width * window->scale;
#endif
// Command output
if (is_focused) {
cairo_set_source_u32(cairo, config->colors.focused_statusline);
@ -309,12 +379,11 @@ void render(struct output *output, struct config *config, struct status_line *li
if (line->protocol == TEXT) {
get_text_size(window->cairo, window->font, &width, &height,
window->scale, config->pango_markup, "%s", line->text_line);
cairo_move_to(cairo, (window->width * window->scale)
- margin - width, margin);
cairo_move_to(cairo, tray_width - margin - width, margin);
pango_printf(window->cairo, window->font, window->scale,
config->pango_markup, "%s", line->text_line);
} else if (line->protocol == I3BAR && line->block_line) {
double pos = (window->width * window->scale) - 0.5;
double pos = tray_width - 0.5;
bool edge = true;
for (i = line->block_line->length - 1; i >= 0; --i) {
struct status_block *block = line->block_line->items[i];

189
swaybar/tray/dbus.c Normal file

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

404
swaybar/tray/icon.c Normal file

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

463
swaybar/tray/sni.c Normal file

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

487
swaybar/tray/sni_watcher.c Normal file

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

279
swaybar/tray/tray.c Normal file

@ -0,0 +1,279 @@
#define _XOPEN_SOURCE 500
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <dbus/dbus.h>
#include "swaybar/bar.h"
#include "swaybar/tray/tray.h"
#include "swaybar/tray/dbus.h"
#include "swaybar/tray/sni.h"
#include "swaybar/bar.h"
#include "list.h"
#include "log.h"
struct tray *tray;
static void register_host(char *name) {
DBusMessage *message;
message = dbus_message_new_method_call(
"org.freedesktop.StatusNotifierWatcher",
"/StatusNotifierWatcher",
"org.freedesktop.StatusNotifierWatcher",
"RegisterStatusNotifierHost");
if (!message) {
sway_log(L_ERROR, "Cannot allocate dbus method call");
return;
}
dbus_message_append_args(message,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID);
dbus_connection_send(conn, message, NULL);
dbus_message_unref(message);
}
static void get_items_reply(DBusPendingCall *pending, void *_data) {
DBusMessage *reply = dbus_pending_call_steal_reply(pending);
if (!reply) {
sway_log(L_ERROR, "Got no items reply from sni watcher");
goto bail;
}
int message_type = dbus_message_get_type(reply);
if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
char *msg;
dbus_message_get_args(reply, NULL,
DBUS_TYPE_STRING, &msg,
DBUS_TYPE_INVALID);
sway_log(L_ERROR, "Message is error: %s", msg);
goto bail;
}
DBusMessageIter iter;
DBusMessageIter variant;
DBusMessageIter array;
dbus_message_iter_init(reply, &iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
goto bail;
}
dbus_message_iter_recurse(&iter, &variant);
if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_ARRAY ||
dbus_message_iter_get_element_type(&variant) != DBUS_TYPE_STRING) {
sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
goto bail;
}
// Clear list
list_foreach(tray->items, (void (*)(void *))sni_free);
list_free(tray->items);
tray->items = create_list();
// O(n) function, could be faster dynamically reading values
int len = dbus_message_iter_get_element_count(&variant);
dbus_message_iter_recurse(&variant, &array);
for (int i = 0; i < len; i++) {
const char *name;
dbus_message_iter_get_basic(&array, &name);
struct StatusNotifierItem *item = sni_create(name);
sway_log(L_DEBUG, "Item registered with host: %s", name);
list_add(tray->items, item);
dirty = true;
}
bail:
dbus_message_unref(reply);
return;
}
static void get_items() {
DBusPendingCall *pending;
DBusMessage *message = dbus_message_new_method_call(
"org.freedesktop.StatusNotifierWatcher",
"/StatusNotifierWatcher",
"org.freedesktop.DBus.Properties",
"Get");
const char *iface = "org.freedesktop.StatusNotifierWatcher";
const char *prop = "RegisteredStatusNotifierItems";
dbus_message_append_args(message,
DBUS_TYPE_STRING, &iface,
DBUS_TYPE_STRING, &prop,
DBUS_TYPE_INVALID);
bool status =
dbus_connection_send_with_reply(conn, message, &pending, -1);
dbus_message_unref(message);
if (!(pending || status)) {
sway_log(L_ERROR, "Could not get items");
return;
}
dbus_pending_call_set_notify(pending, get_items_reply, NULL, NULL);
}
static DBusHandlerResult signal_handler(DBusConnection *connection,
DBusMessage *message, void *_data) {
if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
"StatusNotifierItemRegistered")) {
const char *name;
if (!dbus_message_get_args(message, NULL,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID)) {
sway_log(L_ERROR, "Error getting StatusNotifierItemRegistered args");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
if (list_seq_find(tray->items, sni_str_cmp, name) == -1) {
struct StatusNotifierItem *item = sni_create(name);
list_add(tray->items, item);
dirty = true;
}
return DBUS_HANDLER_RESULT_HANDLED;
} else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
"StatusNotifierItemUnregistered")) {
const char *name;
if (!dbus_message_get_args(message, NULL,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID)) {
sway_log(L_ERROR, "Error getting StatusNotifierItemUnregistered args");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
int index;
if ((index = list_seq_find(tray->items, sni_str_cmp, name)) != -1) {
sni_free(tray->items->items[index]);
list_del(tray->items, index);
dirty = true;
} else {
// If it's not in our list, then our list is incorrect.
// Fetch all items again
sway_log(L_INFO, "Host item list incorrect, refreshing");
get_items();
}
return DBUS_HANDLER_RESULT_HANDLED;
} else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierItem",
"NewIcon") || dbus_message_is_signal(message,
"org.kde.StatusNotifierItem", "NewIcon")) {
const char *name;
int index;
struct StatusNotifierItem *item;
name = dbus_message_get_sender(message);
if ((index = list_seq_find(tray->items, sni_uniq_cmp, name)) != -1) {
item = tray->items->items[index];
get_icon(item);
}
return DBUS_HANDLER_RESULT_HANDLED;
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
int init_tray() {
tray = (struct tray *)malloc(sizeof(tray));
tray->items = create_list();
DBusError error;
dbus_error_init(&error);
char *name = NULL;
if (!conn) {
sway_log(L_ERROR, "Connection is null, cannot init SNI host");
goto err;
}
name = calloc(sizeof(char), 256);
if (!name) {
sway_log(L_ERROR, "Cannot allocate name");
goto err;
}
pid_t pid = getpid();
if (snprintf(name, 256, "org.freedesktop.StatusNotifierHost-%d", pid)
>= 256) {
sway_log(L_ERROR, "Cannot get host name because string is too short."
"This should not happen");
goto err;
}
// We want to be the sole owner of this name
if (dbus_bus_request_name(conn, name, DBUS_NAME_FLAG_DO_NOT_QUEUE,
&error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
sway_log(L_ERROR, "Cannot get host name and start the tray");
goto err;
}
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "Dbus err getting host name: %s\n", error.message);
goto err;
}
sway_log(L_DEBUG, "Got host name");
register_host(name);
get_items();
// Perhaps use addmatch helper functions like wlc does?
dbus_bus_add_match(conn,
"type='signal',\
sender='org.freedesktop.StatusNotifierWatcher',\
member='StatusNotifierItemRegistered'",
&error);
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus_err: %s", error.message);
goto err;
}
dbus_bus_add_match(conn,
"type='signal',\
sender='org.freedesktop.StatusNotifierWatcher',\
member='StatusNotifierItemUnregistered'",
&error);
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus_err: %s", error.message);
return -1;
}
// SNI matches
dbus_bus_add_match(conn,
"type='signal',\
interface='org.freedesktop.StatusNotifierItem',\
member='NewIcon'",
&error);
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus_err %s", error.message);
goto err;
}
dbus_bus_add_match(conn,
"type='signal',\
interface='org.kde.StatusNotifierItem',\
member='NewIcon'",
&error);
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus_err %s", error.message);
goto err;
}
dbus_connection_add_filter(conn, signal_handler, NULL, NULL);
free(name);
return 0;
err:
// TODO better handle errors
free(name);
return -1;
}

@ -8,6 +8,25 @@ void cairo_set_source_u32(cairo_t *cairo, uint32_t color) {
(color >> (0*8) & 0xFF) / 255.0);
}
cairo_surface_t *cairo_image_surface_scale(cairo_surface_t *image, int width, int height) {
int image_width = cairo_image_surface_get_width(image);
int image_height = cairo_image_surface_get_height(image);
cairo_surface_t *new =
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
cairo_t *cairo = cairo_create(new);
cairo_scale(cairo, (double) width / image_width, (double) height / image_height);
cairo_set_source_surface(cairo, image, 0, 0);
cairo_paint(cairo);
cairo_destroy(cairo);
return new;
}
#ifdef WITH_GDK_PIXBUF
#include <gdk-pixbuf/gdk-pixbuf.h>