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

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

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

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

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

See #986 #343
2017-06-07 17:49:16 -07:00

190 lines
4.4 KiB
C

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