aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Calvin Lee <cyrus296@gmail.com>2017-06-07 16:45:28 -0700
committerLibravatar Calvin Lee <cyrus296@gmail.com>2017-06-07 17:49:16 -0700
commit843ad38b3c427adb0bf319e9613d9813c8d9246c (patch)
treee02a5b06e2b6923371fd53724791c147c18a1fa4
parentMerge pull request #1232 from johalun/master-freebsd (diff)
downloadsway-843ad38b3c427adb0bf319e9613d9813c8d9246c.tar.gz
sway-843ad38b3c427adb0bf319e9613d9813c8d9246c.tar.zst
sway-843ad38b3c427adb0bf319e9613d9813c8d9246c.zip
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
-rw-r--r--CMake/FindDBus.cmake59
-rw-r--r--CMakeLists.txt13
-rw-r--r--include/client/cairo.h2
-rw-r--r--include/sway/commands.h4
-rw-r--r--include/sway/config.h12
-rw-r--r--include/swaybar/bar.h6
-rw-r--r--include/swaybar/config.h11
-rw-r--r--include/swaybar/event_loop.h26
-rw-r--r--include/swaybar/tray/dbus.h18
-rw-r--r--include/swaybar/tray/icon.h16
-rw-r--r--include/swaybar/tray/sni.h81
-rw-r--r--include/swaybar/tray/sni_watcher.h10
-rw-r--r--include/swaybar/tray/tray.h26
-rw-r--r--sway/commands.c4
-rw-r--r--sway/commands/bar/activate_button.c26
-rw-r--r--sway/commands/bar/context_button.c26
-rw-r--r--sway/commands/bar/icon_theme.c25
-rw-r--r--sway/commands/bar/secondary_button.c26
-rw-r--r--sway/commands/bar/tray_output.c26
-rw-r--r--sway/commands/bar/tray_padding.c34
-rw-r--r--sway/config.c11
-rw-r--r--sway/ipc-json.c17
-rw-r--r--sway/sway-bar.5.txt36
-rw-r--r--swaybar/CMakeLists.txt11
-rw-r--r--swaybar/bar.c135
-rw-r--r--swaybar/config.c12
-rw-r--r--swaybar/event_loop.c143
-rw-r--r--swaybar/ipc.c40
-rw-r--r--swaybar/render.c75
-rw-r--r--swaybar/tray/dbus.c189
-rw-r--r--swaybar/tray/icon.c404
-rw-r--r--swaybar/tray/sni.c463
-rw-r--r--swaybar/tray/sni_watcher.c487
-rw-r--r--swaybar/tray/tray.c279
-rw-r--r--wayland/cairo.c19
35 files changed, 2714 insertions, 58 deletions
diff --git a/CMake/FindDBus.cmake b/CMake/FindDBus.cmake
new file mode 100644
index 00000000..4a1a1805
--- /dev/null
+++ b/CMake/FindDBus.cmake
@@ -0,0 +1,59 @@
1# - Try to find DBus
2# Once done, this will define
3#
4# DBUS_FOUND - system has DBus
5# DBUS_INCLUDE_DIRS - the DBus include directories
6# DBUS_LIBRARIES - link these to use DBus
7#
8# Copyright (C) 2012 Raphael Kubo da Costa <rakuco@webkit.org>
9#
10# Redistribution and use in source and binary forms, with or without
11# modification, are permitted provided that the following conditions
12# are met:
13# 1. Redistributions of source code must retain the above copyright
14# notice, this list of conditions and the following disclaimer.
15# 2. Redistributions in binary form must reproduce the above copyright
16# notice, this list of conditions and the following disclaimer in the
17# documentation and/or other materials provided with the distribution.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS
20# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS
23# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
26# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
28# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
29# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31FIND_PACKAGE(PkgConfig)
32PKG_CHECK_MODULES(PC_DBUS QUIET dbus-1)
33
34FIND_LIBRARY(DBUS_LIBRARIES
35 NAMES dbus-1
36 HINTS ${PC_DBUS_LIBDIR}
37 ${PC_DBUS_LIBRARY_DIRS}
38)
39
40FIND_PATH(DBUS_INCLUDE_DIR
41 NAMES dbus/dbus.h
42 HINTS ${PC_DBUS_INCLUDEDIR}
43 ${PC_DBUS_INCLUDE_DIRS}
44)
45
46GET_FILENAME_COMPONENT(_DBUS_LIBRARY_DIR ${DBUS_LIBRARIES} PATH)
47FIND_PATH(DBUS_ARCH_INCLUDE_DIR
48 NAMES dbus/dbus-arch-deps.h
49 HINTS ${PC_DBUS_INCLUDEDIR}
50 ${PC_DBUS_INCLUDE_DIRS}
51 ${_DBUS_LIBRARY_DIR}
52 ${DBUS_INCLUDE_DIR}
53 PATH_SUFFIXES include
54)
55
56SET(DBUS_INCLUDE_DIRS ${DBUS_INCLUDE_DIR} ${DBUS_ARCH_INCLUDE_DIR})
57
58INCLUDE(FindPackageHandleStandardArgs)
59FIND_PACKAGE_HANDLE_STANDARD_ARGS(DBUS REQUIRED_VARS DBUS_INCLUDE_DIRS DBUS_LIBRARIES)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index edf486ca..c80f6361 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -47,6 +47,7 @@ option(enable-swaybar "Enables the swaybar utility" YES)
47option(enable-swaygrab "Enables the swaygrab utility" YES) 47option(enable-swaygrab "Enables the swaygrab utility" YES)
48option(enable-swaymsg "Enables the swaymsg utility" YES) 48option(enable-swaymsg "Enables the swaymsg utility" YES)
49option(enable-gdk-pixbuf "Use Pixbuf to support more image formats" YES) 49option(enable-gdk-pixbuf "Use Pixbuf to support more image formats" YES)
50option(enable-tray "Enables the swaybar tray" YES)
50option(zsh-completions "Zsh shell completions" NO) 51option(zsh-completions "Zsh shell completions" NO)
51option(default-wallpaper "Installs the default wallpaper" YES) 52option(default-wallpaper "Installs the default wallpaper" YES)
52option(LD_LIBRARY_PATH "Configure sway's default LD_LIBRARY_PATH") 53option(LD_LIBRARY_PATH "Configure sway's default LD_LIBRARY_PATH")
@@ -64,6 +65,7 @@ find_package(Cairo REQUIRED)
64find_package(Pango REQUIRED) 65find_package(Pango REQUIRED)
65find_package(GdkPixbuf) 66find_package(GdkPixbuf)
66find_package(PAM) 67find_package(PAM)
68find_package(DBus)
67 69
68find_package(LibInput REQUIRED) 70find_package(LibInput REQUIRED)
69 71
@@ -90,6 +92,17 @@ else()
90 message(STATUS "Building without gdk-pixbuf, only png images supported.") 92 message(STATUS "Building without gdk-pixbuf, only png images supported.")
91endif() 93endif()
92 94
95if (enable-tray)
96 if (DBUS_FOUND)
97 set(ENABLE_TRAY)
98 add_definitions(-DENABLE_TRAY)
99 else()
100 message(WARNING "Tray required but DBus was not found. Tray will not be included")
101 endif()
102else()
103 message(STATUS "Building without the tray.")
104endif()
105
93include_directories(include) 106include_directories(include)
94 107
95add_subdirectory(protocols) 108add_subdirectory(protocols)
diff --git a/include/client/cairo.h b/include/client/cairo.h
index 46c53566..e7ef7c7e 100644
--- a/include/client/cairo.h
+++ b/include/client/cairo.h
@@ -6,6 +6,8 @@
6 6
7void cairo_set_source_u32(cairo_t *cairo, uint32_t color); 7void cairo_set_source_u32(cairo_t *cairo, uint32_t color);
8 8
9cairo_surface_t *cairo_image_surface_scale(cairo_surface_t *image, int width, int height);
10
9#ifdef WITH_GDK_PIXBUF 11#ifdef WITH_GDK_PIXBUF
10#include <gdk-pixbuf/gdk-pixbuf.h> 12#include <gdk-pixbuf/gdk-pixbuf.h>
11 13
diff --git a/include/sway/commands.h b/include/sway/commands.h
index 078652e7..f67df10f 100644
--- a/include/sway/commands.h
+++ b/include/sway/commands.h
@@ -157,17 +157,21 @@ sway_cmd cmd_workspace;
157sway_cmd cmd_ws_auto_back_and_forth; 157sway_cmd cmd_ws_auto_back_and_forth;
158sway_cmd cmd_workspace_layout; 158sway_cmd cmd_workspace_layout;
159 159
160sway_cmd bar_cmd_activate_button;
160sway_cmd bar_cmd_binding_mode_indicator; 161sway_cmd bar_cmd_binding_mode_indicator;
161sway_cmd bar_cmd_bindsym; 162sway_cmd bar_cmd_bindsym;
162sway_cmd bar_cmd_colors; 163sway_cmd bar_cmd_colors;
164sway_cmd bar_cmd_context_button;
163sway_cmd bar_cmd_font; 165sway_cmd bar_cmd_font;
164sway_cmd bar_cmd_mode; 166sway_cmd bar_cmd_mode;
165sway_cmd bar_cmd_modifier; 167sway_cmd bar_cmd_modifier;
166sway_cmd bar_cmd_output; 168sway_cmd bar_cmd_output;
167sway_cmd bar_cmd_height; 169sway_cmd bar_cmd_height;
168sway_cmd bar_cmd_hidden_state; 170sway_cmd bar_cmd_hidden_state;
171sway_cmd bar_cmd_icon_theme;
169sway_cmd bar_cmd_id; 172sway_cmd bar_cmd_id;
170sway_cmd bar_cmd_position; 173sway_cmd bar_cmd_position;
174sway_cmd bar_cmd_secondary_button;
171sway_cmd bar_cmd_separator_symbol; 175sway_cmd bar_cmd_separator_symbol;
172sway_cmd bar_cmd_status_command; 176sway_cmd bar_cmd_status_command;
173sway_cmd bar_cmd_pango_markup; 177sway_cmd bar_cmd_pango_markup;
diff --git a/include/sway/config.h b/include/sway/config.h
index 35f8d5f7..999a471a 100644
--- a/include/sway/config.h
+++ b/include/sway/config.h
@@ -133,7 +133,17 @@ struct bar_config {
133 char *swaybar_command; 133 char *swaybar_command;
134 char *font; 134 char *font;
135 int height; // -1 not defined 135 int height; // -1 not defined
136 int tray_padding; 136
137#ifdef ENABLE_TRAY
138 // Tray
139 char *tray_output;
140 char *icon_theme;
141 uint32_t tray_padding;
142 uint32_t activate_button;
143 uint32_t context_button;
144 uint32_t secondary_button;
145#endif
146
137 bool workspace_buttons; 147 bool workspace_buttons;
138 bool wrap_scroll; 148 bool wrap_scroll;
139 char *separator_symbol; 149 char *separator_symbol;
diff --git a/include/swaybar/bar.h b/include/swaybar/bar.h
index 697a48c2..010e1f84 100644
--- a/include/swaybar/bar.h
+++ b/include/swaybar/bar.h
@@ -21,6 +21,9 @@ struct output {
21 struct window *window; 21 struct window *window;
22 struct registry *registry; 22 struct registry *registry;
23 list_t *workspaces; 23 list_t *workspaces;
24#ifdef ENABLE_TRAY
25 list_t *items;
26#endif
24 char *name; 27 char *name;
25 int idx; 28 int idx;
26 bool focused; 29 bool focused;
@@ -37,6 +40,9 @@ struct workspace {
37/** Global bar state */ 40/** Global bar state */
38extern struct bar swaybar; 41extern struct bar swaybar;
39 42
43/** True if sway needs to render */
44extern bool dirty;
45
40/** 46/**
41 * Setup bar. 47 * Setup bar.
42 */ 48 */
diff --git a/include/swaybar/config.h b/include/swaybar/config.h
index 04b12cd4..651f0ee3 100644
--- a/include/swaybar/config.h
+++ b/include/swaybar/config.h
@@ -33,6 +33,17 @@ struct config {
33 bool all_outputs; 33 bool all_outputs;
34 list_t *outputs; 34 list_t *outputs;
35 35
36#ifdef ENABLE_TRAY
37 // Tray
38 char *tray_output;
39 char *icon_theme;
40
41 uint32_t tray_padding;
42 uint32_t activate_button;
43 uint32_t context_button;
44 uint32_t secondary_button;
45#endif
46
36 int height; 47 int height;
37 48
38 struct { 49 struct {
diff --git a/include/swaybar/event_loop.h b/include/swaybar/event_loop.h
new file mode 100644
index 00000000..a0cde07f
--- /dev/null
+++ b/include/swaybar/event_loop.h
@@ -0,0 +1,26 @@
1#ifndef _SWAYBAR_EVENT_LOOP_H
2#define _SWAYBAR_EVENT_LOOP_H
3
4#include <stdbool.h>
5#include <time.h>
6
7void add_event(int fd, short mask,
8 void(*cb)(int fd, short mask, void *data),
9 void *data);
10
11// Not guaranteed to notify cb immediately
12void add_timer(timer_t timer,
13 void(*cb)(timer_t timer, void *data),
14 void *data);
15
16// Returns false if nothing exists, true otherwise
17bool remove_event(int fd);
18
19// Returns false if nothing exists, true otherwise
20bool remove_timer(timer_t timer);
21
22// Blocks and returns after sending callbacks
23void event_loop_poll();
24
25void init_event_loop();
26#endif /*_SWAYBAR_EVENT_LOOP_H */
diff --git a/include/swaybar/tray/dbus.h b/include/swaybar/tray/dbus.h
new file mode 100644
index 00000000..eb9cfea7
--- /dev/null
+++ b/include/swaybar/tray/dbus.h
@@ -0,0 +1,18 @@
1#ifndef _SWAYBAR_DBUS_H
2#define _SWAYBAR_DBUS_H
3
4#include <stdbool.h>
5#include <dbus/dbus.h>
6extern DBusConnection *conn;
7
8/**
9 * Should be called in main loop to dispatch events
10 */
11void dispatch_dbus();
12
13/**
14 * Initializes async dbus communication
15 */
16int dbus_init();
17
18#endif /* _SWAYBAR_DBUS_H */
diff --git a/include/swaybar/tray/icon.h b/include/swaybar/tray/icon.h
new file mode 100644
index 00000000..1cc6ff9c
--- /dev/null
+++ b/include/swaybar/tray/icon.h
@@ -0,0 +1,16 @@
1#ifndef _SWAYBAR_ICON_H
2#define _SWAYBAR_ICON_H
3
4#include <stdint.h>
5#include <stdbool.h>
6#include <client/cairo.h>
7
8/**
9 * Returns the image found by `name` that is closest to `size`
10 */
11cairo_surface_t *find_icon(const char *name, int size);
12
13/* Struct used internally only */
14struct subdir;
15
16#endif /* _SWAYBAR_ICON_H */
diff --git a/include/swaybar/tray/sni.h b/include/swaybar/tray/sni.h
new file mode 100644
index 00000000..83809b2d
--- /dev/null
+++ b/include/swaybar/tray/sni.h
@@ -0,0 +1,81 @@
1#ifndef _SWAYBAR_SNI_H
2#define _SWAYBAR_SNI_H
3
4#include <stdbool.h>
5#include <client/cairo.h>
6
7struct StatusNotifierItem {
8 /* Name registered to sni watcher */
9 char *name;
10 /* Unique bus name, needed for determining signal origins */
11 char *unique_name;
12 bool kde_special_snowflake;
13
14 cairo_surface_t *image;
15 bool dirty;
16};
17
18/* Each output holds an sni_icon_ref of each item to render */
19struct sni_icon_ref {
20 cairo_surface_t *icon;
21 struct StatusNotifierItem *ref;
22};
23
24struct sni_icon_ref *sni_icon_ref_create(struct StatusNotifierItem *item,
25 int height);
26
27void sni_icon_ref_free(struct sni_icon_ref *sni_ref);
28
29/**
30 * Will return a new item and get its icon. (see warning below)
31 */
32struct StatusNotifierItem *sni_create(const char *name);
33
34/**
35 * `item` must be a struct StatusNotifierItem *
36 * `str` must be a NUL terminated char *
37 *
38 * Returns 0 if `item` has a name of `str`
39 */
40int sni_str_cmp(const void *item, const void *str);
41
42/**
43 * Returns 0 if `item` has a unique name of `str` or if
44 * `item->unique_name == NULL`
45 */
46int sni_uniq_cmp(const void *item, const void *str);
47
48/**
49 * Gets an icon for the given item if found.
50 *
51 * XXX
52 * This function keeps a reference to the item until it gets responses, make
53 * sure that the reference and item are valid during this time.
54 */
55void get_icon(struct StatusNotifierItem *item);
56
57/**
58 * Calls the "activate" method on the given StatusNotifierItem
59 *
60 * x and y should be where the item was clicked
61 */
62void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y);
63
64/**
65 * Asks the item to draw a context menu at the given x and y coords
66 */
67void sni_context_menu(struct StatusNotifierItem *item, uint32_t x, uint32_t y);
68
69/**
70 * Calls the "secondary activate" method on the given StatusNotifierItem
71 *
72 * x and y should be where the item was clicked
73 */
74void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y);
75
76/**
77 * Deconstructs `item`
78 */
79void sni_free(struct StatusNotifierItem *item);
80
81#endif /* _SWAYBAR_SNI_H */
diff --git a/include/swaybar/tray/sni_watcher.h b/include/swaybar/tray/sni_watcher.h
new file mode 100644
index 00000000..25ddfcd2
--- /dev/null
+++ b/include/swaybar/tray/sni_watcher.h
@@ -0,0 +1,10 @@
1#ifndef _SWAYBAR_SNI_WATCHER_H
2#define _SWAYBAR_SNI_WATCHER_H
3
4/**
5 * Starts the sni_watcher, the watcher is practically a black box and should
6 * only be accessed though functions described in its spec
7 */
8int init_sni_watcher();
9
10#endif /* _SWAYBAR_SNI_WATCHER_H */
diff --git a/include/swaybar/tray/tray.h b/include/swaybar/tray/tray.h
new file mode 100644
index 00000000..7d371008
--- /dev/null
+++ b/include/swaybar/tray/tray.h
@@ -0,0 +1,26 @@
1#ifndef _SWAYBAR_TRAY_H
2#define _SWAYBAR_TRAY_H
3
4#include <stdint.h>
5#include <stdbool.h>
6#include "swaybar/tray/dbus.h"
7#include "swaybar/tray/sni.h"
8#include "list.h"
9
10extern struct tray *tray;
11
12struct tray {
13 list_t *items;
14};
15
16/**
17 * Initializes the tray host with D-Bus
18 */
19int init_tray();
20
21/**
22 * Returns an item if `x` and `y` collide with it and NULL otherwise
23 */
24struct StatusNotifierItem *collides_with_sni(int x, int y);
25
26#endif /* _SWAYBAR_TRAY_H */
diff --git a/sway/commands.c b/sway/commands.c
index 34218491..f83b5287 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -221,18 +221,22 @@ static struct cmd_handler handlers[] = {
221}; 221};
222 222
223static struct cmd_handler bar_handlers[] = { 223static struct cmd_handler bar_handlers[] = {
224 { "activate_button", bar_cmd_activate_button },
224 { "binding_mode_indicator", bar_cmd_binding_mode_indicator }, 225 { "binding_mode_indicator", bar_cmd_binding_mode_indicator },
225 { "bindsym", bar_cmd_bindsym }, 226 { "bindsym", bar_cmd_bindsym },
226 { "colors", bar_cmd_colors }, 227 { "colors", bar_cmd_colors },
228 { "context_button", bar_cmd_context_button },
227 { "font", bar_cmd_font }, 229 { "font", bar_cmd_font },
228 { "height", bar_cmd_height }, 230 { "height", bar_cmd_height },
229 { "hidden_state", bar_cmd_hidden_state }, 231 { "hidden_state", bar_cmd_hidden_state },
232 { "icon_theme", bar_cmd_icon_theme },
230 { "id", bar_cmd_id }, 233 { "id", bar_cmd_id },
231 { "mode", bar_cmd_mode }, 234 { "mode", bar_cmd_mode },
232 { "modifier", bar_cmd_modifier }, 235 { "modifier", bar_cmd_modifier },
233 { "output", bar_cmd_output }, 236 { "output", bar_cmd_output },
234 { "pango_markup", bar_cmd_pango_markup }, 237 { "pango_markup", bar_cmd_pango_markup },
235 { "position", bar_cmd_position }, 238 { "position", bar_cmd_position },
239 { "secondary_button", bar_cmd_secondary_button },
236 { "separator_symbol", bar_cmd_separator_symbol }, 240 { "separator_symbol", bar_cmd_separator_symbol },
237 { "status_command", bar_cmd_status_command }, 241 { "status_command", bar_cmd_status_command },
238 { "strip_workspace_numbers", bar_cmd_strip_workspace_numbers }, 242 { "strip_workspace_numbers", bar_cmd_strip_workspace_numbers },
diff --git a/sway/commands/bar/activate_button.c b/sway/commands/bar/activate_button.c
new file mode 100644
index 00000000..32a1d3e5
--- /dev/null
+++ b/sway/commands/bar/activate_button.c
@@ -0,0 +1,26 @@
1#include <stdlib.h>
2#include "sway/commands.h"
3#include "log.h"
4
5struct cmd_results *bar_cmd_activate_button(int argc, char **argv) {
6 const char *cmd_name = "activate_button";
7#ifndef ENABLE_TRAY
8 return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command "
9 "%s called, but sway was compiled without tray support",
10 cmd_name, cmd_name);
11#else
12 struct cmd_results *error = NULL;
13 if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) {
14 return error;
15 }
16
17 if (!config->current_bar) {
18 return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
19 }
20
21 // User should be able to prefix with 0x or whatever they want
22 config->current_bar->secondary_button = strtoul(argv[0], NULL, 0);
23
24 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
25#endif
26}
diff --git a/sway/commands/bar/context_button.c b/sway/commands/bar/context_button.c
new file mode 100644
index 00000000..6d7d7aec
--- /dev/null
+++ b/sway/commands/bar/context_button.c
@@ -0,0 +1,26 @@
1#include <stdlib.h>
2#include "sway/commands.h"
3#include "log.h"
4
5struct cmd_results *bar_cmd_context_button(int argc, char **argv) {
6 const char *cmd_name = "context_button";
7#ifndef ENABLE_TRAY
8 return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command "
9 "%s called, but sway was compiled without tray support",
10 cmd_name, cmd_name);
11#else
12 struct cmd_results *error = NULL;
13 if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) {
14 return error;
15 }
16
17 if (!config->current_bar) {
18 return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
19 }
20
21 // User should be able to prefix with 0x or whatever they want
22 config->current_bar->context_button = strtoul(argv[0], NULL, 0);
23
24 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
25#endif
26}
diff --git a/sway/commands/bar/icon_theme.c b/sway/commands/bar/icon_theme.c
new file mode 100644
index 00000000..cbfc0be5
--- /dev/null
+++ b/sway/commands/bar/icon_theme.c
@@ -0,0 +1,25 @@
1#define _XOPEN_SOURCE 500
2#include <string.h>
3#include "sway/commands.h"
4
5struct cmd_results *bar_cmd_icon_theme(int argc, char **argv) {
6 const char *cmd_name = "tray_output";
7#ifndef ENABLE_TRAY
8 return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command "
9 "%s called, but sway was compiled without tray support",
10 cmd_name, cmd_name);
11#else
12 struct cmd_results *error = NULL;
13 if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) {
14 return error;
15 }
16
17 if (!config->current_bar) {
18 return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
19 }
20
21 config->current_bar->icon_theme = strdup(argv[0]);
22
23 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
24#endif
25}
diff --git a/sway/commands/bar/secondary_button.c b/sway/commands/bar/secondary_button.c
new file mode 100644
index 00000000..745045c5
--- /dev/null
+++ b/sway/commands/bar/secondary_button.c
@@ -0,0 +1,26 @@
1#include <stdlib.h>
2#include "sway/commands.h"
3#include "log.h"
4
5struct cmd_results *bar_cmd_secondary_button(int argc, char **argv) {
6 const char *cmd_name = "secondary_button";
7#ifndef ENABLE_TRAY
8 return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command "
9 "%s called, but sway was compiled without tray support",
10 cmd_name, cmd_name);
11#else
12 struct cmd_results *error = NULL;
13 if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) {
14 return error;
15 }
16
17 if (!config->current_bar) {
18 return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
19 }
20
21 // User should be able to prefix with 0x or whatever they want
22 config->current_bar->secondary_button = strtoul(argv[0], NULL, 0);
23
24 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
25#endif
26}
diff --git a/sway/commands/bar/tray_output.c b/sway/commands/bar/tray_output.c
index 8a1b5d35..012304a9 100644
--- a/sway/commands/bar/tray_output.c
+++ b/sway/commands/bar/tray_output.c
@@ -1,7 +1,29 @@
1#define _XOPEN_SOURCE 500
2#include <string.h>
1#include "sway/commands.h" 3#include "sway/commands.h"
2#include "log.h"
3 4
4struct cmd_results *bar_cmd_tray_output(int argc, char **argv) { 5struct cmd_results *bar_cmd_tray_output(int argc, char **argv) {
5 sway_log(L_ERROR, "Warning: tray_output is not supported on wayland"); 6 const char *cmd_name = "tray_output";
7#ifndef ENABLE_TRAY
8 return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command "
9 "%s called, but sway was compiled without tray support",
10 cmd_name, cmd_name);
11#else
12 struct cmd_results *error = NULL;
13 if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) {
14 return error;
15 }
16
17 if (!config->current_bar) {
18 return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
19 }
20
21 if (strcmp(argv[0], "all") == 0) {
22 // Default behaviour
23 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
24 }
25 config->current_bar->tray_output = strdup(argv[0]);
26
6 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 27 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
28#endif
7} 29}
diff --git a/sway/commands/bar/tray_padding.c b/sway/commands/bar/tray_padding.c
index 8c559f65..ac0572ce 100644
--- a/sway/commands/bar/tray_padding.c
+++ b/sway/commands/bar/tray_padding.c
@@ -1,30 +1,34 @@
1#include <stdlib.h> 1#include <stdlib.h>
2#include <string.h>
3#include <strings.h> 2#include <strings.h>
4#include "sway/commands.h" 3#include "sway/commands.h"
5#include "log.h" 4#include "log.h"
6 5
7struct cmd_results *bar_cmd_tray_padding(int argc, char **argv) { 6struct cmd_results *bar_cmd_tray_padding(int argc, char **argv) {
7 const char *cmd_name = "tray_padding";
8#ifndef ENABLE_TRAY
9 return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command"
10 "%s called, but sway was compiled without tray support",
11 cmd_name, cmd_name);
12#else
8 struct cmd_results *error = NULL; 13 struct cmd_results *error = NULL;
9 if ((error = checkarg(argc, "tray_padding", EXPECTED_AT_LEAST, 1))) { 14 if ((error = checkarg(argc, cmd_name, EXPECTED_AT_LEAST, 1))) {
10 return error; 15 return error;
11 } 16 }
12 17
13 if (!config->current_bar) { 18 if (!config->current_bar) {
14 return cmd_results_new(CMD_FAILURE, "tray_padding", "No bar defined."); 19 return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
15 } 20 }
16 21
17 int padding = atoi(argv[0]); 22 if (argc == 1 || (argc == 2 && strcasecmp("px", argv[1]) == 0)) {
18 if (padding < 0) { 23 char *inv;
19 return cmd_results_new(CMD_INVALID, "tray_padding", 24 uint32_t padding = strtoul(argv[0], &inv, 10);
20 "Invalid padding value %s, minimum is 0", argv[0]); 25 if (*inv == '\0' || strcasecmp(inv, "px") == 0) {
26 config->current_bar->tray_padding = padding;
27 sway_log(L_DEBUG, "Enabling tray padding of %d px on bar: %s", padding, config->current_bar->id);
28 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
29 }
21 } 30 }
22 31 return cmd_results_new(CMD_FAILURE, cmd_name,
23 if (argc > 1 && strcasecmp("px", argv[1]) != 0) { 32 "Expected 'tray_padding <padding>[px]'");
24 return cmd_results_new(CMD_INVALID, "tray_padding", 33#endif
25 "Unknown unit %s", argv[1]);
26 }
27 config->current_bar->tray_padding = padding;
28 sway_log(L_DEBUG, "Enabling tray padding of %d px on bar: %s", padding, config->current_bar->id);
29 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
30} 34}
diff --git a/sway/config.c b/sway/config.c
index 85823953..e0b65615 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -69,6 +69,10 @@ static void free_bar(struct bar_config *bar) {
69 } 69 }
70 free(bar->mode); 70 free(bar->mode);
71 free(bar->hidden_state); 71 free(bar->hidden_state);
72#ifdef ENABLE_TRAY
73 free(bar->tray_output);
74 free(bar->icon_theme);
75#endif
72 free(bar->status_command); 76 free(bar->status_command);
73 free(bar->font); 77 free(bar->font);
74 free(bar->separator_symbol); 78 free(bar->separator_symbol);
@@ -1386,7 +1390,14 @@ struct bar_config *default_bar_config(void) {
1386 bar->separator_symbol = NULL; 1390 bar->separator_symbol = NULL;
1387 bar->strip_workspace_numbers = false; 1391 bar->strip_workspace_numbers = false;
1388 bar->binding_mode_indicator = true; 1392 bar->binding_mode_indicator = true;
1393#ifdef ENABLE_TRAY
1394 bar->tray_output = NULL;
1395 bar->icon_theme = NULL;
1389 bar->tray_padding = 2; 1396 bar->tray_padding = 2;
1397 bar->activate_button = 0x110; /* BTN_LEFT */
1398 bar->context_button = 0x111; /* BTN_RIGHT */
1399 bar->secondary_button = 0x112; /* BTN_MIDDLE */
1400#endif
1390 bar->verbose = false; 1401 bar->verbose = false;
1391 bar->pid = 0; 1402 bar->pid = 0;
1392 // set default colors 1403 // set default colors
diff --git a/sway/ipc-json.c b/sway/ipc-json.c
index 512144a4..31de53f0 100644
--- a/sway/ipc-json.c
+++ b/sway/ipc-json.c
@@ -327,7 +327,22 @@ json_object *ipc_json_describe_bar_config(struct bar_config *bar) {
327 327
328 json_object *json = json_object_new_object(); 328 json_object *json = json_object_new_object();
329 json_object_object_add(json, "id", json_object_new_string(bar->id)); 329 json_object_object_add(json, "id", json_object_new_string(bar->id));
330 json_object_object_add(json, "tray_output", NULL); 330#ifdef ENABLE_TRAY
331 if (bar->tray_output) {
332 json_object_object_add(json, "tray_output", json_object_new_string(bar->tray_output));
333 } else {
334 json_object_object_add(json, "tray_output", NULL);
335 }
336 if (bar->icon_theme) {
337 json_object_object_add(json, "icon_theme", json_object_new_string(bar->icon_theme));
338 } else {
339 json_object_object_add(json, "icon_theme", NULL);
340 }
341 json_object_object_add(json, "tray_padding", json_object_new_int(bar->tray_padding));
342 json_object_object_add(json, "activate_button", json_object_new_int(bar->activate_button));
343 json_object_object_add(json, "context_button", json_object_new_int(bar->context_button));
344 json_object_object_add(json, "secondary_button", json_object_new_int(bar->secondary_button));
345#endif
331 json_object_object_add(json, "mode", json_object_new_string(bar->mode)); 346 json_object_object_add(json, "mode", json_object_new_string(bar->mode));
332 json_object_object_add(json, "hidden_state", json_object_new_string(bar->hidden_state)); 347 json_object_object_add(json, "hidden_state", json_object_new_string(bar->hidden_state));
333 json_object_object_add(json, "modifier", json_object_new_string(get_modifier_name_by_mask(bar->modifier))); 348 json_object_object_add(json, "modifier", json_object_new_string(get_modifier_name_by_mask(bar->modifier)));
diff --git a/sway/sway-bar.5.txt b/sway/sway-bar.5.txt
index 5a52e7db..29487662 100644
--- a/sway/sway-bar.5.txt
+++ b/sway/sway-bar.5.txt
@@ -65,6 +65,42 @@ Commands
65**height** <height>:: 65**height** <height>::
66 Sets the height of the bar. Default height will match the font size. 66 Sets the height of the bar. Default height will match the font size.
67 67
68Tray
69----
70
71Swaybar provides a system tray where programs such as NetworkManager, VLC,
72Pidgin, etc. can place little icons. The following commands configure
73interaction with the tray or individual icons.
74The _button_ argument in all following commands is a Linux input event code as
75defined in linux/input-event-codes.h. This is because wayland defines button
76codes in this manner.
77
78**activate_button** <button>::
79 Sets the button to be used for the _activate_ (primary click) tray item
80 event. By default is BTN_LEFT (0x110).
81
82**context_button** <button>::
83 Sets the button to be used for the _context menu_ (right click) tray item
84 event. By default is BTN_RIGHT (0x111).
85
86**secondary_button** <button>::
87 Sets the button to be used for the _secondary_ (middle click) tray item
88 event. By default is BTN_MIDDLE (0x112).
89
90**tray_output** none|all|<name>::
91 Sets the output that the tray will appear on or none. Unlike i3bar, swaybar
92 should be able to show icons on any number of bars and outputs without
93 races. Because of this, the default value for this is _all_.
94
95**tray_padding** <px> [px]::
96 Sets the pixel padding of the system tray. This padding will surround the
97 tray on all sides and between each item. The default value for _px_ is 2.
98
99**icon_theme** <name>::
100 Sets the icon theme that sway will look for item icons in. This option has
101 no default value, because sway will always default to the fallback theme,
102 hicolor.
103
68Colors 104Colors
69------ 105------
70 106
diff --git a/swaybar/CMakeLists.txt b/swaybar/CMakeLists.txt
index f59a48fd..373719de 100644
--- a/swaybar/CMakeLists.txt
+++ b/swaybar/CMakeLists.txt
@@ -5,7 +5,13 @@ include_directories(
5 ${PANGO_INCLUDE_DIRS} 5 ${PANGO_INCLUDE_DIRS}
6 ${JSONC_INCLUDE_DIRS} 6 ${JSONC_INCLUDE_DIRS}
7 ${XKBCOMMON_INCLUDE_DIRS} 7 ${XKBCOMMON_INCLUDE_DIRS}
8 ${DBUS_INCLUDE_DIRS}
8) 9)
10if (enable-tray)
11 file(GLOB tray
12 tray/*.c
13 )
14endif()
9 15
10add_executable(swaybar 16add_executable(swaybar
11 main.c 17 main.c
@@ -14,6 +20,8 @@ add_executable(swaybar
14 bar.c 20 bar.c
15 status_line.c 21 status_line.c
16 ipc.c 22 ipc.c
23 event_loop.c
24 ${tray}
17) 25)
18 26
19target_link_libraries(swaybar 27target_link_libraries(swaybar
@@ -24,6 +32,7 @@ target_link_libraries(swaybar
24 ${CAIRO_LIBRARIES} 32 ${CAIRO_LIBRARIES}
25 ${PANGO_LIBRARIES} 33 ${PANGO_LIBRARIES}
26 ${JSONC_LIBRARIES} 34 ${JSONC_LIBRARIES}
35 ${DBUS_LIBRARIES}
27) 36)
28 37
29if (WITH_GDK_PIXBUF) 38if (WITH_GDK_PIXBUF)
@@ -32,6 +41,8 @@ if (WITH_GDK_PIXBUF)
32 ) 41 )
33endif() 42endif()
34 43
44target_link_libraries(swaybar rt)
45
35install( 46install(
36 TARGETS swaybar 47 TARGETS swaybar
37 RUNTIME 48 RUNTIME
diff --git a/swaybar/bar.c b/swaybar/bar.c
index abde1cc9..cdaf6a37 100644
--- a/swaybar/bar.c
+++ b/swaybar/bar.c
@@ -7,10 +7,17 @@
7#include <sys/wait.h> 7#include <sys/wait.h>
8#include <signal.h> 8#include <signal.h>
9#include <poll.h> 9#include <poll.h>
10#ifdef ENABLE_TRAY
11#include <dbus/dbus.h>
12#include "swaybar/tray/sni_watcher.h"
13#include "swaybar/tray/tray.h"
14#include "swaybar/tray/sni.h"
15#endif
10#include "swaybar/ipc.h" 16#include "swaybar/ipc.h"
11#include "swaybar/render.h" 17#include "swaybar/render.h"
12#include "swaybar/config.h" 18#include "swaybar/config.h"
13#include "swaybar/status_line.h" 19#include "swaybar/status_line.h"
20#include "swaybar/event_loop.h"
14#include "swaybar/bar.h" 21#include "swaybar/bar.h"
15#include "ipc-client.h" 22#include "ipc-client.h"
16#include "list.h" 23#include "list.h"
@@ -50,18 +57,39 @@ static void spawn_status_cmd_proc(struct bar *bar) {
50 } 57 }
51} 58}
52 59
60#ifdef ENABLE_TRAY
61static void spawn_xembed_sni_proxy() {
62 pid_t pid = fork();
63 if (pid == 0) {
64 int wstatus;
65 do {
66 pid = fork();
67 if (pid == 0) {
68 execlp("xembedsniproxy", "xembedsniproxy", NULL);
69 _exit(EXIT_FAILURE);
70 }
71 waitpid(pid, &wstatus, 0);
72 } while (!WIFEXITED(wstatus));
73 _exit(EXIT_FAILURE);
74 }
75}
76#endif
77
53struct output *new_output(const char *name) { 78struct output *new_output(const char *name) {
54 struct output *output = malloc(sizeof(struct output)); 79 struct output *output = malloc(sizeof(struct output));
55 output->name = strdup(name); 80 output->name = strdup(name);
56 output->window = NULL; 81 output->window = NULL;
57 output->registry = NULL; 82 output->registry = NULL;
58 output->workspaces = create_list(); 83 output->workspaces = create_list();
84#ifdef ENABLE_TRAY
85 output->items = create_list();
86#endif
59 return output; 87 return output;
60} 88}
61 89
62static void mouse_button_notify(struct window *window, int x, int y, 90static void mouse_button_notify(struct window *window, int x, int y,
63 uint32_t button, uint32_t state_w) { 91 uint32_t button, uint32_t state_w) {
64 sway_log(L_DEBUG, "Mouse button %d clicked at %d %d %d\n", button, x, y, state_w); 92 sway_log(L_DEBUG, "Mouse button %d clicked at %d %d %d", button, x, y, state_w);
65 if (!state_w) { 93 if (!state_w) {
66 return; 94 return;
67 } 95 }
@@ -92,6 +120,30 @@ static void mouse_button_notify(struct window *window, int x, int y,
92 break; 120 break;
93 } 121 }
94 } 122 }
123
124#ifdef ENABLE_TRAY
125 uint32_t tray_padding = swaybar.config->tray_padding;
126 int tray_width = window->width * window->scale;
127
128 for (int i = 0; i < clicked_output->items->length; ++i) {
129 struct sni_icon_ref *item =
130 clicked_output->items->items[i];
131 int icon_width = cairo_image_surface_get_width(item->icon);
132
133 tray_width -= tray_padding;
134 if (x <= tray_width && x >= tray_width - icon_width) {
135 if (button == swaybar.config->activate_button) {
136 sni_activate(item->ref, x, y);
137 } else if (button == swaybar.config->context_button) {
138 sni_context_menu(item->ref, x, y);
139 } else if (button == swaybar.config->secondary_button) {
140 sni_secondary(item->ref, x, y);
141 }
142 break;
143 }
144 tray_width -= icon_width;
145 }
146#endif
95} 147}
96 148
97static void mouse_scroll_notify(struct window *window, enum scroll_direction direction) { 149static 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) {
136 /* initialize bar with default values */ 188 /* initialize bar with default values */
137 bar_init(bar); 189 bar_init(bar);
138 190
191 /* Initialize event loop lists */
192 init_event_loop();
193
139 /* connect to sway ipc */ 194 /* connect to sway ipc */
140 bar->ipc_socketfd = ipc_open_socket(socket_path); 195 bar->ipc_socketfd = ipc_open_socket(socket_path);
141 bar->ipc_event_socketfd = ipc_open_socket(socket_path); 196 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) {
178 } 233 }
179 /* spawn status command */ 234 /* spawn status command */
180 spawn_status_cmd_proc(bar); 235 spawn_status_cmd_proc(bar);
236
237#ifdef ENABLE_TRAY
238 // We should have at least one output to serve the tray to
239 if (!swaybar.config->tray_output || strcmp(swaybar.config->tray_output, "none") != 0) {
240 /* Connect to the D-Bus */
241 dbus_init();
242
243 /* Start the SNI watcher */
244 init_sni_watcher();
245
246 /* Start the SNI host */
247 init_tray();
248
249 /* Start xembedsniproxy */
250 spawn_xembed_sni_proxy();
251 }
252#endif
181} 253}
182 254
183void bar_run(struct bar *bar) { 255bool dirty = true;
184 int pfds = bar->outputs->length + 2;
185 struct pollfd *pfd = malloc(pfds * sizeof(struct pollfd));
186 bool dirty = true;
187 256
188 pfd[0].fd = bar->ipc_event_socketfd; 257static void respond_ipc(int fd, short mask, void *_bar) {
189 pfd[0].events = POLLIN; 258 struct bar *bar = (struct bar *)_bar;
190 pfd[1].fd = bar->status_read_fd; 259 sway_log(L_DEBUG, "Got IPC event.");
191 pfd[1].events = POLLIN; 260 dirty = handle_ipc_event(bar);
261}
262
263static void respond_command(int fd, short mask, void *_bar) {
264 struct bar *bar = (struct bar *)_bar;
265 dirty = handle_status_line(bar);
266}
267
268static void respond_output(int fd, short mask, void *_output) {
269 struct output *output = (struct output *)_output;
270 if (wl_display_dispatch(output->registry->display) == -1) {
271 sway_log(L_ERROR, "failed to dispatch wl: %d", errno);
272 }
273}
274
275void bar_run(struct bar *bar) {
276 add_event(bar->ipc_event_socketfd, POLLIN, respond_ipc, bar);
277 add_event(bar->status_read_fd, POLLIN, respond_command, bar);
192 278
193 int i; 279 int i;
194 for (i = 0; i < bar->outputs->length; ++i) { 280 for (i = 0; i < bar->outputs->length; ++i) {
195 struct output *output = bar->outputs->items[i]; 281 struct output *output = bar->outputs->items[i];
196 pfd[i+2].fd = wl_display_get_fd(output->registry->display); 282 add_event(wl_display_get_fd(output->registry->display),
197 pfd[i+2].events = POLLIN; 283 POLLIN, respond_output, output);
198 } 284 }
199 285
200 while (1) { 286 while (1) {
@@ -212,29 +298,10 @@ void bar_run(struct bar *bar) {
212 298
213 dirty = false; 299 dirty = false;
214 300
215 poll(pfd, pfds, -1); 301 event_loop_poll();
216 302#ifdef ENABLE_TRAY
217 if (pfd[0].revents & POLLIN) { 303 dispatch_dbus();
218 sway_log(L_DEBUG, "Got IPC event."); 304#endif
219 dirty = handle_ipc_event(bar);
220 }
221
222 if (bar->config->status_command && pfd[1].revents & POLLIN) {
223 sway_log(L_DEBUG, "Got update from status command.");
224 dirty = handle_status_line(bar);
225 }
226
227 // dispatch wl_display events
228 for (i = 0; i < bar->outputs->length; ++i) {
229 struct output *output = bar->outputs->items[i];
230 if (pfd[i+2].revents & POLLIN) {
231 if (wl_display_dispatch(output->registry->display) == -1) {
232 sway_log(L_ERROR, "failed to dispatch wl: %d", errno);
233 }
234 } else {
235 wl_display_dispatch_pending(output->registry->display);
236 }
237 }
238 } 305 }
239} 306}
240 307
diff --git a/swaybar/config.c b/swaybar/config.c
index 1d802022..8fe552f2 100644
--- a/swaybar/config.c
+++ b/swaybar/config.c
@@ -48,6 +48,18 @@ struct config *init_config() {
48 /* height */ 48 /* height */
49 config->height = 0; 49 config->height = 0;
50 50
51#ifdef ENABLE_TRAY
52 config->tray_output = NULL;
53 config->icon_theme = NULL;
54 config->tray_padding = 2;
55 /**
56 * These constants are used by wayland and are defined in
57 * linux/input-event-codes.h
58 */
59 config->activate_button = 0x110; /* BTN_LEFT */
60 config->context_button = 0x111; /* BTN_RIGHT */
61#endif
62
51 /* colors */ 63 /* colors */
52 config->colors.background = 0x000000FF; 64 config->colors.background = 0x000000FF;
53 config->colors.statusline = 0xFFFFFFFF; 65 config->colors.statusline = 0xFFFFFFFF;
diff --git a/swaybar/event_loop.c b/swaybar/event_loop.c
new file mode 100644
index 00000000..80655d8b
--- /dev/null
+++ b/swaybar/event_loop.c
@@ -0,0 +1,143 @@
1#define _XOPEN_SOURCE 500
2#include <stdlib.h>
3#include <stdbool.h>
4#include <string.h>
5#include <strings.h>
6#include <poll.h>
7#include "swaybar/bar.h"
8#include "swaybar/event_loop.h"
9#include "list.h"
10#include "log.h"
11
12struct event_item {
13 void(*cb)(int fd, short mask, void *data);
14 void *data;
15};
16
17struct timer_item {
18 timer_t timer;
19 void(*cb)(timer_t timer, void *data);
20 void *data;
21};
22
23static struct {
24 // The order of each must be kept consistent
25 struct { /* pollfd array */
26 struct pollfd *items;
27 int capacity;
28 int length;
29 } fds;
30 list_t *items; /* event_item list */
31
32 // Timer list
33 list_t *timers;
34} event_loop;
35
36void add_timer(timer_t timer,
37 void(*cb)(timer_t timer, void *data),
38 void *data) {
39
40 struct timer_item *item = malloc(sizeof(struct timer_item));
41 item->timer = timer;
42 item->cb = cb;
43 item->data = data;
44
45 list_add(event_loop.timers, item);
46}
47
48void add_event(int fd, short mask,
49 void(*cb)(int fd, short mask, void *data), void *data) {
50
51 struct pollfd pollfd = {
52 fd,
53 mask,
54 0,
55 };
56
57 // Resize
58 if (event_loop.fds.length == event_loop.fds.capacity) {
59 event_loop.fds.capacity += 10;
60 event_loop.fds.items = realloc(event_loop.fds.items,
61 sizeof(struct pollfd) * event_loop.fds.capacity);
62 }
63
64 event_loop.fds.items[event_loop.fds.length++] = pollfd;
65
66 struct event_item *item = malloc(sizeof(struct event_item));
67 item->cb = cb;
68 item->data = data;
69
70 list_add(event_loop.items, item);
71
72 return;
73}
74
75bool remove_event(int fd) {
76 int index = -1;
77 for (int i = 0; i < event_loop.fds.length; ++i) {
78 if (event_loop.fds.items[i].fd == fd) {
79 index = i;
80 }
81 }
82 if (index != -1) {
83 free(event_loop.items->items[index]);
84
85 --event_loop.fds.length;
86 memmove(&event_loop.fds.items[index], &event_loop.fds.items[index + 1],
87 sizeof(struct pollfd) * event_loop.fds.length - index);
88
89 list_del(event_loop.items, index);
90 return true;
91 } else {
92 return false;
93 }
94}
95
96static int timer_item_timer_cmp(const void *_timer_item, const void *_timer) {
97 const struct timer_item *timer_item = _timer_item;
98 const timer_t *timer = _timer;
99 if (timer_item->timer == timer) {
100 return 0;
101 } else {
102 return -1;
103 }
104}
105bool remove_timer(timer_t timer) {
106 int index = list_seq_find(event_loop.timers, timer_item_timer_cmp, &timer);
107 if (index != -1) {
108 list_del(event_loop.timers, index);
109 return true;
110 }
111 return false;
112}
113
114void event_loop_poll() {
115 poll(event_loop.fds.items, event_loop.fds.length, -1);
116
117 for (int i = 0; i < event_loop.fds.length; ++i) {
118 struct pollfd pfd = event_loop.fds.items[i];
119 struct event_item *item = (struct event_item *)event_loop.items->items[i];
120
121 if (pfd.revents & pfd.events) {
122 item->cb(pfd.fd, pfd.revents, item->data);
123 }
124 }
125
126 // check timers
127 // not tested, but seems to work
128 for (int i = 0; i < event_loop.timers->length; ++i) {
129 struct timer_item *item = event_loop.timers->items[i];
130 int overrun = timer_getoverrun(item->timer);
131 if (overrun && overrun != -1) {
132 item->cb(item->timer, item->data);
133 }
134 }
135}
136
137void init_event_loop() {
138 event_loop.fds.length = 0;
139 event_loop.fds.capacity = 10;
140 event_loop.fds.items = malloc(event_loop.fds.capacity * sizeof(struct pollfd));
141 event_loop.items = create_list();
142 event_loop.timers = create_list();
143}
diff --git a/swaybar/ipc.c b/swaybar/ipc.c
index b08eeea8..93d1219c 100644
--- a/swaybar/ipc.c
+++ b/swaybar/ipc.c
@@ -19,11 +19,19 @@ void ipc_send_workspace_command(const char *workspace_name) {
19 19
20static void ipc_parse_config(struct config *config, const char *payload) { 20static void ipc_parse_config(struct config *config, const char *payload) {
21 json_object *bar_config = json_tokener_parse(payload); 21 json_object *bar_config = json_tokener_parse(payload);
22 json_object *tray_output, *mode, *hidden_bar, *position, *status_command; 22 json_object *markup, *mode, *hidden_bar, *position, *status_command;
23 json_object *font, *bar_height, *wrap_scroll, *workspace_buttons, *strip_workspace_numbers; 23 json_object *font, *bar_height, *wrap_scroll, *workspace_buttons, *strip_workspace_numbers;
24 json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol, *outputs; 24 json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol, *outputs;
25 json_object *markup; 25#ifdef ENABLE_TRAY
26 json_object *tray_output, *icon_theme, *tray_padding, *activate_button, *context_button;
27 json_object *secondary_button;
26 json_object_object_get_ex(bar_config, "tray_output", &tray_output); 28 json_object_object_get_ex(bar_config, "tray_output", &tray_output);
29 json_object_object_get_ex(bar_config, "icon_theme", &icon_theme);
30 json_object_object_get_ex(bar_config, "tray_padding", &tray_padding);
31 json_object_object_get_ex(bar_config, "activate_button", &activate_button);
32 json_object_object_get_ex(bar_config, "context_button", &context_button);
33 json_object_object_get_ex(bar_config, "secondary_button", &secondary_button);
34#endif
27 json_object_object_get_ex(bar_config, "mode", &mode); 35 json_object_object_get_ex(bar_config, "mode", &mode);
28 json_object_object_get_ex(bar_config, "hidden_bar", &hidden_bar); 36 json_object_object_get_ex(bar_config, "hidden_bar", &hidden_bar);
29 json_object_object_get_ex(bar_config, "position", &position); 37 json_object_object_get_ex(bar_config, "position", &position);
@@ -83,6 +91,34 @@ static void ipc_parse_config(struct config *config, const char *payload) {
83 config->pango_markup = json_object_get_boolean(markup); 91 config->pango_markup = json_object_get_boolean(markup);
84 } 92 }
85 93
94#ifdef ENABLE_TRAY
95 if (tray_output) {
96 free(config->tray_output);
97 config->tray_output = strdup(json_object_get_string(tray_output));
98 }
99
100 if (icon_theme) {
101 free(config->icon_theme);
102 config->icon_theme = strdup(json_object_get_string(icon_theme));
103 }
104
105 if (tray_padding) {
106 config->tray_padding = json_object_get_int(tray_padding);
107 }
108
109 if (activate_button) {
110 config->activate_button = json_object_get_int(activate_button);
111 }
112
113 if (context_button) {
114 config->context_button = json_object_get_int(context_button);
115 }
116
117 if (secondary_button) {
118 config->secondary_button = json_object_get_int(secondary_button);
119 }
120#endif
121
86 // free previous outputs list 122 // free previous outputs list
87 int i; 123 int i;
88 for (i = 0; i < config->outputs->length; ++i) { 124 for (i = 0; i < config->outputs->length; ++i) {
diff --git a/swaybar/render.c b/swaybar/render.c
index 2eae997f..0b2ac438 100644
--- a/swaybar/render.c
+++ b/swaybar/render.c
@@ -8,6 +8,10 @@
8#include "swaybar/config.h" 8#include "swaybar/config.h"
9#include "swaybar/status_line.h" 9#include "swaybar/status_line.h"
10#include "swaybar/render.h" 10#include "swaybar/render.h"
11#ifdef ENABLE_TRAY
12#include "swaybar/tray/tray.h"
13#include "swaybar/tray/sni.h"
14#endif
11#include "log.h" 15#include "log.h"
12 16
13 17
@@ -297,6 +301,72 @@ void render(struct output *output, struct config *config, struct status_line *li
297 } 301 }
298 cairo_paint(cairo); 302 cairo_paint(cairo);
299 303
304#ifdef ENABLE_TRAY
305 // Tray icons
306 uint32_t tray_padding = config->tray_padding;
307 unsigned int tray_width = window->width * window->scale;
308 const int item_size = (window->height * window->scale) - (2 * tray_padding);
309
310 if (item_size < 0) {
311 // Can't render items if the padding is too large
312 goto no_tray;
313 }
314
315 if (config->tray_output && strcmp(config->tray_output, output->name) != 0) {
316 goto no_tray;
317 }
318
319 for (int i = 0; i < tray->items->length; ++i) {
320 struct StatusNotifierItem *item =
321 tray->items->items[i];
322 if (!item->image) {
323 continue;
324 }
325
326 struct sni_icon_ref *render_item = NULL;
327 int j;
328 for (j = i; j < output->items->length; ++j) {
329 struct sni_icon_ref *ref =
330 output->items->items[j];
331 if (ref->ref == item) {
332 render_item = ref;
333 break;
334 } else {
335 sni_icon_ref_free(ref);
336 list_del(output->items, j);
337 }
338 }
339
340 if (!render_item) {
341 render_item = sni_icon_ref_create(item, item_size);
342 list_add(output->items, render_item);
343 } else if (item->dirty) {
344 // item needs re-render
345 sni_icon_ref_free(render_item);
346 output->items->items[j] = render_item =
347 sni_icon_ref_create(item, item_size);
348 }
349
350 tray_width -= tray_padding;
351 tray_width -= item_size;
352
353 cairo_set_source_surface(cairo, render_item->icon, tray_width, tray_padding);
354 cairo_rectangle(cairo, tray_width, tray_padding, item_size, item_size);
355 cairo_fill(cairo);
356
357 item->dirty = false;
358 }
359
360
361 if (tray_width != window->width * window->scale) {
362 tray_width -= tray_padding;
363 }
364
365no_tray:
366#else
367 const int tray_width = window->width * window->scale;
368#endif
369
300 // Command output 370 // Command output
301 if (is_focused) { 371 if (is_focused) {
302 cairo_set_source_u32(cairo, config->colors.focused_statusline); 372 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
309 if (line->protocol == TEXT) { 379 if (line->protocol == TEXT) {
310 get_text_size(window->cairo, window->font, &width, &height, 380 get_text_size(window->cairo, window->font, &width, &height,
311 window->scale, config->pango_markup, "%s", line->text_line); 381 window->scale, config->pango_markup, "%s", line->text_line);
312 cairo_move_to(cairo, (window->width * window->scale) 382 cairo_move_to(cairo, tray_width - margin - width, margin);
313 - margin - width, margin);
314 pango_printf(window->cairo, window->font, window->scale, 383 pango_printf(window->cairo, window->font, window->scale,
315 config->pango_markup, "%s", line->text_line); 384 config->pango_markup, "%s", line->text_line);
316 } else if (line->protocol == I3BAR && line->block_line) { 385 } else if (line->protocol == I3BAR && line->block_line) {
317 double pos = (window->width * window->scale) - 0.5; 386 double pos = tray_width - 0.5;
318 bool edge = true; 387 bool edge = true;
319 for (i = line->block_line->length - 1; i >= 0; --i) { 388 for (i = line->block_line->length - 1; i >= 0; --i) {
320 struct status_block *block = line->block_line->items[i]; 389 struct status_block *block = line->block_line->items[i];
diff --git a/swaybar/tray/dbus.c b/swaybar/tray/dbus.c
new file mode 100644
index 00000000..333d398e
--- /dev/null
+++ b/swaybar/tray/dbus.c
@@ -0,0 +1,189 @@
1#define _XOPEN_SOURCE 500
2#include <stdio.h>
3#include <stdlib.h>
4#include <stdint.h>
5#include <stdbool.h>
6#include <poll.h>
7#include <signal.h>
8#include <time.h>
9#include <dbus/dbus.h>
10#include "swaybar/tray/dbus.h"
11#include "swaybar/event_loop.h"
12#include "log.h"
13
14DBusConnection *conn = NULL;
15
16static void dispatch_watch(int fd, short mask, void *data) {
17 sway_log(L_DEBUG, "Dispatching watch");
18 DBusWatch *watch = data;
19
20 if (!dbus_watch_get_enabled(watch)) {
21 return;
22 }
23
24 uint32_t flags = 0;
25
26 if (mask & POLLIN) {
27 flags |= DBUS_WATCH_READABLE;
28 } if (mask & POLLOUT) {
29 flags |= DBUS_WATCH_WRITABLE;
30 } if (mask & POLLHUP) {
31 flags |= DBUS_WATCH_HANGUP;
32 } if (mask & POLLERR) {
33 flags |= DBUS_WATCH_ERROR;
34 }
35
36 dbus_watch_handle(watch, flags);
37}
38
39static dbus_bool_t add_watch(DBusWatch *watch, void *_data) {
40 if (!dbus_watch_get_enabled(watch)) {
41 // Watch should not be polled
42 return TRUE;
43 }
44
45 short mask = 0;
46 uint32_t flags = dbus_watch_get_flags(watch);
47
48 if (flags & DBUS_WATCH_READABLE) {
49 mask |= POLLIN;
50 } if (flags & DBUS_WATCH_WRITABLE) {
51 mask |= POLLOUT;
52 }
53
54 int fd = dbus_watch_get_unix_fd(watch);
55
56 sway_log(L_DEBUG, "Adding DBus watch fd: %d", fd);
57 add_event(fd, mask, dispatch_watch, watch);
58
59 return TRUE;
60}
61
62static void remove_watch(DBusWatch *watch, void *_data) {
63 int fd = dbus_watch_get_unix_fd(watch);
64
65 remove_event(fd);
66}
67
68static void dispatch_timeout(timer_t timer, void *data) {
69 sway_log(L_DEBUG, "Dispatching DBus timeout");
70 DBusTimeout *timeout = data;
71
72 if (dbus_timeout_get_enabled(timeout)) {
73 dbus_timeout_handle(timeout);
74 }
75}
76
77static dbus_bool_t add_timeout(DBusTimeout *timeout, void *_data) {
78 if (!dbus_timeout_get_enabled(timeout)) {
79 return TRUE;
80 }
81
82 timer_t *timer = malloc(sizeof(timer_t));
83 if (!timer) {
84 sway_log(L_ERROR, "Cannot allocate memory");
85 return FALSE;
86 }
87 struct sigevent ev = {
88 .sigev_notify = SIGEV_NONE,
89 };
90
91 if (timer_create(CLOCK_MONOTONIC, &ev, timer)) {
92 sway_log(L_ERROR, "Could not create DBus timer");
93 return FALSE;
94 }
95
96 int interval = dbus_timeout_get_interval(timeout);
97 int interval_sec = interval / 1000;
98 int interval_msec = (interval_sec * 1000) - interval;
99
100 struct timespec period = {
101 (time_t) interval_sec,
102 ((long) interval_msec) * 1000 * 1000,
103 };
104 struct itimerspec time = {
105 period,
106 period,
107 };
108
109 timer_settime(*timer, 0, &time, NULL);
110
111 dbus_timeout_set_data(timeout, timer, free);
112
113 sway_log(L_DEBUG, "Adding DBus timeout. Interval: %ds %dms", interval_sec, interval_msec);
114 add_timer(*timer, dispatch_timeout, timeout);
115
116 return TRUE;
117}
118static void remove_timeout(DBusTimeout *timeout, void *_data) {
119 timer_t *timer = (timer_t *) dbus_timeout_get_data(timeout);
120 sway_log(L_DEBUG, "Removing DBus timeout.");
121
122 if (timer) {
123 remove_timer(*timer);
124 }
125}
126
127static bool should_dispatch = true;
128
129static void dispatch_status(DBusConnection *connection, DBusDispatchStatus new_status,
130 void *_data) {
131 if (new_status == DBUS_DISPATCH_DATA_REMAINS) {
132 should_dispatch = true;
133 }
134}
135
136/* Public functions below */
137
138void dispatch_dbus() {
139 if (!should_dispatch) {
140 return;
141 }
142
143 DBusDispatchStatus status;
144
145 do {
146 status = dbus_connection_dispatch(conn);
147 } while (status == DBUS_DISPATCH_DATA_REMAINS);
148
149 if (status != DBUS_DISPATCH_COMPLETE) {
150 sway_log(L_ERROR, "Cannot dispatch dbus events: %d", status);
151 }
152
153 should_dispatch = false;
154}
155
156int dbus_init() {
157 DBusError error;
158 dbus_error_init(&error);
159
160 conn = dbus_bus_get(DBUS_BUS_SESSION, &error);
161 dbus_connection_set_exit_on_disconnect(conn, FALSE);
162 if (dbus_error_is_set(&error)) {
163 sway_log(L_ERROR, "Cannot get bus connection: %s\n", error.message);
164 conn = NULL;
165 return -1;
166 }
167
168 sway_log(L_INFO, "Unique name: %s\n", dbus_bus_get_unique_name(conn));
169
170 // Will be called if dispatch status changes
171 dbus_connection_set_dispatch_status_function(conn, dispatch_status, NULL, NULL);
172
173 if (!dbus_connection_set_watch_functions(conn, add_watch, remove_watch,
174 NULL, NULL, NULL)) {
175 dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL);
176 sway_log(L_ERROR, "Failed to activate DBUS watch functions");
177 return -1;
178 }
179
180 if (!dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout,
181 NULL, NULL, NULL)) {
182 dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL);
183 dbus_connection_set_timeout_functions(conn, NULL, NULL, NULL, NULL, NULL);
184 sway_log(L_ERROR, "Failed to activate DBUS timeout functions");
185 return -1;
186 }
187
188 return 0;
189}
diff --git a/swaybar/tray/icon.c b/swaybar/tray/icon.c
new file mode 100644
index 00000000..29151a74
--- /dev/null
+++ b/swaybar/tray/icon.c
@@ -0,0 +1,404 @@
1#define _XOPEN_SOURCE 500
2#define _POSIX_C_SOURCE 200809L
3#include <stdio.h>
4#include <stdlib.h>
5#include <unistd.h>
6#include <string.h>
7#include <dirent.h>
8#include <sys/stat.h>
9#include <stdbool.h>
10#include <stdint.h>
11#include <limits.h>
12#include "swaybar/tray/icon.h"
13#include "swaybar/bar.h"
14#include "swaybar/config.h"
15#include "stringop.h"
16#include "log.h"
17
18/**
19 * REVIEW:
20 * This file repeats lots of "costly" operations that are the same for every
21 * icon. It's possible to create a dictionary or some other structure to cache
22 * these, though it may complicate things somewhat.
23 *
24 * Also parsing (index.theme) is currently pretty messy, so that could be made
25 * much better as well. Over all, things work, but are not optimal.
26 */
27
28/* Finds all themes that the given theme inherits */
29static list_t *find_inherits(const char *theme_dir) {
30 const char inherits[] = "Inherits";
31 const char index_name[] = "index.theme";
32 list_t *themes = create_list();
33 FILE *index = NULL;
34 char *path = malloc(strlen(theme_dir) + sizeof(index_name));
35 if (!path) {
36 goto fail;
37 }
38 if (!themes) {
39 goto fail;
40 }
41
42 strcpy(path, theme_dir);
43 strcat(path, index_name);
44
45 index = fopen(path, "r");
46 if (!index) {
47 goto fail;
48 }
49
50 char *buf = NULL;
51 size_t n = 0;
52 while (!feof(index)) {
53 getline(&buf, &n, index);
54 if (n <= sizeof(inherits) + 1) {
55 continue;
56 }
57 if (strncmp(inherits, buf, sizeof(inherits) - 1) == 0) {
58 char *themestr = buf + sizeof(inherits);
59 themes = split_string(themestr, ",");
60 break;
61 }
62 }
63 free(buf);
64
65fail:
66 free(path);
67 if (index) {
68 fclose(index);
69 }
70 return themes;
71}
72
73static bool isdir(const char *path) {
74 struct stat statbuf;
75 if (stat(path, &statbuf) != -1) {
76 if (S_ISDIR(statbuf.st_mode)) {
77 return true;
78 }
79 }
80 return false;
81
82}
83
84/**
85 * Returns the directory of a given theme if it exists.
86 * The returned pointer must be freed.
87 */
88static char *find_theme_dir(const char *theme) {
89 char *basedir;
90 char *icon_dir;
91
92 if (!theme) {
93 return NULL;
94 }
95
96 if (!(icon_dir = malloc(1024))) {
97 sway_log(L_ERROR, "Out of memory!");
98 goto fail;
99 }
100
101 if ((basedir = getenv("HOME"))) {
102 if (snprintf(icon_dir, 1024, "%s/.icons/%s", basedir, theme) >= 1024) {
103 sway_log(L_ERROR, "Path too long to render");
104 // XXX perhaps just goto trying in /usr/share? This
105 // shouldn't happen anyway, but might with a long global
106 goto fail;
107 }
108
109 if (isdir(icon_dir)) {
110 return icon_dir;
111 }
112 }
113
114 if ((basedir = getenv("XDG_DATA_DIRS"))) {
115 if (snprintf(icon_dir, 1024, "%s/icons/%s", basedir, theme) >= 1024) {
116 sway_log(L_ERROR, "Path too long to render");
117 // ditto
118 goto fail;
119 }
120
121 if (isdir(icon_dir)) {
122 return icon_dir;
123 }
124 }
125
126 // Spec says use "/usr/share/pixmaps/", but I see everything in
127 // "/usr/share/icons/" look it both, I suppose.
128 if (snprintf(icon_dir, 1024, "/usr/share/pixmaps/%s", theme) >= 1024) {
129 sway_log(L_ERROR, "Path too long to render");
130 goto fail;
131 }
132 if (isdir(icon_dir)) {
133 return icon_dir;
134 }
135
136 if (snprintf(icon_dir, 1024, "/usr/share/icons/%s", theme) >= 1024) {
137 sway_log(L_ERROR, "Path too long to render");
138 goto fail;
139 }
140 if (isdir(icon_dir)) {
141 return icon_dir;
142 }
143
144fail:
145 free(icon_dir);
146 sway_log(L_ERROR, "Could not find dir for theme: %s", theme);
147 return NULL;
148}
149
150/**
151 * Returns all theme dirs needed to be looked in for an icon.
152 * Does not check for duplicates
153 */
154static list_t *find_all_theme_dirs(const char *theme) {
155 list_t *dirs = create_list();
156 if (!dirs) {
157 return NULL;
158 }
159 char *dir = find_theme_dir(theme);
160 if (dir) {
161 list_add(dirs, dir);
162 list_t *inherits = find_inherits(dir);
163 list_cat(dirs, inherits);
164 list_free(inherits);
165 }
166 dir = find_theme_dir("hicolor");
167 if (dir) {
168 list_add(dirs, dir);
169 }
170
171 return dirs;
172}
173
174struct subdir {
175 int size;
176 char name[];
177};
178
179static int subdir_str_cmp(const void *_subdir, const void *_str) {
180 const struct subdir *subdir = _subdir;
181 const char *str = _str;
182 return strcmp(subdir->name, str);
183}
184/**
185 * Helper to find_subdirs. Acts similar to `split_string(subdirs, ",")` but
186 * generates a list of struct subdirs
187 */
188static list_t *split_subdirs(char *subdir_str) {
189 list_t *subdir_list = create_list();
190 char *copy = strdup(subdir_str);
191 if (!subdir_list || !copy) {
192 list_free(subdir_list);
193 free(copy);
194 return NULL;
195 }
196
197 char *token;
198 token = strtok(copy, ",");
199 while(token) {
200 int len = strlen(token) + 1;
201 struct subdir *subdir =
202 malloc(sizeof(struct subdir) + sizeof(char [len]));
203 if (!subdir) {
204 // Return what we have
205 return subdir_list;
206 }
207 subdir->size = 0;
208 strcpy(subdir->name, token);
209
210 list_add(subdir_list, subdir);
211
212 token = strtok(NULL, ",");
213 }
214 free(copy);
215
216 return subdir_list;
217}
218/**
219 * Returns a list of all subdirectories of a theme.
220 * Take note: the subdir names are all relative to `theme_dir` and must be
221 * combined with it to form a valid directory.
222 *
223 * Each member of the list is of type (struct subdir *) this struct contains
224 * the name of the subdir, along with size information. These must be freed
225 * bye the caller.
226 *
227 * This currently ignores min and max sizes of icons.
228 */
229static list_t* find_theme_subdirs(const char *theme_dir) {
230 const char index_name[] = "/index.theme";
231 list_t *dirs = NULL;
232 char *path = malloc(strlen(theme_dir) + sizeof(index_name));
233 FILE *index = NULL;
234 if (!path) {
235 sway_log(L_ERROR, "Failed to allocate memory");
236 goto fail;
237 }
238
239 strcpy(path, theme_dir);
240 strcat(path, index_name);
241
242 index = fopen(path, "r");
243 if (!index) {
244 sway_log(L_ERROR, "Could not open file: %s", path);
245 goto fail;
246 }
247
248 char *buf = NULL;
249 size_t n = 0;
250 while (!feof(index)) {
251 const char directories[] = "Directories";
252 getline(&buf, &n, index);
253 if (n <= sizeof(directories) + 1) {
254 continue;
255 }
256 if (strncmp(directories, buf, sizeof(directories) - 1) == 0) {
257 char *dirstr = buf + sizeof(directories);
258 dirs = split_subdirs(dirstr);
259 break;
260 }
261 }
262 // Now, find the size of each dir
263 struct subdir *current_subdir = NULL;
264 while (!feof(index)) {
265 const char size[] = "Size";
266 getline(&buf, &n, index);
267
268 if (buf[0] == '[') {
269 int len = strlen(buf);
270 if (buf[len-1] == '\n') {
271 len--;
272 }
273 // replace ']'
274 buf[len-1] = '\0';
275
276 int index;
277 if ((index = list_seq_find(dirs, subdir_str_cmp, buf+1)) != -1) {
278 current_subdir = (dirs->items[index]);
279 }
280 }
281
282 if (strncmp(size, buf, sizeof(size) - 1) == 0) {
283 if (current_subdir) {
284 current_subdir->size = atoi(buf + sizeof(size));
285 }
286 }
287 }
288 free(buf);
289fail:
290 free(path);
291 if (index) {
292 fclose(index);
293 }
294 return dirs;
295}
296
297/* Returns the file of an icon given its name and size */
298static char *find_icon_file(const char *name, int size) {
299 int namelen = strlen(name);
300 list_t *dirs = find_all_theme_dirs(swaybar.config->icon_theme);
301 if (!dirs) {
302 return NULL;
303 }
304 int min_size_diff = INT_MAX;
305 char *current_file = NULL;
306
307 for (int i = 0; i < dirs->length; ++i) {
308 char *dir = dirs->items[i];
309 list_t *subdirs = find_theme_subdirs(dir);
310
311 if (!subdirs) {
312 continue;
313 }
314
315 for (int i = 0; i < subdirs->length; ++i) {
316 struct subdir *subdir = subdirs->items[i];
317
318 // Only use an unsized if we don't already have a
319 // canidate this should probably change to allow svgs
320 if (!subdir->size && current_file) {
321 continue;
322 }
323
324 int size_diff = abs(size - subdir->size);
325
326 if (size_diff >= min_size_diff) {
327 continue;
328 }
329
330 char *path = malloc(strlen(subdir->name) + strlen(dir) + 2);
331
332 strcpy(path, dir);
333 path[strlen(dir)] = '/';
334 strcpy(path + strlen(dir) + 1, subdir->name);
335
336 DIR *icons = opendir(path);
337 if (!icons) {
338 free(path);
339 continue;
340 }
341
342 struct dirent *direntry;
343 while ((direntry = readdir(icons)) != NULL) {
344 int len = strlen(direntry->d_name);
345 if (len <= namelen + 2) { //must have some ext
346 continue;
347 }
348 if (strncmp(direntry->d_name, name, namelen) == 0) {
349 char *ext = direntry->d_name + namelen + 1;
350#ifdef WITH_GDK_PIXBUF
351 if (strcmp(ext, "png") == 0 ||
352 strcmp(ext, "xpm") == 0 ||
353 strcmp(ext, "svg") == 0) {
354#else
355 if (strcmp(ext, "png") == 0) {
356#endif
357 free(current_file);
358 char *icon_path = malloc(strlen(path) + len + 2);
359
360 strcpy(icon_path, path);
361 icon_path[strlen(path)] = '/';
362 strcpy(icon_path + strlen(path) + 1, direntry->d_name);
363 current_file = icon_path;
364 min_size_diff = size_diff;
365 }
366 }
367 }
368 free(path);
369 closedir(icons);
370 }
371 free_flat_list(subdirs);
372 }
373 free_flat_list(dirs);
374
375 return current_file;
376}
377
378cairo_surface_t *find_icon(const char *name, int size) {
379 char *image_path = find_icon_file(name, size);
380 if (image_path == NULL) {
381 return NULL;
382 }
383
384 cairo_surface_t *image = NULL;
385#ifdef WITH_GDK_PIXBUF
386 GError *err = NULL;
387 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(image_path, &err);
388 if (!pixbuf) {
389 sway_log(L_ERROR, "Failed to load icon image: %s", err->message);
390 }
391 image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf);
392 g_object_unref(pixbuf);
393#else
394 // TODO make svg work? cairo supports it. maybe remove gdk alltogether
395 image = cairo_image_surface_create_from_png(image_path);
396#endif //WITH_GDK_PIXBUF
397 if (!image) {
398 sway_log(L_ERROR, "Could not read icon image");
399 return NULL;
400 }
401
402 free(image_path);
403 return image;
404}
diff --git a/swaybar/tray/sni.c b/swaybar/tray/sni.c
new file mode 100644
index 00000000..f0638dca
--- /dev/null
+++ b/swaybar/tray/sni.c
@@ -0,0 +1,463 @@
1#define _XOPEN_SOURCE 500
2#include <stdlib.h>
3#include <stdio.h>
4#include <string.h>
5#include <stdint.h>
6#include <stdbool.h>
7#include <dbus/dbus.h>
8#include <arpa/inet.h>
9#include <netinet/in.h>
10#include "swaybar/tray/dbus.h"
11#include "swaybar/tray/sni.h"
12#include "swaybar/tray/icon.h"
13#include "swaybar/bar.h"
14#include "client/cairo.h"
15#include "log.h"
16
17// Not sure what this is but cairo needs it.
18static const cairo_user_data_key_t cairo_user_data_key;
19
20struct sni_icon_ref *sni_icon_ref_create(struct StatusNotifierItem *item,
21 int height) {
22 struct sni_icon_ref *sni_ref = malloc(sizeof(struct sni_icon_ref));
23 if (!sni_ref) {
24 return NULL;
25 }
26 sni_ref->icon = cairo_image_surface_scale(item->image, height, height);
27 sni_ref->ref = item;
28
29 return sni_ref;
30}
31
32void sni_icon_ref_free(struct sni_icon_ref *sni_ref) {
33 if (!sni_ref) {
34 return;
35 }
36 cairo_surface_destroy(sni_ref->icon);
37 free(sni_ref);
38}
39
40/* Gets the pixmap of an icon */
41static void reply_icon(DBusPendingCall *pending, void *_data) {
42 struct StatusNotifierItem *item = _data;
43
44 DBusMessage *reply = dbus_pending_call_steal_reply(pending);
45
46 if (!reply) {
47 sway_log(L_ERROR, "Did not get reply");
48 goto bail;
49 }
50
51 int message_type = dbus_message_get_type(reply);
52
53 if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
54 char *msg;
55
56 dbus_message_get_args(reply, NULL,
57 DBUS_TYPE_STRING, &msg,
58 DBUS_TYPE_INVALID);
59
60 sway_log(L_ERROR, "Message is error: %s", msg);
61 goto bail;
62 }
63
64 DBusMessageIter iter;
65 DBusMessageIter variant; /* v[a(iiay)] */
66 DBusMessageIter array; /* a(iiay) */
67 DBusMessageIter d_struct; /* (iiay) */
68 DBusMessageIter icon; /* ay */
69
70 dbus_message_iter_init(reply, &iter);
71
72 // Each if here checks the types above before recursing
73 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
74 sway_log(L_ERROR, "Relpy type incorrect");
75 sway_log(L_ERROR, "Should be \"v\", is \"%s\"",
76 dbus_message_iter_get_signature(&iter));
77 goto bail;
78 }
79 dbus_message_iter_recurse(&iter, &variant);
80
81 if (strcmp("a(iiay)", dbus_message_iter_get_signature(&variant)) != 0) {
82 sway_log(L_ERROR, "Relpy type incorrect");
83 sway_log(L_ERROR, "Should be \"a(iiay)\", is \"%s\"",
84 dbus_message_iter_get_signature(&variant));
85 goto bail;
86 }
87
88 if (dbus_message_iter_get_element_count(&variant) == 0) {
89 // Can't recurse if there are no items
90 sway_log(L_INFO, "Item has no icon");
91 goto bail;
92 }
93 dbus_message_iter_recurse(&variant, &array);
94
95 dbus_message_iter_recurse(&array, &d_struct);
96
97 int width;
98 dbus_message_iter_get_basic(&d_struct, &width);
99 dbus_message_iter_next(&d_struct);
100
101 int height;
102 dbus_message_iter_get_basic(&d_struct, &height);
103 dbus_message_iter_next(&d_struct);
104
105 int len = dbus_message_iter_get_element_count(&d_struct);
106
107 if (!len) {
108 sway_log(L_ERROR, "No icon data");
109 goto bail;
110 }
111
112 // Also implies len % 4 == 0, useful below
113 if (len != width * height * 4) {
114 sway_log(L_ERROR, "Incorrect array size passed");
115 goto bail;
116 }
117
118 dbus_message_iter_recurse(&d_struct, &icon);
119
120 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
121 // FIXME support a variable stride
122 // (works on my machine though for all tested widths)
123 if (!sway_assert(stride == width * 4, "Stride must be equal to byte length")) {
124 goto bail;
125 }
126
127 // Data is by reference, no need to free
128 uint8_t *message_data;
129 dbus_message_iter_get_fixed_array(&icon, &message_data, &len);
130
131 uint8_t *image_data = malloc(stride * height);
132 if (!image_data) {
133 sway_log(L_ERROR, "Could not allocate memory for icon");
134 goto bail;
135 }
136
137 // Transform from network byte order to host byte order
138 // Assumptions are safe because the equality above
139 uint32_t *network = (uint32_t *) message_data;
140 uint32_t *host = (uint32_t *)image_data;
141 for (int i = 0; i < width * height; ++i) {
142 host[i] = ntohl(network[i]);
143 }
144
145 cairo_surface_t *image = cairo_image_surface_create_for_data(
146 image_data, CAIRO_FORMAT_ARGB32,
147 width, height, stride);
148
149 if (image) {
150 if (item->image) {
151 cairo_surface_destroy(item->image);
152 }
153 item->image = image;
154 // Free the image data on surface destruction
155 cairo_surface_set_user_data(image,
156 &cairo_user_data_key,
157 image_data,
158 free);
159 item->dirty = true;
160 dirty = true;
161
162 dbus_message_unref(reply);
163 return;
164 } else {
165 sway_log(L_ERROR, "Could not create image surface");
166 free(image_data);
167 }
168
169bail:
170 if (reply) {
171 dbus_message_unref(reply);
172 }
173 sway_log(L_ERROR, "Could not get icon from item");
174 return;
175}
176static void send_icon_msg(struct StatusNotifierItem *item) {
177 DBusPendingCall *pending;
178 DBusMessage *message = dbus_message_new_method_call(
179 item->name,
180 "/StatusNotifierItem",
181 "org.freedesktop.DBus.Properties",
182 "Get");
183 const char *iface;
184 if (item->kde_special_snowflake) {
185 iface = "org.kde.StatusNotifierItem";
186 } else {
187 iface = "org.freedesktop.StatusNotifierItem";
188 }
189 const char *prop = "IconPixmap";
190
191 dbus_message_append_args(message,
192 DBUS_TYPE_STRING, &iface,
193 DBUS_TYPE_STRING, &prop,
194 DBUS_TYPE_INVALID);
195
196 bool status =
197 dbus_connection_send_with_reply(conn, message, &pending, -1);
198
199 dbus_message_unref(message);
200
201 if (!(pending || status)) {
202 sway_log(L_ERROR, "Could not get item icon");
203 return;
204 }
205
206 dbus_pending_call_set_notify(pending, reply_icon, item, NULL);
207}
208
209/* Get an icon by its name */
210static void reply_icon_name(DBusPendingCall *pending, void *_data) {
211 struct StatusNotifierItem *item = _data;
212
213 DBusMessage *reply = dbus_pending_call_steal_reply(pending);
214
215 if (!reply) {
216 sway_log(L_INFO, "Got no icon name reply from item");
217 goto bail;
218 }
219
220 int message_type = dbus_message_get_type(reply);
221
222 if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
223 char *msg;
224
225 dbus_message_get_args(reply, NULL,
226 DBUS_TYPE_STRING, &msg,
227 DBUS_TYPE_INVALID);
228
229 sway_log(L_INFO, "Could not get icon name: %s", msg);
230 goto bail;
231 }
232
233 DBusMessageIter iter; /* v[s] */
234 DBusMessageIter variant; /* s */
235
236 dbus_message_iter_init(reply, &iter);
237 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
238 sway_log(L_ERROR, "Relpy type incorrect");
239 sway_log(L_ERROR, "Should be \"v\", is \"%s\"",
240 dbus_message_iter_get_signature(&iter));
241 goto bail;
242 }
243 dbus_message_iter_recurse(&iter, &variant);
244
245
246 if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_STRING) {
247 sway_log(L_ERROR, "Relpy type incorrect");
248 sway_log(L_ERROR, "Should be \"s\", is \"%s\"",
249 dbus_message_iter_get_signature(&iter));
250 goto bail;
251 }
252
253 char *icon_name;
254 dbus_message_iter_get_basic(&variant, &icon_name);
255
256 cairo_surface_t *image = find_icon(icon_name, 256);
257
258 if (image) {
259 sway_log(L_DEBUG, "Icon for %s found with size %d", icon_name,
260 cairo_image_surface_get_width(image));
261 if (item->image) {
262 cairo_surface_destroy(item->image);
263 }
264 item->image = image;
265 item->dirty = true;
266 dirty = true;
267
268 dbus_message_unref(reply);
269 return;
270 }
271
272bail:
273 if (reply) {
274 dbus_message_unref(reply);
275 }
276 // Now try the pixmap
277 send_icon_msg(item);
278 return;
279}
280static void send_icon_name_msg(struct StatusNotifierItem *item) {
281 DBusPendingCall *pending;
282 DBusMessage *message = dbus_message_new_method_call(
283 item->name,
284 "/StatusNotifierItem",
285 "org.freedesktop.DBus.Properties",
286 "Get");
287 const char *iface;
288 if (item->kde_special_snowflake) {
289 iface = "org.kde.StatusNotifierItem";
290 } else {
291 iface = "org.freedesktop.StatusNotifierItem";
292 }
293 const char *prop = "IconName";
294
295 dbus_message_append_args(message,
296 DBUS_TYPE_STRING, &iface,
297 DBUS_TYPE_STRING, &prop,
298 DBUS_TYPE_INVALID);
299
300 bool status =
301 dbus_connection_send_with_reply(conn, message, &pending, -1);
302
303 dbus_message_unref(message);
304
305 if (!(pending || status)) {
306 sway_log(L_ERROR, "Could not get item icon name");
307 return;
308 }
309
310 dbus_pending_call_set_notify(pending, reply_icon_name, item, NULL);
311}
312
313void get_icon(struct StatusNotifierItem *item) {
314 send_icon_name_msg(item);
315}
316
317void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
318 const char *iface =
319 (item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
320 : "org.freedesktop.StatusNotifierItem");
321 DBusMessage *message = dbus_message_new_method_call(
322 item->name,
323 "/StatusNotifierItem",
324 iface,
325 "Activate");
326
327 dbus_message_append_args(message,
328 DBUS_TYPE_INT32, &x,
329 DBUS_TYPE_INT32, &y,
330 DBUS_TYPE_INVALID);
331
332 dbus_connection_send(conn, message, NULL);
333
334 dbus_message_unref(message);
335}
336
337void sni_context_menu(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
338 const char *iface =
339 (item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
340 : "org.freedesktop.StatusNotifierItem");
341 DBusMessage *message = dbus_message_new_method_call(
342 item->name,
343 "/StatusNotifierItem",
344 iface,
345 "ContextMenu");
346
347 dbus_message_append_args(message,
348 DBUS_TYPE_INT32, &x,
349 DBUS_TYPE_INT32, &y,
350 DBUS_TYPE_INVALID);
351
352 dbus_connection_send(conn, message, NULL);
353
354 dbus_message_unref(message);
355}
356void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
357 const char *iface =
358 (item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
359 : "org.freedesktop.StatusNotifierItem");
360 DBusMessage *message = dbus_message_new_method_call(
361 item->name,
362 "/StatusNotifierItem",
363 iface,
364 "SecondaryActivate");
365
366 dbus_message_append_args(message,
367 DBUS_TYPE_INT32, &x,
368 DBUS_TYPE_INT32, &y,
369 DBUS_TYPE_INVALID);
370
371 dbus_connection_send(conn, message, NULL);
372
373 dbus_message_unref(message);
374}
375
376static void get_unique_name(struct StatusNotifierItem *item) {
377 // I think that we're fine being sync here becaues the message is
378 // directly to the message bus. Could be async though.
379 DBusMessage *message = dbus_message_new_method_call(
380 "org.freedesktop.DBus",
381 "/org/freedesktop/DBus",
382 "org.freedesktop.DBus",
383 "GetNameOwner");
384
385 dbus_message_append_args(message,
386 DBUS_TYPE_STRING, &item->name,
387 DBUS_TYPE_INVALID);
388
389 DBusMessage *reply = dbus_connection_send_with_reply_and_block(
390 conn, message, -1, NULL);
391
392 dbus_message_unref(message);
393
394 if (!reply) {
395 sway_log(L_ERROR, "Could not get unique name for item: %s",
396 item->name);
397 return;
398 }
399
400 if (!dbus_message_get_args(reply, NULL,
401 DBUS_TYPE_STRING, &item->unique_name,
402 DBUS_TYPE_INVALID)) {
403 item->unique_name = NULL;
404 sway_log(L_ERROR, "Error parsing method args");
405 }
406
407 dbus_message_unref(reply);
408}
409
410struct StatusNotifierItem *sni_create(const char *name) {
411 struct StatusNotifierItem *item = malloc(sizeof(struct StatusNotifierItem));
412 item->name = strdup(name);
413 item->unique_name = NULL;
414 item->image = NULL;
415 item->dirty = false;
416
417 // If it doesn't use this name then assume that it uses the KDE spec
418 // This is because xembed-sni-proxy uses neither "org.freedesktop" nor
419 // "org.kde" and just gives us the items "unique name"
420 //
421 // We could use this to our advantage and fill out the "unique name"
422 // field with the given name if it is neither freedesktop or kde, but
423 // that's makes us rely on KDE hackyness which is bad practice
424 const char freedesktop_name[] = "org.freedesktop";
425 if (strncmp(name, freedesktop_name, sizeof(freedesktop_name) - 1) != 0) {
426 item->kde_special_snowflake = true;
427 } else {
428 item->kde_special_snowflake = false;
429 }
430
431 get_icon(item);
432
433 get_unique_name(item);
434
435 return item;
436}
437/* Return true if `item` has a name of `str` */
438int sni_str_cmp(const void *_item, const void *_str) {
439 const struct StatusNotifierItem *item = _item;
440 const char *str = _str;
441
442 return strcmp(item->name, str);
443}
444/* Returns true if `item` has a unique name of `str` */
445int sni_uniq_cmp(const void *_item, const void *_str) {
446 const struct StatusNotifierItem *item = _item;
447 const char *str = _str;
448
449 if (!item->unique_name) {
450 return false;
451 }
452 return strcmp(item->unique_name, str);
453}
454void sni_free(struct StatusNotifierItem *item) {
455 if (!item) {
456 return;
457 }
458 free(item->name);
459 if (item->image) {
460 cairo_surface_destroy(item->image);
461 }
462 free(item);
463}
diff --git a/swaybar/tray/sni_watcher.c b/swaybar/tray/sni_watcher.c
new file mode 100644
index 00000000..388e181d
--- /dev/null
+++ b/swaybar/tray/sni_watcher.c
@@ -0,0 +1,487 @@
1#define _XOPEN_SOURCE 500
2#include <unistd.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <stdbool.h>
7#include <dbus/dbus.h>
8#include "swaybar/tray/dbus.h"
9#include "list.h"
10#include "log.h"
11
12static list_t *items = NULL;
13static list_t *hosts = NULL;
14
15/**
16 * Describes the function of the StatusNotifierWatcher
17 * See https://freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierWatcher/
18 *
19 * We also implement KDE's special snowflake protocol, it's like this but with
20 * all occurrences 'freedesktop' replaced with 'kde'. There is no KDE introspect.
21 */
22static const char *interface_xml =
23 "<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'"
24 "'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>"
25 "<node>"
26 " <interface name='org.freedesktop.DBus.Introspectable'>"
27 " <method name='Introspect'>"
28 " <arg name='xml_data' direction='out' type='s'/>"
29 " </method>"
30 " </interface>"
31 " <interface name='org.freedesktop.DBus.Properties'>"
32 " <method name='Get'>"
33 " <arg name='interface' direction='in' type='s'/>"
34 " <arg name='propname' direction='in' type='s'/>"
35 " <arg name='value' direction='out' type='v'/>"
36 " </method>"
37 " <method name='Set'>"
38 " <arg name='interface' direction='in' type='s'/>"
39 " <arg name='propname' direction='in' type='s'/>"
40 " <arg name='value' direction='in' type='v'/>"
41 " </method>"
42 " <method name='GetAll'>"
43 " <arg name='interface' direction='in' type='s'/>"
44 " <arg name='props' direction='out' type='a{sv}'/>"
45 " </method>"
46 " </interface>"
47 " <interface name='org.freedesktop.StatusNotifierWatcher'>"
48 " <method name='RegisterStatusNotifierItem'>"
49 " <arg type='s' name='service' direction='in'/>"
50 " </method>"
51 " <method name='RegisterStatusNotifierHost'>"
52 " <arg type='s' name='service' direction='in'/>"
53 " </method>"
54 " <property name='RegisteredStatusNotifierItems' type='as' access='read'/>"
55 " <property name='IsStatusNotifierHostRegistered' type='b' access='read'/>"
56 " <property name='ProtocolVersion' type='i' access='read'/>"
57 " <signal name='StatusNotifierItemRegistered'>"
58 " <arg type='s' name='service' direction='out'/>"
59 " </signal>"
60 " <signal name='StatusNotifierItemUnregistered'>"
61 " <arg type='s' name='service' direction='out'/>"
62 " </signal>"
63 " <signal name='StatusNotifierHostRegistered'>"
64 " <arg type='' name='service' direction='out'/>"
65 " </signal>"
66 " </interface>"
67 "</node>";
68
69static void host_registered_signal(DBusConnection *connection) {
70 // Send one signal for each protocol
71 DBusMessage *signal = dbus_message_new_signal(
72 "/StatusNotifierWatcher",
73 "org.freedesktop.StatusNotifierWatcher",
74 "StatusNotifierHostRegistered");
75
76 dbus_connection_send(connection, signal, NULL);
77 dbus_message_unref(signal);
78
79
80 signal = dbus_message_new_signal(
81 "/StatusNotifierWatcher",
82 "org.kde.StatusNotifierWatcher",
83 "StatusNotifierHostRegistered");
84
85 dbus_connection_send(connection, signal, NULL);
86 dbus_message_unref(signal);
87}
88static void item_registered_signal(DBusConnection *connection, const char *name) {
89 DBusMessage *signal = dbus_message_new_signal(
90 "/StatusNotifierWatcher",
91 "org.freedesktop.StatusNotifierWatcher",
92 "StatusNotifierItemRegistered");
93 dbus_message_append_args(signal,
94 DBUS_TYPE_STRING, &name,
95 DBUS_TYPE_INVALID);
96 dbus_connection_send(connection, signal, NULL);
97 dbus_message_unref(signal);
98
99 signal = dbus_message_new_signal(
100 "/StatusNotifierWatcher",
101 "org.kde.StatusNotifierWatcher",
102 "StatusNotifierItemRegistered");
103 dbus_message_append_args(signal,
104 DBUS_TYPE_STRING, &name,
105 DBUS_TYPE_INVALID);
106 dbus_connection_send(connection, signal, NULL);
107 dbus_message_unref(signal);
108}
109static void item_unregistered_signal(DBusConnection *connection, const char *name) {
110 DBusMessage *signal = dbus_message_new_signal(
111 "/StatusNotifierWatcher",
112 "org.freedesktop.StatusNotifierWatcher",
113 "StatusNotifierItemUnregistered");
114 dbus_message_append_args(signal,
115 DBUS_TYPE_STRING, &name,
116 DBUS_TYPE_INVALID);
117 dbus_connection_send(connection, signal, NULL);
118 dbus_message_unref(signal);
119
120 signal = dbus_message_new_signal(
121 "/StatusNotifierWatcher",
122 "org.kde.StatusNotifierWatcher",
123 "StatusNotifierItemUnregistered");
124 dbus_message_append_args(signal,
125 DBUS_TYPE_STRING, &name,
126 DBUS_TYPE_INVALID);
127 dbus_connection_send(connection, signal, NULL);
128 dbus_message_unref(signal);
129}
130
131static void respond_to_introspect(DBusConnection *connection, DBusMessage *request) {
132 DBusMessage *reply;
133
134 reply = dbus_message_new_method_return(request);
135 dbus_message_append_args(reply,
136 DBUS_TYPE_STRING, &interface_xml,
137 DBUS_TYPE_INVALID);
138 dbus_connection_send(connection, reply, NULL);
139 dbus_message_unref(reply);
140}
141
142static void register_item(DBusConnection *connection, DBusMessage *message) {
143 DBusError error;
144 char *name;
145
146 dbus_error_init(&error);
147 if (!dbus_message_get_args(message, &error,
148 DBUS_TYPE_STRING, &name,
149 DBUS_TYPE_INVALID)) {
150 sway_log(L_ERROR, "Error parsing method args: %s\n", error.message);
151 }
152
153 name = strdup(name);
154 sway_log(L_INFO, "RegisterStatusNotifierItem called with \"%s\"\n", name);
155
156 // Don't add duplicate or not real item
157 if (list_seq_find(items, (int (*)(const void *, const void *))strcmp, name) != -1) {
158 return;
159 }
160 if (!dbus_bus_name_has_owner(connection, name, &error)) {
161 return;
162 }
163
164 list_add(items, name);
165 item_registered_signal(connection, name);
166
167 // It's silly, but xembedsniproxy wants a reply for this function
168 DBusMessage *reply = dbus_message_new_method_return(message);
169 dbus_connection_send(connection, reply, NULL);
170 dbus_message_unref(reply);
171}
172
173static void register_host(DBusConnection *connection, DBusMessage *message) {
174 DBusError error;
175 char *name;
176
177 dbus_error_init(&error);
178 if (!dbus_message_get_args(message, &error,
179 DBUS_TYPE_STRING, &name,
180 DBUS_TYPE_INVALID)) {
181 sway_log(L_ERROR, "Error parsing method args: %s\n", error.message);
182 }
183
184 sway_log(L_INFO, "RegisterStatusNotifierHost called with \"%s\"\n", name);
185
186 // Don't add duplicate or not real host
187 if (list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name) != -1) {
188 return;
189 }
190 if (!dbus_bus_name_has_owner(connection, name, &error)) {
191 return;
192 }
193
194 list_add(hosts, strdup(name));
195 host_registered_signal(connection);
196}
197
198static void get_property(DBusConnection *connection, DBusMessage *message) {
199 DBusError error;
200 char *interface;
201 char *property;
202
203 dbus_error_init(&error);
204 if (!dbus_message_get_args(message, &error,
205 DBUS_TYPE_STRING, &interface,
206 DBUS_TYPE_STRING, &property,
207 DBUS_TYPE_INVALID)) {
208 sway_log(L_ERROR, "Error parsing prop args: %s\n", error.message);
209 return;
210 }
211
212 if (strcmp(property, "RegisteredStatusNotifierItems") == 0) {
213 sway_log(L_INFO, "Replying with items\n");
214 DBusMessage *reply;
215 reply = dbus_message_new_method_return(message);
216 DBusMessageIter iter;
217 DBusMessageIter sub;
218 DBusMessageIter subsub;
219
220 dbus_message_iter_init_append(reply, &iter);
221
222 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
223 "as", &sub);
224 dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY,
225 "s", &subsub);
226
227 for (int i = 0; i < items->length; ++i) {
228 dbus_message_iter_append_basic(&subsub,
229 DBUS_TYPE_STRING, &items->items[i]);
230 }
231
232 dbus_message_iter_close_container(&sub, &subsub);
233 dbus_message_iter_close_container(&iter, &sub);
234
235 dbus_connection_send(connection, reply, NULL);
236 dbus_message_unref(reply);
237 } else if (strcmp(property, "IsStatusNotifierHostRegistered") == 0) {
238 DBusMessage *reply;
239 DBusMessageIter iter;
240 DBusMessageIter sub;
241 int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1;
242
243 reply = dbus_message_new_method_return(message);
244
245 dbus_message_iter_init_append(reply, &iter);
246
247 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
248 "b", &sub);
249 dbus_message_iter_append_basic(&sub,
250 DBUS_TYPE_BOOLEAN, &registered);
251
252 dbus_message_iter_close_container(&iter, &sub);
253
254 dbus_connection_send(connection, reply, NULL);
255 dbus_message_unref(reply);
256 } else if (strcmp(property, "ProtocolVersion") == 0) {
257 DBusMessage *reply;
258 DBusMessageIter iter;
259 DBusMessageIter sub;
260 const int version = 0;
261
262 reply = dbus_message_new_method_return(message);
263
264 dbus_message_iter_init_append(reply, &iter);
265
266 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
267 "i", &sub);
268 dbus_message_iter_append_basic(&sub,
269 DBUS_TYPE_INT32, &version);
270
271 dbus_message_iter_close_container(&iter, &sub);
272 dbus_connection_send(connection, reply, NULL);
273 dbus_message_unref(reply);
274 }
275}
276
277static void set_property(DBusConnection *connection, DBusMessage *message) {
278 // All properties are read only and we don't allow new properties
279 return;
280}
281
282static void get_all(DBusConnection *connection, DBusMessage *message) {
283 DBusMessage *reply;
284 reply = dbus_message_new_method_return(message);
285 DBusMessageIter iter; /* a{v} */
286 DBusMessageIter arr;
287 DBusMessageIter dict;
288 DBusMessageIter sub;
289 DBusMessageIter subsub;
290 int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1;
291 const int version = 0;
292 const char *prop;
293
294 // Could clean this up with a function for each prop
295 dbus_message_iter_init_append(reply, &iter);
296 dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
297 "{sv}", &arr);
298
299 prop = "RegisteredStatusNotifierItems";
300 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
301 NULL, &dict);
302 dbus_message_iter_append_basic(&dict,
303 DBUS_TYPE_STRING, &prop);
304 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
305 "as", &sub);
306 dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY,
307 "s", &subsub);
308 for (int i = 0; i < items->length; ++i) {
309 dbus_message_iter_append_basic(&subsub,
310 DBUS_TYPE_STRING, &items->items[i]);
311 }
312 dbus_message_iter_close_container(&sub, &subsub);
313 dbus_message_iter_close_container(&dict, &sub);
314 dbus_message_iter_close_container(&arr, &dict);
315
316 prop = "IsStatusNotifierHostRegistered";
317 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
318 NULL, &dict);
319 dbus_message_iter_append_basic(&dict,
320 DBUS_TYPE_STRING, &prop);
321 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
322 "b", &sub);
323 dbus_message_iter_append_basic(&sub,
324 DBUS_TYPE_BOOLEAN, &registered);
325 dbus_message_iter_close_container(&dict, &sub);
326 dbus_message_iter_close_container(&arr, &dict);
327
328 prop = "ProtocolVersion";
329 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
330 NULL, &dict);
331 dbus_message_iter_append_basic(&dict,
332 DBUS_TYPE_STRING, &prop);
333 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
334 "i", &sub);
335 dbus_message_iter_append_basic(&sub,
336 DBUS_TYPE_INT32, &version);
337 dbus_message_iter_close_container(&dict, &sub);
338 dbus_message_iter_close_container(&arr, &dict);
339
340 dbus_message_iter_close_container(&iter, &arr);
341
342 dbus_connection_send(connection, reply, NULL);
343 dbus_message_unref(reply);
344}
345
346static DBusHandlerResult message_handler(DBusConnection *connection,
347 DBusMessage *message, void *data) {
348 const char *interface_name = dbus_message_get_interface(message);
349 const char *member_name = dbus_message_get_member(message);
350
351 // In order of the xml above
352 if (strcmp(interface_name, "org.freedesktop.DBus.Introspectable") == 0 &&
353 strcmp(member_name, "Introspect") == 0) {
354 // We don't have an introspect for KDE
355 respond_to_introspect(connection, message);
356 return DBUS_HANDLER_RESULT_HANDLED;
357 } else if (strcmp(interface_name, "org.freedesktop.DBus.Properties") == 0) {
358 if (strcmp(member_name, "Get") == 0) {
359 get_property(connection, message);
360 return DBUS_HANDLER_RESULT_HANDLED;
361 } else if (strcmp(member_name, "Set") == 0) {
362 set_property(connection, message);
363 return DBUS_HANDLER_RESULT_HANDLED;
364 } else if (strcmp(member_name, "GetAll") == 0) {
365 get_all(connection, message);
366 return DBUS_HANDLER_RESULT_HANDLED;
367 } else {
368 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
369 }
370 } else if (strcmp(interface_name, "org.freedesktop.StatusNotifierWatcher") == 0 ||
371 strcmp(interface_name, "org.kde.StatusNotifierWatcher") == 0) {
372 if (strcmp(member_name, "RegisterStatusNotifierItem") == 0) {
373 register_item(connection, message);
374 return DBUS_HANDLER_RESULT_HANDLED;
375 } else if (strcmp(member_name, "RegisterStatusNotifierHost") == 0) {
376 register_host(connection, message);
377 return DBUS_HANDLER_RESULT_HANDLED;
378 } else {
379 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
380 }
381 }
382 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
383}
384
385static DBusHandlerResult signal_handler(DBusConnection *connection,
386 DBusMessage *message, void *_data) {
387 if (dbus_message_is_signal(message, "org.freedesktop.DBus", "NameOwnerChanged")) {
388 // Only eat the message if it is name that we are watching
389 const char *name;
390 const char *old_owner;
391 const char *new_owner;
392 int index;
393 if (!dbus_message_get_args(message, NULL,
394 DBUS_TYPE_STRING, &name,
395 DBUS_TYPE_STRING, &old_owner,
396 DBUS_TYPE_STRING, &new_owner,
397 DBUS_TYPE_INVALID)) {
398 sway_log(L_ERROR, "Error getting LostName args");
399 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
400 }
401 if (strcmp(new_owner, "") != 0) {
402 // Name is not lost
403 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
404 }
405 if ((index = list_seq_find(items, (int (*)(const void *, const void *))strcmp, name)) != -1) {
406 sway_log(L_INFO, "Status Notifier Item lost %s", name);
407 free(items->items[index]);
408 list_del(items, index);
409 item_unregistered_signal(connection, name);
410
411 return DBUS_HANDLER_RESULT_HANDLED;
412 }
413 if ((index = list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name)) != -1) {
414 sway_log(L_INFO, "Status Notifier Host lost %s", name);
415 free(hosts->items[index]);
416 list_del(hosts, index);
417
418 return DBUS_HANDLER_RESULT_HANDLED;
419 }
420 }
421 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
422}
423
424static const DBusObjectPathVTable vtable = {
425 .message_function = message_handler,
426 .unregister_function = NULL,
427};
428
429int init_sni_watcher() {
430 DBusError error;
431 dbus_error_init(&error);
432 if (!conn) {
433 sway_log(L_ERROR, "Connection is null, cannot initiate StatusNotifierWatcher");
434 return -1;
435 }
436
437 items = create_list();
438 hosts = create_list();
439
440 int status = dbus_bus_request_name(conn, "org.freedesktop.StatusNotifierWatcher",
441 DBUS_NAME_FLAG_REPLACE_EXISTING,
442 &error);
443 if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
444 sway_log(L_DEBUG, "Got watcher name");
445 } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
446 sway_log(L_INFO, "Could not get watcher name, it may start later");
447 }
448 if (dbus_error_is_set(&error)) {
449 sway_log(L_ERROR, "dbus err getting watcher name: %s\n", error.message);
450 return -1;
451 }
452
453 status = dbus_bus_request_name(conn, "org.kde.StatusNotifierWatcher",
454 DBUS_NAME_FLAG_REPLACE_EXISTING,
455 &error);
456 if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
457 sway_log(L_DEBUG, "Got kde watcher name");
458 } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
459 sway_log(L_INFO, "Could not get kde watcher name, it may start later");
460 }
461 if (dbus_error_is_set(&error)) {
462 sway_log(L_ERROR, "dbus err getting kde watcher name: %s\n", error.message);
463 return -1;
464 }
465
466 dbus_connection_try_register_object_path(conn,
467 "/StatusNotifierWatcher",
468 &vtable, NULL, &error);
469 if (dbus_error_is_set(&error)) {
470 sway_log(L_ERROR, "dbus_err: %s\n", error.message);
471 return -1;
472 }
473
474 dbus_bus_add_match(conn,
475 "type='signal',\
476 sender='org.freedesktop.DBus',\
477 interface='org.freedesktop.DBus',\
478 member='NameOwnerChanged'",
479 &error);
480
481 if (dbus_error_is_set(&error)) {
482 sway_log(L_ERROR, "DBus error getting match args: %s", error.message);
483 }
484
485 dbus_connection_add_filter(conn, signal_handler, NULL, NULL);
486 return 0;
487}
diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c
new file mode 100644
index 00000000..9a709fe4
--- /dev/null
+++ b/swaybar/tray/tray.c
@@ -0,0 +1,279 @@
1#define _XOPEN_SOURCE 500
2#include <unistd.h>
3#include <stdlib.h>
4#include <string.h>
5#include <dbus/dbus.h>
6#include "swaybar/bar.h"
7#include "swaybar/tray/tray.h"
8#include "swaybar/tray/dbus.h"
9#include "swaybar/tray/sni.h"
10#include "swaybar/bar.h"
11#include "list.h"
12#include "log.h"
13
14struct tray *tray;
15
16static void register_host(char *name) {
17 DBusMessage *message;
18
19 message = dbus_message_new_method_call(
20 "org.freedesktop.StatusNotifierWatcher",
21 "/StatusNotifierWatcher",
22 "org.freedesktop.StatusNotifierWatcher",
23 "RegisterStatusNotifierHost");
24 if (!message) {
25 sway_log(L_ERROR, "Cannot allocate dbus method call");
26 return;
27 }
28
29 dbus_message_append_args(message,
30 DBUS_TYPE_STRING, &name,
31 DBUS_TYPE_INVALID);
32
33 dbus_connection_send(conn, message, NULL);
34
35 dbus_message_unref(message);
36}
37
38static void get_items_reply(DBusPendingCall *pending, void *_data) {
39 DBusMessage *reply = dbus_pending_call_steal_reply(pending);
40
41 if (!reply) {
42 sway_log(L_ERROR, "Got no items reply from sni watcher");
43 goto bail;
44 }
45
46 int message_type = dbus_message_get_type(reply);
47
48 if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
49 char *msg;
50
51 dbus_message_get_args(reply, NULL,
52 DBUS_TYPE_STRING, &msg,
53 DBUS_TYPE_INVALID);
54
55 sway_log(L_ERROR, "Message is error: %s", msg);
56 goto bail;
57 }
58
59 DBusMessageIter iter;
60 DBusMessageIter variant;
61 DBusMessageIter array;
62
63 dbus_message_iter_init(reply, &iter);
64 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
65 sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
66 goto bail;
67 }
68 dbus_message_iter_recurse(&iter, &variant);
69 if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_ARRAY ||
70 dbus_message_iter_get_element_type(&variant) != DBUS_TYPE_STRING) {
71 sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
72 goto bail;
73 }
74
75 // Clear list
76 list_foreach(tray->items, (void (*)(void *))sni_free);
77 list_free(tray->items);
78 tray->items = create_list();
79
80 // O(n) function, could be faster dynamically reading values
81 int len = dbus_message_iter_get_element_count(&variant);
82
83 dbus_message_iter_recurse(&variant, &array);
84 for (int i = 0; i < len; i++) {
85 const char *name;
86 dbus_message_iter_get_basic(&array, &name);
87
88 struct StatusNotifierItem *item = sni_create(name);
89
90 sway_log(L_DEBUG, "Item registered with host: %s", name);
91 list_add(tray->items, item);
92 dirty = true;
93 }
94
95bail:
96 dbus_message_unref(reply);
97 return;
98}
99static void get_items() {
100 DBusPendingCall *pending;
101 DBusMessage *message = dbus_message_new_method_call(
102 "org.freedesktop.StatusNotifierWatcher",
103 "/StatusNotifierWatcher",
104 "org.freedesktop.DBus.Properties",
105 "Get");
106
107 const char *iface = "org.freedesktop.StatusNotifierWatcher";
108 const char *prop = "RegisteredStatusNotifierItems";
109 dbus_message_append_args(message,
110 DBUS_TYPE_STRING, &iface,
111 DBUS_TYPE_STRING, &prop,
112 DBUS_TYPE_INVALID);
113
114 bool status =
115 dbus_connection_send_with_reply(conn, message, &pending, -1);
116 dbus_message_unref(message);
117
118 if (!(pending || status)) {
119 sway_log(L_ERROR, "Could not get items");
120 return;
121 }
122
123 dbus_pending_call_set_notify(pending, get_items_reply, NULL, NULL);
124}
125
126static DBusHandlerResult signal_handler(DBusConnection *connection,
127 DBusMessage *message, void *_data) {
128 if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
129 "StatusNotifierItemRegistered")) {
130 const char *name;
131 if (!dbus_message_get_args(message, NULL,
132 DBUS_TYPE_STRING, &name,
133 DBUS_TYPE_INVALID)) {
134 sway_log(L_ERROR, "Error getting StatusNotifierItemRegistered args");
135 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
136 }
137
138 if (list_seq_find(tray->items, sni_str_cmp, name) == -1) {
139 struct StatusNotifierItem *item = sni_create(name);
140
141 list_add(tray->items, item);
142 dirty = true;
143 }
144
145 return DBUS_HANDLER_RESULT_HANDLED;
146 } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
147 "StatusNotifierItemUnregistered")) {
148 const char *name;
149 if (!dbus_message_get_args(message, NULL,
150 DBUS_TYPE_STRING, &name,
151 DBUS_TYPE_INVALID)) {
152 sway_log(L_ERROR, "Error getting StatusNotifierItemUnregistered args");
153 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
154 }
155
156 int index;
157 if ((index = list_seq_find(tray->items, sni_str_cmp, name)) != -1) {
158 sni_free(tray->items->items[index]);
159 list_del(tray->items, index);
160 dirty = true;
161 } else {
162 // If it's not in our list, then our list is incorrect.
163 // Fetch all items again
164 sway_log(L_INFO, "Host item list incorrect, refreshing");
165 get_items();
166 }
167
168 return DBUS_HANDLER_RESULT_HANDLED;
169 } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierItem",
170 "NewIcon") || dbus_message_is_signal(message,
171 "org.kde.StatusNotifierItem", "NewIcon")) {
172 const char *name;
173 int index;
174 struct StatusNotifierItem *item;
175
176 name = dbus_message_get_sender(message);
177 if ((index = list_seq_find(tray->items, sni_uniq_cmp, name)) != -1) {
178 item = tray->items->items[index];
179 get_icon(item);
180 }
181
182 return DBUS_HANDLER_RESULT_HANDLED;
183 }
184 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
185}
186
187int init_tray() {
188 tray = (struct tray *)malloc(sizeof(tray));
189
190 tray->items = create_list();
191
192 DBusError error;
193 dbus_error_init(&error);
194 char *name = NULL;
195 if (!conn) {
196 sway_log(L_ERROR, "Connection is null, cannot init SNI host");
197 goto err;
198 }
199 name = calloc(sizeof(char), 256);
200
201 if (!name) {
202 sway_log(L_ERROR, "Cannot allocate name");
203 goto err;
204 }
205
206 pid_t pid = getpid();
207 if (snprintf(name, 256, "org.freedesktop.StatusNotifierHost-%d", pid)
208 >= 256) {
209 sway_log(L_ERROR, "Cannot get host name because string is too short."
210 "This should not happen");
211 goto err;
212 }
213
214 // We want to be the sole owner of this name
215 if (dbus_bus_request_name(conn, name, DBUS_NAME_FLAG_DO_NOT_QUEUE,
216 &error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
217 sway_log(L_ERROR, "Cannot get host name and start the tray");
218 goto err;
219 }
220 if (dbus_error_is_set(&error)) {
221 sway_log(L_ERROR, "Dbus err getting host name: %s\n", error.message);
222 goto err;
223 }
224 sway_log(L_DEBUG, "Got host name");
225
226 register_host(name);
227
228 get_items();
229
230 // Perhaps use addmatch helper functions like wlc does?
231 dbus_bus_add_match(conn,
232 "type='signal',\
233 sender='org.freedesktop.StatusNotifierWatcher',\
234 member='StatusNotifierItemRegistered'",
235 &error);
236 if (dbus_error_is_set(&error)) {
237 sway_log(L_ERROR, "dbus_err: %s", error.message);
238 goto err;
239 }
240 dbus_bus_add_match(conn,
241 "type='signal',\
242 sender='org.freedesktop.StatusNotifierWatcher',\
243 member='StatusNotifierItemUnregistered'",
244 &error);
245 if (dbus_error_is_set(&error)) {
246 sway_log(L_ERROR, "dbus_err: %s", error.message);
247 return -1;
248 }
249
250 // SNI matches
251 dbus_bus_add_match(conn,
252 "type='signal',\
253 interface='org.freedesktop.StatusNotifierItem',\
254 member='NewIcon'",
255 &error);
256 if (dbus_error_is_set(&error)) {
257 sway_log(L_ERROR, "dbus_err %s", error.message);
258 goto err;
259 }
260 dbus_bus_add_match(conn,
261 "type='signal',\
262 interface='org.kde.StatusNotifierItem',\
263 member='NewIcon'",
264 &error);
265 if (dbus_error_is_set(&error)) {
266 sway_log(L_ERROR, "dbus_err %s", error.message);
267 goto err;
268 }
269
270 dbus_connection_add_filter(conn, signal_handler, NULL, NULL);
271
272 free(name);
273 return 0;
274
275err:
276 // TODO better handle errors
277 free(name);
278 return -1;
279}
diff --git a/wayland/cairo.c b/wayland/cairo.c
index ba439d9d..193205b1 100644
--- a/wayland/cairo.c
+++ b/wayland/cairo.c
@@ -8,6 +8,25 @@ void cairo_set_source_u32(cairo_t *cairo, uint32_t color) {
8 (color >> (0*8) & 0xFF) / 255.0); 8 (color >> (0*8) & 0xFF) / 255.0);
9} 9}
10 10
11cairo_surface_t *cairo_image_surface_scale(cairo_surface_t *image, int width, int height) {
12 int image_width = cairo_image_surface_get_width(image);
13 int image_height = cairo_image_surface_get_height(image);
14
15 cairo_surface_t *new =
16 cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
17
18 cairo_t *cairo = cairo_create(new);
19
20 cairo_scale(cairo, (double) width / image_width, (double) height / image_height);
21
22 cairo_set_source_surface(cairo, image, 0, 0);
23 cairo_paint(cairo);
24
25 cairo_destroy(cairo);
26
27 return new;
28}
29
11#ifdef WITH_GDK_PIXBUF 30#ifdef WITH_GDK_PIXBUF
12#include <gdk-pixbuf/gdk-pixbuf.h> 31#include <gdk-pixbuf/gdk-pixbuf.h>
13 32