diff --git a/include/swaybar/tray/dbus.h b/include/swaybar/tray/dbus.h index eb9cfea70..93c42cb68 100644 --- a/include/swaybar/tray/dbus.h +++ b/include/swaybar/tray/dbus.h @@ -2,17 +2,14 @@ #define _SWAYBAR_DBUS_H #include -#include -extern DBusConnection *conn; +#include -/** - * Should be called in main loop to dispatch events - */ -void dispatch_dbus(); +void process_request(int fd, short mask, void *data); -/** - * Initializes async dbus communication - */ -int dbus_init(); +bool dbus_init(); + +void finish_dbus(sd_bus_slot *slot, sd_bus *bus); + +int dbus_name_has_owner(sd_bus *bus, const char *name, sd_bus_error *error); #endif /* _SWAYBAR_DBUS_H */ diff --git a/include/swaybar/tray/sni.h b/include/swaybar/tray/sni.h index c2544e2a9..05a2ffcb4 100644 --- a/include/swaybar/tray/sni.h +++ b/include/swaybar/tray/sni.h @@ -2,7 +2,7 @@ #define _SWAYBAR_SNI_H #include -#include +#include struct StatusNotifierItem { /* Name registered to sni watcher */ diff --git a/include/swaybar/tray/sni_host.h b/include/swaybar/tray/sni_host.h new file mode 100644 index 000000000..80497c2f5 --- /dev/null +++ b/include/swaybar/tray/sni_host.h @@ -0,0 +1,3 @@ + +char *init_dbus_sni_host(const char *prefix); + diff --git a/include/swaybar/tray/sni_watcher.h b/include/swaybar/tray/sni_watcher.h index 25ddfcd29..e354c038c 100644 --- a/include/swaybar/tray/sni_watcher.h +++ b/include/swaybar/tray/sni_watcher.h @@ -1,10 +1,41 @@ #ifndef _SWAYBAR_SNI_WATCHER_H #define _SWAYBAR_SNI_WATCHER_H +#include +#include + +#include "list.h" + +struct sni_watcher { + sd_bus *bus; + const char *iface; + const char *path; + list_t *hosts; + list_t *items; + int version; +}; + /** * Starts the sni_watcher, the watcher is practically a black box and should * only be accessed though functions described in its spec */ -int init_sni_watcher(); + +static int method_register_sni(sd_bus_message *msg, void *userdata, + sd_bus_error *ret_error); + +static int property_registered_sni(sd_bus *bus, const char *path, + const char *interface, const char *property, sd_bus_message *reply, + void *userdata, sd_bus_error *error); + +bool sni_watcher_vtable_init(struct sni_watcher *watcher, sd_bus_slot *slot); + +bool sni_watcher_init(struct sni_watcher *watcher); + +static int handle_new_icon(sd_bus_message *msg, void *userdata, + sd_bus_error *ret_error); + +int add_sni_signal_matches(struct sni_watcher *watcher); + +int sni_item_cmp(const void *item, const void *data); #endif /* _SWAYBAR_SNI_WATCHER_H */ diff --git a/include/swaybar/tray/tray.h b/include/swaybar/tray/tray.h index 2d0662bed..b90b8be28 100644 --- a/include/swaybar/tray/tray.h +++ b/include/swaybar/tray/tray.h @@ -17,16 +17,16 @@ struct tray { /** * Processes a mouse event on the bar */ -void tray_mouse_event(struct output *output, int x, int y, - uint32_t button, uint32_t state); +//void tray_mouse_event(struct output *output, int x, int y, +// uint32_t button, uint32_t state); -uint32_t tray_render(struct output *output, struct config *config); +//uint32_t tray_render(struct output *output, struct config *config); -void tray_upkeep(struct bar *bar); +//void tray_upkeep(struct swaybar *bar); /** * Initializes the tray with D-Bus */ -void init_tray(struct bar *bar); +void tray_init(struct swaybar *bar); #endif /* _SWAYBAR_TRAY_H */ diff --git a/meson.build b/meson.build index 253a4e96e..f47736fd4 100644 --- a/meson.build +++ b/meson.build @@ -70,6 +70,10 @@ if elogind.found() swayidle_deps += elogind endif +if get_option('enable-tray') + conf_data.set('ENABLE_TRAY', true) +endif + scdoc = find_program('scdoc', required: false) if scdoc.found() diff --git a/meson_options.txt b/meson_options.txt index 50d646fd3..6cdb74941 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -5,3 +5,4 @@ option('zsh-completions', type: 'boolean', value: true, description: 'Install zs option('bash-completions', type: 'boolean', value: true, description: 'Install bash shell completions.') option('fish-completions', type: 'boolean', value: true, description: 'Install fish shell completions.') option('enable-xwayland', type: 'boolean', value: true, description: 'Enable support for X11 applications') +option('enable-tray', type: 'boolean', value: false, description: 'Enable support for the swaybar tray') diff --git a/swaybar/bar.c b/swaybar/bar.c index 62a7727e0..512e6bc39 100644 --- a/swaybar/bar.c +++ b/swaybar/bar.c @@ -22,6 +22,9 @@ #include "swaybar/status_line.h" #include "swaybar/bar.h" #include "swaybar/ipc.h" +#ifdef ENABLE_TRAY +#include "swaybar/tray/tray.h" +#endif #include "ipc-client.h" #include "list.h" #include "log.h" @@ -448,6 +451,9 @@ void bar_setup(struct swaybar *bar, } ipc_get_workspaces(bar); render_all_frames(bar); +#ifdef ENABLE_TRAY + tray_init(bar); +#endif } static void display_in(int fd, short mask, void *data) { diff --git a/swaybar/meson.build b/swaybar/meson.build index d65edb11e..c95cd3a5b 100644 --- a/swaybar/meson.build +++ b/swaybar/meson.build @@ -8,6 +8,10 @@ executable( 'main.c', 'render.c', 'status_line.c', + 'tray/dbus.c', + 'tray/sni_host.c', + 'tray/sni_watcher.c', + 'tray/tray.c', ], include_directories: [sway_inc], dependencies: [ @@ -19,6 +23,7 @@ executable( pango, pangocairo, rt, + systemd, wayland_client, wayland_cursor, wlroots, diff --git a/swaybar/tray/dbus.c b/swaybar/tray/dbus.c new file mode 100644 index 000000000..fbdbcce1c --- /dev/null +++ b/swaybar/tray/dbus.c @@ -0,0 +1,131 @@ +#define _XOPEN_SOURCE 500 + +#ifdef __FreeBSD__ +#include +#else +#include +#endif +#include +#include +#include +#include "swaybar/event_loop.h" +#include "swaybar/tray/dbus.h" +#include "swaybar/tray/sni_host.h" +#include "swaybar/tray/sni_watcher.h" + +static const char *fd_iface = "org.freedesktop.StatusNotifierWatcher"; +static const char *fd_notifier_host = "org.freedesktop.StatusNotifierHost"; + +bool dbus_init() { + int ret = 0; + sd_bus *bus = NULL; + /* BUG: *slot should contained in sni_watcher struct to facilitate multiple + * watchers but when done in this way the sd-bus handler callbacks + * (handle_new_icon) were getting bad userdata + */ + + sd_bus_slot *slot = NULL; + // TODO: Add other watchers for KDE, etc. + struct sni_watcher fd_sni_watcher = {0}; + + ret = sd_bus_open_user(&bus); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to connect to user bus: %s", + strerror(-ret)); + goto error; + } + + wlr_log(WLR_DEBUG, "Connected to user bus"); + + fd_sni_watcher.bus = bus; + fd_sni_watcher.iface = fd_iface; + sni_watcher_init(&fd_sni_watcher); + + ret = sni_watcher_vtable_init(&fd_sni_watcher, slot); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to init freedesktop watcher vtable: %s", + strerror(-ret)); + goto error; + } + + ret = sd_bus_request_name(bus, fd_iface, 0); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get freedesktop SNI watcher name: %s", + strerror(-ret)); + goto error; + } + + char *fd_name = init_dbus_sni_host(fd_notifier_host); + if (fd_name == NULL) { + wlr_log(WLR_ERROR, "Failed to init freedesktop Notifier host"); + goto error; + } + + ret = sd_bus_request_name(bus, fd_name, 0); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get freedesktop notifier host name: %s", + strerror(-ret)); + goto error; + } + + add_event(sd_bus_get_fd(bus), POLLIN, process_request, bus); + + return true; + +error: + finish_dbus(slot, bus); + return false; +} + +void finish_dbus(sd_bus_slot *slot, sd_bus *bus) { + sd_bus_slot_unref(slot); + sd_bus_unref(bus); +} + +void process_request(int fd, short mask, void *data) { + int ret = 0; + sd_bus *bus = data; + wlr_log(WLR_DEBUG, "Processing a bus request"); + while(1) { + ret = sd_bus_process(bus, NULL); + + if (ret == 0) { + break; + } + } + + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to process bus: %s", strerror(-ret)); + return; + } + +} + +int dbus_name_has_owner(sd_bus *bus, const char *name, sd_bus_error *error) { + sd_bus_message *reply = NULL; + int ret = 0; + int has_owner = 0; + + ret = sd_bus_call_method(bus, + "org.freedesktop.DBus", + "/org/freedesktop/dbus", + "org.freedesktop.DBus", + "NameHasOwner", + error, + &reply, + "s", + name); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to call NameHasOwner on: %s", name); + return ret; + } + + ret = sd_bus_message_read_basic(reply, 'b', &has_owner); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to read NameHasOwner message on: %s", name); + return sd_bus_error_set_errno(error, ret); + } + + return has_owner; +} + diff --git a/swaybar/tray/sni_host.c b/swaybar/tray/sni_host.c new file mode 100644 index 000000000..081f5fe95 --- /dev/null +++ b/swaybar/tray/sni_host.c @@ -0,0 +1,20 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include + +char *init_dbus_sni_host(const char *prefix) { + char *name = NULL; + + name = calloc(sizeof(char), 256); + if (name == NULL) { + fprintf(stderr, "Could not allocate SNI host name\n"); + return NULL; + } + + pid_t pid = getpid(); + snprintf(name, sizeof(char) * 256, "%s-%d", prefix, pid); + return name; +} + diff --git a/swaybar/tray/sni_watcher.c b/swaybar/tray/sni_watcher.c new file mode 100644 index 000000000..4d1711f3a --- /dev/null +++ b/swaybar/tray/sni_watcher.c @@ -0,0 +1,169 @@ +#include +#include + +#include + +#include "swaybar/tray/dbus.h" +#include "swaybar/tray/sni_watcher.h" + +static const char *sni_watcher_path = "/StatusNotifierWatcher"; + +static int method_register_sni(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { + struct sni_watcher *watcher = userdata; + int ret = 0; + char *name; + + wlr_log(WLR_INFO, "Interface: %s", watcher->iface); + ret = sd_bus_message_read(msg, "s", &name); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse register SNI item message: %s", + strerror(-ret)); + return ret; + } + + wlr_log(WLR_DEBUG, "Incoming SNI registration: %s", name); + + if (!dbus_name_has_owner(sd_bus_message_get_bus(msg), name, NULL)) { + wlr_log(WLR_DEBUG, "DBus name does not have owner"); + return ret; + } + + wlr_log(WLR_INFO, "RegisterStatusNotifierItem called with: %s", name); + if (list_seq_find(watcher->items, sni_item_cmp, name) != -1) { + wlr_log(WLR_DEBUG, "Watcher already has name: %s", name); + } else { + list_add(watcher->items, name); + sd_bus_emit_signal(watcher->bus, watcher->path, watcher->iface, + "StatusNotifierItemRegistered", "b", "1"); + } + + return sd_bus_reply_method_return(msg, ""); +} + +static int property_registered_sni(sd_bus *bus, const char *path, const char + *interface, const char *property, sd_bus_message *reply, + void *userdata, sd_bus_error *error) { + struct sni_watcher *watcher = userdata; + int ret = 0; + + ret = sd_bus_message_open_container(reply, 'a', "s"); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to open reply container: %s", + strerror(-ret)); + return ret; + } + for (int i = 0;i < watcher->items->length; i++) { + ret = sd_bus_message_append(reply, "s", watcher->items->items[i]); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to append to reply container: %s", + strerror(-ret)); + return ret; + } + } + + return sd_bus_message_close_container(reply); +} + +static const sd_bus_vtable sni_watcher_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("RegisterStatusNotifierItem", "s", "", method_register_sni, + SD_BUS_VTABLE_UNPRIVILEGED), +// SD_BUS_METHOD("RegisterStatusNotifierHost", "s", "", method_register_snh, +// SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_PROPERTY("RegisteredStatusNotifierItems", "as", property_registered_sni, + 0, SD_BUS_VTABLE_PROPERTY_CONST), +// SD_BUS_PROPERTY("IsStatusNotifierHostRegistered", "b", property_is_snh_registered, +// 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ProtocolVersion", "i", NULL, offsetof(struct sni_watcher, version), + SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_SIGNAL("StatusNotifierItemRegistered", "s", 0), + SD_BUS_SIGNAL("StatusNotifierItemUnregistered", "s", 0), + SD_BUS_SIGNAL("StatusNotifierHostRegistered", NULL, 0), + SD_BUS_VTABLE_END +}; + +bool sni_watcher_vtable_init(struct sni_watcher *watcher, sd_bus_slot *slot) { + int ret = 0; + + ret = sd_bus_add_object_vtable(watcher->bus, &slot, watcher->path, watcher->iface, + sni_watcher_vtable, watcher); + if (ret < 0) { + wlr_log(WLR_ERROR, "Could not init SNI watcher vtable: %s", + strerror(-1)); + return false; + } + + return true; +} + +bool sni_watcher_init(struct sni_watcher *watcher) { + int ret = 0; + + watcher->path = sni_watcher_path; + watcher->items = create_list(); + watcher->hosts = create_list(); + watcher->version = 0; + ret = add_sni_signal_matches(watcher); + if (ret < 0) { + wlr_log(WLR_ERROR, "Could not init SNI watcher matches: %s", + strerror(-1)); + return false; + } + return true; +} + +static int handle_new_icon(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { + struct sni_watcher *watcher = userdata; + int ret = 0; + sd_bus_error err = SD_BUS_ERROR_NULL; + char *name; + + ret = sd_bus_get_property_string(watcher->bus, sd_bus_message_get_sender(msg), + "/StatusNotifierItem", sd_bus_message_get_interface(msg), "IconName", + &err, + &name); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get IconName property: %s", + err.message); + goto finish; + } + + wlr_log(WLR_DEBUG, "Got IconName property: %s", name); + // TODO: call code to find the icon path, display the icon, etc. + +finish: + sd_bus_error_free(&err); + return ret; +} + +int add_sni_signal_matches(struct sni_watcher *watcher) { + int ret = 0; + char *iface = NULL; + + if (strcmp(watcher->iface, "org.freedesktop.StatusNotifierWatcher") == 0) { + iface = "org.freedesktop.StatusNotifierItem"; + } + + if (iface == NULL) { + wlr_log(WLR_ERROR, "Unsupported interface: %s", watcher->iface); + return -1; + } + + ret = sd_bus_match_signal(watcher->bus, NULL, NULL, NULL, iface, "NewIcon", + handle_new_icon, watcher); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to add match for NewIcon signal: %s", + strerror(-ret)); + return ret; + } + wlr_log(WLR_DEBUG, "Added NewIcon signal match for: %s", iface); + + return ret; +} + +int sni_item_cmp(const void *item, const void *data) { + const char *sni_item = item; + const char *name = data; + return strcmp(sni_item, name); +} + diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c new file mode 100644 index 000000000..2e3a056db --- /dev/null +++ b/swaybar/tray/tray.c @@ -0,0 +1,8 @@ +#include "swaybar/bar.h" +#include "swaybar/tray/dbus.h" + +void tray_init(struct swaybar *bar) { + dbus_init(); + +} +