aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Drew DeVault <sir@cmpwn.com>2017-06-14 18:53:32 -0400
committerLibravatar GitHub <noreply@github.com>2017-06-14 18:53:32 -0400
commiteb6e38c86d2deb37cc6f378f8644c4a530fd7448 (patch)
treee3c567c4684faf0f9a7f77494f1bf638171d5349
parentMerge pull request #1236 from ykrivopalov/fix_complex_status_command (diff)
parentMerge branch 'master' into tray (diff)
downloadsway-eb6e38c86d2deb37cc6f378f8644c4a530fd7448.tar.gz
sway-eb6e38c86d2deb37cc6f378f8644c4a530fd7448.tar.zst
sway-eb6e38c86d2deb37cc6f378f8644c4a530fd7448.zip
Merge pull request #1234 from 4e554c4c/tray
Implement Tray Icons
-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.h32
-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/container.c17
-rw-r--r--sway/handlers.c2
-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.c84
-rw-r--r--swaybar/config.c12
-rw-r--r--swaybar/event_loop.c143
-rw-r--r--swaybar/ipc.c40
-rw-r--r--swaybar/render.c15
-rw-r--r--swaybar/tray/dbus.c189
-rw-r--r--swaybar/tray/icon.c404
-rw-r--r--swaybar/tray/sni.c471
-rw-r--r--swaybar/tray/sni_watcher.c487
-rw-r--r--swaybar/tray/tray.c393
-rw-r--r--wayland/cairo.c19
37 files changed, 2747 insertions, 61 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..b718e555
--- /dev/null
+++ b/include/swaybar/tray/tray.h
@@ -0,0 +1,32 @@
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 "swaybar/bar.h"
9#include "list.h"
10
11extern struct tray *tray;
12
13struct tray {
14 list_t *items;
15};
16
17/**
18 * Processes a mouse event on the bar
19 */
20void tray_mouse_event(struct output *output, int x, int y,
21 uint32_t button, uint32_t state);
22
23uint32_t tray_render(struct output *output, struct config *config);
24
25void tray_upkeep(struct bar *bar);
26
27/**
28 * Initializes the tray with D-Bus
29 */
30void init_tray();
31
32#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/container.c b/sway/container.c
index 08aa77a8..358ba767 100644
--- a/sway/container.c
+++ b/sway/container.c
@@ -699,12 +699,12 @@ static bool pointer_test(swayc_t *view, void *_origin) {
699 699
700swayc_t *container_under_pointer(void) { 700swayc_t *container_under_pointer(void) {
701 // root.output->workspace 701 // root.output->workspace
702 if (!root_container.focused || !root_container.focused->focused) { 702 if (!root_container.focused) {
703 return NULL; 703 return NULL;
704 } 704 }
705 swayc_t *lookup = root_container.focused->focused; 705 swayc_t *lookup = root_container.focused;
706 // Case of empty workspace 706 // Case of empty workspace
707 if (lookup->children == 0) { 707 if (lookup->children && !lookup->unmanaged) {
708 return NULL; 708 return NULL;
709 } 709 }
710 struct wlc_point origin; 710 struct wlc_point origin;
@@ -712,6 +712,17 @@ swayc_t *container_under_pointer(void) {
712 while (lookup && lookup->type != C_VIEW) { 712 while (lookup && lookup->type != C_VIEW) {
713 int i; 713 int i;
714 int len; 714 int len;
715 for (int _i = 0; lookup->unmanaged && _i < lookup->unmanaged->length; ++_i) {
716 wlc_handle *handle = lookup->unmanaged->items[_i];
717 const struct wlc_geometry *geo = wlc_view_get_geometry(*handle);
718 if (origin.x >= geo->origin.x && origin.y >= geo->origin.y
719 && origin.x < geo->origin.x + (int)geo->size.w
720 && origin.y < geo->origin.y + (int)geo->size.h) {
721 // Hack: we force focus upon unmanaged views here
722 wlc_view_focus(*handle);
723 return NULL;
724 }
725 }
715 // if tabbed/stacked go directly to focused container, otherwise search 726 // if tabbed/stacked go directly to focused container, otherwise search
716 // children 727 // children
717 if (lookup->layout == L_TABBED || lookup->layout == L_STACKED) { 728 if (lookup->layout == L_TABBED || lookup->layout == L_STACKED) {
diff --git a/sway/handlers.c b/sway/handlers.c
index e1b90a07..a912b991 100644
--- a/sway/handlers.c
+++ b/sway/handlers.c
@@ -599,6 +599,8 @@ static void handle_view_geometry_request(wlc_handle handle, const struct wlc_geo
599 view->y = geometry->origin.y; 599 view->y = geometry->origin.y;
600 update_geometry(view); 600 update_geometry(view);
601 } 601 }
602 } else {
603 wlc_view_set_geometry(handle, 0, geometry);
602 } 604 }
603} 605}
604 606
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 5ed0d266..3412ff29 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"
@@ -57,12 +64,15 @@ struct output *new_output(const char *name) {
57 output->window = NULL; 64 output->window = NULL;
58 output->registry = NULL; 65 output->registry = NULL;
59 output->workspaces = create_list(); 66 output->workspaces = create_list();
67#ifdef ENABLE_TRAY
68 output->items = create_list();
69#endif
60 return output; 70 return output;
61} 71}
62 72
63static void mouse_button_notify(struct window *window, int x, int y, 73static void mouse_button_notify(struct window *window, int x, int y,
64 uint32_t button, uint32_t state_w) { 74 uint32_t button, uint32_t state_w) {
65 sway_log(L_DEBUG, "Mouse button %d clicked at %d %d %d\n", button, x, y, state_w); 75 sway_log(L_DEBUG, "Mouse button %d clicked at %d %d %d", button, x, y, state_w);
66 if (!state_w) { 76 if (!state_w) {
67 return; 77 return;
68 } 78 }
@@ -93,6 +103,10 @@ static void mouse_button_notify(struct window *window, int x, int y,
93 break; 103 break;
94 } 104 }
95 } 105 }
106
107#ifdef ENABLE_TRAY
108 tray_mouse_event(clicked_output, x, y, button, state_w);
109#endif
96} 110}
97 111
98static void mouse_scroll_notify(struct window *window, enum scroll_direction direction) { 112static void mouse_scroll_notify(struct window *window, enum scroll_direction direction) {
@@ -137,6 +151,9 @@ void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id) {
137 /* initialize bar with default values */ 151 /* initialize bar with default values */
138 bar_init(bar); 152 bar_init(bar);
139 153
154 /* Initialize event loop lists */
155 init_event_loop();
156
140 /* connect to sway ipc */ 157 /* connect to sway ipc */
141 bar->ipc_socketfd = ipc_open_socket(socket_path); 158 bar->ipc_socketfd = ipc_open_socket(socket_path);
142 bar->ipc_event_socketfd = ipc_open_socket(socket_path); 159 bar->ipc_event_socketfd = ipc_open_socket(socket_path);
@@ -179,23 +196,41 @@ void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id) {
179 } 196 }
180 /* spawn status command */ 197 /* spawn status command */
181 spawn_status_cmd_proc(bar); 198 spawn_status_cmd_proc(bar);
199
200#ifdef ENABLE_TRAY
201 init_tray(bar);
202#endif
182} 203}
183 204
184void bar_run(struct bar *bar) { 205bool dirty = true;
185 int pfds = bar->outputs->length + 2; 206
186 struct pollfd *pfd = malloc(pfds * sizeof(struct pollfd)); 207static void respond_ipc(int fd, short mask, void *_bar) {
187 bool dirty = true; 208 struct bar *bar = (struct bar *)_bar;
209 sway_log(L_DEBUG, "Got IPC event.");
210 dirty = handle_ipc_event(bar);
211}
212
213static void respond_command(int fd, short mask, void *_bar) {
214 struct bar *bar = (struct bar *)_bar;
215 dirty = handle_status_line(bar);
216}
217
218static void respond_output(int fd, short mask, void *_output) {
219 struct output *output = (struct output *)_output;
220 if (wl_display_dispatch(output->registry->display) == -1) {
221 sway_log(L_ERROR, "failed to dispatch wl: %d", errno);
222 }
223}
188 224
189 pfd[0].fd = bar->ipc_event_socketfd; 225void bar_run(struct bar *bar) {
190 pfd[0].events = POLLIN; 226 add_event(bar->ipc_event_socketfd, POLLIN, respond_ipc, bar);
191 pfd[1].fd = bar->status_read_fd; 227 add_event(bar->status_read_fd, POLLIN, respond_command, bar);
192 pfd[1].events = POLLIN;
193 228
194 int i; 229 int i;
195 for (i = 0; i < bar->outputs->length; ++i) { 230 for (i = 0; i < bar->outputs->length; ++i) {
196 struct output *output = bar->outputs->items[i]; 231 struct output *output = bar->outputs->items[i];
197 pfd[i+2].fd = wl_display_get_fd(output->registry->display); 232 add_event(wl_display_get_fd(output->registry->display),
198 pfd[i+2].events = POLLIN; 233 POLLIN, respond_output, output);
199 } 234 }
200 235
201 while (1) { 236 while (1) {
@@ -213,29 +248,10 @@ void bar_run(struct bar *bar) {
213 248
214 dirty = false; 249 dirty = false;
215 250
216 poll(pfd, pfds, -1); 251 event_loop_poll();
217 252#ifdef ENABLE_TRAY
218 if (pfd[0].revents & POLLIN) { 253 dispatch_dbus();
219 sway_log(L_DEBUG, "Got IPC event."); 254#endif
220 dirty = handle_ipc_event(bar);
221 }
222
223 if (bar->config->status_command && pfd[1].revents & POLLIN) {
224 sway_log(L_DEBUG, "Got update from status command.");
225 dirty = handle_status_line(bar);
226 }
227
228 // dispatch wl_display events
229 for (i = 0; i < bar->outputs->length; ++i) {
230 struct output *output = bar->outputs->items[i];
231 if (pfd[i+2].revents & POLLIN) {
232 if (wl_display_dispatch(output->registry->display) == -1) {
233 sway_log(L_ERROR, "failed to dispatch wl: %d", errno);
234 }
235 } else {
236 wl_display_dispatch_pending(output->registry->display);
237 }
238 }
239 } 255 }
240} 256}
241 257
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..6ec47e79 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,12 @@ 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 uint32_t tray_width = tray_render(output, config);
306#else
307 const uint32_t tray_width = window->width * window->scale;
308#endif
309
300 // Command output 310 // Command output
301 if (is_focused) { 311 if (is_focused) {
302 cairo_set_source_u32(cairo, config->colors.focused_statusline); 312 cairo_set_source_u32(cairo, config->colors.focused_statusline);
@@ -309,12 +319,11 @@ void render(struct output *output, struct config *config, struct status_line *li
309 if (line->protocol == TEXT) { 319 if (line->protocol == TEXT) {
310 get_text_size(window->cairo, window->font, &width, &height, 320 get_text_size(window->cairo, window->font, &width, &height,
311 window->scale, config->pango_markup, "%s", line->text_line); 321 window->scale, config->pango_markup, "%s", line->text_line);
312 cairo_move_to(cairo, (window->width * window->scale) 322 cairo_move_to(cairo, tray_width - margin - width, margin);
313 - margin - width, margin);
314 pango_printf(window->cairo, window->font, window->scale, 323 pango_printf(window->cairo, window->font, window->scale,
315 config->pango_markup, "%s", line->text_line); 324 config->pango_markup, "%s", line->text_line);
316 } else if (line->protocol == I3BAR && line->block_line) { 325 } else if (line->protocol == I3BAR && line->block_line) {
317 double pos = (window->width * window->scale) - 0.5; 326 double pos = tray_width - 0.5;
318 bool edge = true; 327 bool edge = true;
319 for (i = line->block_line->length - 1; i >= 0; --i) { 328 for (i = line->block_line->length - 1; i >= 0; --i) {
320 struct status_block *block = line->block_line->items[i]; 329 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..0c46d5c0
--- /dev/null
+++ b/swaybar/tray/sni.c
@@ -0,0 +1,471 @@
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 char *unique_name;
401 if (!dbus_message_get_args(reply, NULL,
402 DBUS_TYPE_STRING, &unique_name,
403 DBUS_TYPE_INVALID)) {
404 sway_log(L_ERROR, "Error parsing method args");
405 } else {
406 if (item->unique_name) {
407 free(item->unique_name);
408 }
409 item->unique_name = strdup(unique_name);
410 }
411
412 dbus_message_unref(reply);
413}
414
415struct StatusNotifierItem *sni_create(const char *name) {
416 struct StatusNotifierItem *item = malloc(sizeof(struct StatusNotifierItem));
417 item->name = strdup(name);
418 item->unique_name = NULL;
419 item->image = NULL;
420 item->dirty = false;
421
422 // If it doesn't use this name then assume that it uses the KDE spec
423 // This is because xembed-sni-proxy uses neither "org.freedesktop" nor
424 // "org.kde" and just gives us the items "unique name"
425 //
426 // We could use this to our advantage and fill out the "unique name"
427 // field with the given name if it is neither freedesktop or kde, but
428 // that's makes us rely on KDE hackyness which is bad practice
429 const char freedesktop_name[] = "org.freedesktop";
430 if (strncmp(name, freedesktop_name, sizeof(freedesktop_name) - 1) != 0) {
431 item->kde_special_snowflake = true;
432 } else {
433 item->kde_special_snowflake = false;
434 }
435
436 get_icon(item);
437
438 get_unique_name(item);
439
440 return item;
441}
442/* Return 0 if `item` has a name of `str` */
443int sni_str_cmp(const void *_item, const void *_str) {
444 const struct StatusNotifierItem *item = _item;
445 const char *str = _str;
446
447 return strcmp(item->name, str);
448}
449/* Returns 0 if `item` has a unique name of `str` */
450int sni_uniq_cmp(const void *_item, const void *_str) {
451 const struct StatusNotifierItem *item = _item;
452 const char *str = _str;
453
454 if (!item->unique_name) {
455 return false;
456 }
457 return strcmp(item->unique_name, str);
458}
459void sni_free(struct StatusNotifierItem *item) {
460 if (!item) {
461 return;
462 }
463 free(item->name);
464 if (item->unique_name) {
465 free(item->unique_name);
466 }
467 if (item->image) {
468 cairo_surface_destroy(item->image);
469 }
470 free(item);
471}
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..00f1a44f
--- /dev/null
+++ b/swaybar/tray/tray.c
@@ -0,0 +1,393 @@
1#define _XOPEN_SOURCE 500
2#include <unistd.h>
3#include <stdlib.h>
4#include <string.h>
5#include <sys/wait.h>
6#include <dbus/dbus.h>
7#include "swaybar/bar.h"
8#include "swaybar/tray/tray.h"
9#include "swaybar/tray/dbus.h"
10#include "swaybar/tray/sni.h"
11#include "swaybar/tray/sni_watcher.h"
12#include "swaybar/bar.h"
13#include "swaybar/config.h"
14#include "list.h"
15#include "log.h"
16
17struct tray *tray;
18
19static void register_host(char *name) {
20 DBusMessage *message;
21
22 message = dbus_message_new_method_call(
23 "org.freedesktop.StatusNotifierWatcher",
24 "/StatusNotifierWatcher",
25 "org.freedesktop.StatusNotifierWatcher",
26 "RegisterStatusNotifierHost");
27 if (!message) {
28 sway_log(L_ERROR, "Cannot allocate dbus method call");
29 return;
30 }
31
32 dbus_message_append_args(message,
33 DBUS_TYPE_STRING, &name,
34 DBUS_TYPE_INVALID);
35
36 dbus_connection_send(conn, message, NULL);
37
38 dbus_message_unref(message);
39}
40
41static void get_items_reply(DBusPendingCall *pending, void *_data) {
42 DBusMessage *reply = dbus_pending_call_steal_reply(pending);
43
44 if (!reply) {
45 sway_log(L_ERROR, "Got no items reply from sni watcher");
46 goto bail;
47 }
48
49 int message_type = dbus_message_get_type(reply);
50
51 if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
52 char *msg;
53
54 dbus_message_get_args(reply, NULL,
55 DBUS_TYPE_STRING, &msg,
56 DBUS_TYPE_INVALID);
57
58 sway_log(L_ERROR, "Message is error: %s", msg);
59 goto bail;
60 }
61
62 DBusMessageIter iter;
63 DBusMessageIter variant;
64 DBusMessageIter array;
65
66 dbus_message_iter_init(reply, &iter);
67 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
68 sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
69 goto bail;
70 }
71 dbus_message_iter_recurse(&iter, &variant);
72 if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_ARRAY ||
73 dbus_message_iter_get_element_type(&variant) != DBUS_TYPE_STRING) {
74 sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
75 goto bail;
76 }
77
78 // Clear list
79 list_foreach(tray->items, (void (*)(void *))sni_free);
80 list_free(tray->items);
81 tray->items = create_list();
82
83 // O(n) function, could be faster dynamically reading values
84 int len = dbus_message_iter_get_element_count(&variant);
85
86 dbus_message_iter_recurse(&variant, &array);
87 for (int i = 0; i < len; i++) {
88 const char *name;
89 dbus_message_iter_get_basic(&array, &name);
90
91 struct StatusNotifierItem *item = sni_create(name);
92
93 sway_log(L_DEBUG, "Item registered with host: %s", name);
94 list_add(tray->items, item);
95 dirty = true;
96 }
97
98bail:
99 dbus_message_unref(reply);
100 return;
101}
102static void get_items() {
103 DBusPendingCall *pending;
104 DBusMessage *message = dbus_message_new_method_call(
105 "org.freedesktop.StatusNotifierWatcher",
106 "/StatusNotifierWatcher",
107 "org.freedesktop.DBus.Properties",
108 "Get");
109
110 const char *iface = "org.freedesktop.StatusNotifierWatcher";
111 const char *prop = "RegisteredStatusNotifierItems";
112 dbus_message_append_args(message,
113 DBUS_TYPE_STRING, &iface,
114 DBUS_TYPE_STRING, &prop,
115 DBUS_TYPE_INVALID);
116
117 bool status =
118 dbus_connection_send_with_reply(conn, message, &pending, -1);
119 dbus_message_unref(message);
120
121 if (!(pending || status)) {
122 sway_log(L_ERROR, "Could not get items");
123 return;
124 }
125
126 dbus_pending_call_set_notify(pending, get_items_reply, NULL, NULL);
127}
128
129static DBusHandlerResult signal_handler(DBusConnection *connection,
130 DBusMessage *message, void *_data) {
131 if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
132 "StatusNotifierItemRegistered")) {
133 const char *name;
134 if (!dbus_message_get_args(message, NULL,
135 DBUS_TYPE_STRING, &name,
136 DBUS_TYPE_INVALID)) {
137 sway_log(L_ERROR, "Error getting StatusNotifierItemRegistered args");
138 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
139 }
140
141 if (list_seq_find(tray->items, sni_str_cmp, name) == -1) {
142 struct StatusNotifierItem *item = sni_create(name);
143
144 list_add(tray->items, item);
145 dirty = true;
146 }
147
148 return DBUS_HANDLER_RESULT_HANDLED;
149 } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
150 "StatusNotifierItemUnregistered")) {
151 const char *name;
152 if (!dbus_message_get_args(message, NULL,
153 DBUS_TYPE_STRING, &name,
154 DBUS_TYPE_INVALID)) {
155 sway_log(L_ERROR, "Error getting StatusNotifierItemUnregistered args");
156 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
157 }
158
159 int index;
160 if ((index = list_seq_find(tray->items, sni_str_cmp, name)) != -1) {
161 sni_free(tray->items->items[index]);
162 list_del(tray->items, index);
163 dirty = true;
164 } else {
165 // If it's not in our list, then our list is incorrect.
166 // Fetch all items again
167 sway_log(L_INFO, "Host item list incorrect, refreshing");
168 get_items();
169 }
170
171 return DBUS_HANDLER_RESULT_HANDLED;
172 } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierItem",
173 "NewIcon") || dbus_message_is_signal(message,
174 "org.kde.StatusNotifierItem", "NewIcon")) {
175 const char *name;
176 int index;
177 struct StatusNotifierItem *item;
178
179 name = dbus_message_get_sender(message);
180 if ((index = list_seq_find(tray->items, sni_uniq_cmp, name)) != -1) {
181 item = tray->items->items[index];
182 sway_log(L_INFO, "NewIcon signal from item %s", item->name);
183 get_icon(item);
184 }
185
186 return DBUS_HANDLER_RESULT_HANDLED;
187 }
188 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
189}
190
191static int init_host() {
192 tray = (struct tray *)malloc(sizeof(tray));
193
194 tray->items = create_list();
195
196 DBusError error;
197 dbus_error_init(&error);
198 char *name = NULL;
199 if (!conn) {
200 sway_log(L_ERROR, "Connection is null, cannot init SNI host");
201 goto err;
202 }
203 name = calloc(sizeof(char), 256);
204
205 if (!name) {
206 sway_log(L_ERROR, "Cannot allocate name");
207 goto err;
208 }
209
210 pid_t pid = getpid();
211 if (snprintf(name, 256, "org.freedesktop.StatusNotifierHost-%d", pid)
212 >= 256) {
213 sway_log(L_ERROR, "Cannot get host name because string is too short."
214 "This should not happen");
215 goto err;
216 }
217
218 // We want to be the sole owner of this name
219 if (dbus_bus_request_name(conn, name, DBUS_NAME_FLAG_DO_NOT_QUEUE,
220 &error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
221 sway_log(L_ERROR, "Cannot get host name and start the tray");
222 goto err;
223 }
224 if (dbus_error_is_set(&error)) {
225 sway_log(L_ERROR, "Dbus err getting host name: %s\n", error.message);
226 goto err;
227 }
228 sway_log(L_DEBUG, "Got host name");
229
230 register_host(name);
231
232 get_items();
233
234 // Perhaps use addmatch helper functions like wlc does?
235 dbus_bus_add_match(conn,
236 "type='signal',\
237 sender='org.freedesktop.StatusNotifierWatcher',\
238 member='StatusNotifierItemRegistered'",
239 &error);
240 if (dbus_error_is_set(&error)) {
241 sway_log(L_ERROR, "dbus_err: %s", error.message);
242 goto err;
243 }
244 dbus_bus_add_match(conn,
245 "type='signal',\
246 sender='org.freedesktop.StatusNotifierWatcher',\
247 member='StatusNotifierItemUnregistered'",
248 &error);
249 if (dbus_error_is_set(&error)) {
250 sway_log(L_ERROR, "dbus_err: %s", error.message);
251 return -1;
252 }
253
254 // SNI matches
255 dbus_bus_add_match(conn,
256 "type='signal',\
257 interface='org.freedesktop.StatusNotifierItem',\
258 member='NewIcon'",
259 &error);
260 if (dbus_error_is_set(&error)) {
261 sway_log(L_ERROR, "dbus_err %s", error.message);
262 goto err;
263 }
264 dbus_bus_add_match(conn,
265 "type='signal',\
266 interface='org.kde.StatusNotifierItem',\
267 member='NewIcon'",
268 &error);
269 if (dbus_error_is_set(&error)) {
270 sway_log(L_ERROR, "dbus_err %s", error.message);
271 goto err;
272 }
273
274 dbus_connection_add_filter(conn, signal_handler, NULL, NULL);
275
276 free(name);
277 return 0;
278
279err:
280 // TODO better handle errors
281 free(name);
282 return -1;
283}
284
285void tray_mouse_event(struct output *output, int x, int y,
286 uint32_t button, uint32_t state) {
287
288 struct window *window = output->window;
289 uint32_t tray_padding = swaybar.config->tray_padding;
290 int tray_width = window->width * window->scale;
291
292 for (int i = 0; i < output->items->length; ++i) {
293 struct sni_icon_ref *item =
294 output->items->items[i];
295 int icon_width = cairo_image_surface_get_width(item->icon);
296
297 tray_width -= tray_padding;
298 if (x <= tray_width && x >= tray_width - icon_width) {
299 if (button == swaybar.config->activate_button) {
300 sni_activate(item->ref, x, y);
301 } else if (button == swaybar.config->context_button) {
302 sni_context_menu(item->ref, x, y);
303 } else if (button == swaybar.config->secondary_button) {
304 sni_secondary(item->ref, x, y);
305 }
306 break;
307 }
308 tray_width -= icon_width;
309 }
310}
311
312uint32_t tray_render(struct output *output, struct config *config) {
313 struct window *window = output->window;
314 cairo_t *cairo = window->cairo;
315
316 // Tray icons
317 uint32_t tray_padding = config->tray_padding;
318 uint32_t tray_width = window->width * window->scale;
319 const int item_size = (window->height * window->scale) - (2 * tray_padding);
320
321 if (item_size < 0) {
322 // Can't render items if the padding is too large
323 return tray_width;
324 }
325
326 if (config->tray_output && strcmp(config->tray_output, output->name) != 0) {
327 return tray_width;
328 }
329
330 for (int i = 0; i < tray->items->length; ++i) {
331 struct StatusNotifierItem *item =
332 tray->items->items[i];
333 if (!item->image) {
334 continue;
335 }
336
337 struct sni_icon_ref *render_item = NULL;
338 int j;
339 for (j = i; j < output->items->length; ++j) {
340 struct sni_icon_ref *ref =
341 output->items->items[j];
342 if (ref->ref == item) {
343 render_item = ref;
344 break;
345 } else {
346 sni_icon_ref_free(ref);
347 list_del(output->items, j);
348 }
349 }
350
351 if (!render_item) {
352 render_item = sni_icon_ref_create(item, item_size);
353 list_add(output->items, render_item);
354 } else if (item->dirty) {
355 // item needs re-render
356 sni_icon_ref_free(render_item);
357 output->items->items[j] = render_item =
358 sni_icon_ref_create(item, item_size);
359 }
360
361 tray_width -= tray_padding;
362 tray_width -= item_size;
363
364 cairo_operator_t op = cairo_get_operator(cairo);
365 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
366 cairo_set_source_surface(cairo, render_item->icon, tray_width, tray_padding);
367 cairo_rectangle(cairo, tray_width, tray_padding, item_size, item_size);
368 cairo_fill(cairo);
369 cairo_set_operator(cairo, op);
370
371 item->dirty = false;
372 }
373
374
375 if (tray_width != window->width * window->scale) {
376 tray_width -= tray_padding;
377 }
378
379 return tray_width;
380}
381
382void init_tray(struct bar *bar) {
383 if (!bar->config->tray_output || strcmp(bar->config->tray_output, "none") != 0) {
384 /* Connect to the D-Bus */
385 dbus_init();
386
387 /* Start the SNI watcher */
388 init_sni_watcher();
389
390 /* Start the SNI host */
391 init_host();
392 }
393}
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