diff options
Diffstat (limited to 'swaybar/tray')
| -rw-r--r-- | swaybar/tray/dbus.c | 189 | ||||
| -rw-r--r-- | swaybar/tray/icon.c | 404 | ||||
| -rw-r--r-- | swaybar/tray/sni.c | 471 | ||||
| -rw-r--r-- | swaybar/tray/sni_watcher.c | 487 | ||||
| -rw-r--r-- | swaybar/tray/tray.c | 393 | 
5 files changed, 1944 insertions, 0 deletions
| 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 @@ +#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; +} 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 @@ +#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <dirent.h> +#include <sys/stat.h> +#include <stdbool.h> +#include <stdint.h> +#include <limits.h> +#include "swaybar/tray/icon.h" +#include "swaybar/bar.h" +#include "swaybar/config.h" +#include "stringop.h" +#include "log.h" + +/** + * REVIEW: + * This file repeats lots of "costly" operations that are the same for every + * icon. It's possible to create a dictionary or some other structure to cache + * these, though it may complicate things somewhat. + * + * Also parsing (index.theme) is currently pretty messy, so that could be made + * much better as well. Over all, things work, but are not optimal. + */ + +/* Finds all themes that the given theme inherits */ +static list_t *find_inherits(const char *theme_dir) { +	const char inherits[] = "Inherits"; +	const char index_name[] = "index.theme"; +	list_t *themes = create_list(); +	FILE *index = NULL; +	char *path = malloc(strlen(theme_dir) + sizeof(index_name)); +	if (!path) { +		goto fail; +	} +	if (!themes) { +		goto fail; +	} + +	strcpy(path, theme_dir); +	strcat(path, index_name); + +	index = fopen(path, "r"); +	if (!index) { +		goto fail; +	} + +	char *buf = NULL; +	size_t n = 0; +	while (!feof(index)) { +		getline(&buf, &n, index); +		if (n <= sizeof(inherits) + 1) { +			continue; +		} +		if (strncmp(inherits, buf, sizeof(inherits) - 1) == 0) { +			char *themestr = buf + sizeof(inherits); +			themes = split_string(themestr, ","); +			break; +		} +	} +	free(buf); + +fail: +	free(path); +	if (index) { +		fclose(index); +	} +	return themes; +} + +static bool isdir(const char *path) { +	struct stat statbuf; +	if (stat(path, &statbuf) != -1) { +		if (S_ISDIR(statbuf.st_mode)) { +			return true; +		} +	} +	return false; + +} + +/** + * Returns the directory of a given theme if it exists. + * The returned pointer must be freed. + */ +static char *find_theme_dir(const char *theme) { +	char *basedir; +	char *icon_dir; + +	if (!theme) { +		return NULL; +	} + +	if (!(icon_dir = malloc(1024))) { +		sway_log(L_ERROR, "Out of memory!"); +		goto fail; +	} + +	if ((basedir = getenv("HOME"))) { +		if (snprintf(icon_dir, 1024, "%s/.icons/%s", basedir, theme) >= 1024) { +			sway_log(L_ERROR, "Path too long to render"); +			// XXX perhaps just goto trying in /usr/share? This +			// shouldn't happen anyway, but might with a long global +			goto fail; +		} + +		if (isdir(icon_dir)) { +			return icon_dir; +		} +	} + +	if ((basedir = getenv("XDG_DATA_DIRS"))) { +		if (snprintf(icon_dir, 1024, "%s/icons/%s", basedir, theme) >= 1024) { +			sway_log(L_ERROR, "Path too long to render"); +			// ditto +			goto fail; +		} + +		if (isdir(icon_dir)) { +			return icon_dir; +		} +	} + +	// Spec says use "/usr/share/pixmaps/", but I see everything in +	// "/usr/share/icons/" look it both, I suppose. +	if (snprintf(icon_dir, 1024, "/usr/share/pixmaps/%s", theme) >= 1024) { +		sway_log(L_ERROR, "Path too long to render"); +		goto fail; +	} +	if (isdir(icon_dir)) { +		return icon_dir; +	} + +	if (snprintf(icon_dir, 1024, "/usr/share/icons/%s", theme) >= 1024) { +		sway_log(L_ERROR, "Path too long to render"); +		goto fail; +	} +	if (isdir(icon_dir)) { +		return icon_dir; +	} + +fail: +	free(icon_dir); +	sway_log(L_ERROR, "Could not find dir for theme: %s", theme); +	return NULL; +} + +/** + * Returns all theme dirs needed to be looked in for an icon. + * Does not check for duplicates + */ +static list_t *find_all_theme_dirs(const char *theme) { +	list_t *dirs = create_list(); +	if (!dirs) { +		return NULL; +	} +	char *dir = find_theme_dir(theme); +	if (dir) { +		list_add(dirs, dir); +		list_t *inherits = find_inherits(dir); +		list_cat(dirs, inherits); +		list_free(inherits); +	} +	dir = find_theme_dir("hicolor"); +	if (dir) { +		list_add(dirs, dir); +	} + +	return dirs; +} + +struct subdir { +	int size; +	char name[]; +}; + +static int subdir_str_cmp(const void *_subdir, const void *_str) { +	const struct subdir *subdir = _subdir; +	const char *str = _str; +	return strcmp(subdir->name, str); +} +/** + * Helper to find_subdirs. Acts similar to `split_string(subdirs, ",")` but + * generates a list of struct subdirs + */ +static list_t *split_subdirs(char *subdir_str) { +	list_t *subdir_list = create_list(); +	char *copy = strdup(subdir_str); +	if (!subdir_list || !copy) { +		list_free(subdir_list); +		free(copy); +		return NULL; +	} + +	char *token; +	token = strtok(copy, ","); +	while(token) { +		int len = strlen(token) + 1; +		struct subdir *subdir = +			malloc(sizeof(struct subdir) + sizeof(char [len])); +		if (!subdir) { +			// Return what we have +			return subdir_list; +		} +		subdir->size = 0; +		strcpy(subdir->name, token); + +		list_add(subdir_list, subdir); + +		token = strtok(NULL, ","); +	} +	free(copy); + +	return subdir_list; +} +/** + * Returns a list of all subdirectories of a theme. + * Take note: the subdir names are all relative to `theme_dir` and must be + * combined with it to form a valid directory. + * + * Each member of the list is of type (struct subdir *) this struct contains + * the name of the subdir, along with size information. These must be freed + * bye the caller. + * + * This currently ignores min and max sizes of icons. + */ +static list_t* find_theme_subdirs(const char *theme_dir) { +	const char index_name[] = "/index.theme"; +	list_t *dirs = NULL; +	char *path = malloc(strlen(theme_dir) + sizeof(index_name)); +	FILE *index = NULL; +	if (!path) { +		sway_log(L_ERROR, "Failed to allocate memory"); +		goto fail; +	} + +	strcpy(path, theme_dir); +	strcat(path, index_name); + +	index = fopen(path, "r"); +	if (!index) { +		sway_log(L_ERROR, "Could not open file: %s", path); +		goto fail; +	} + +	char *buf = NULL; +	size_t n = 0; +	while (!feof(index)) { +		const char directories[] = "Directories"; +		getline(&buf, &n, index); +		if (n <= sizeof(directories) + 1) { +			continue; +		} +		if (strncmp(directories, buf, sizeof(directories) - 1) == 0) { +			char *dirstr = buf + sizeof(directories); +			dirs = split_subdirs(dirstr); +			break; +		} +	} +	// Now, find the size of each dir +	struct subdir *current_subdir = NULL; +	while (!feof(index)) { +		const char size[] = "Size"; +		getline(&buf, &n, index); + +		if (buf[0] == '[') { +			int len = strlen(buf); +			if (buf[len-1] == '\n') { +				len--; +			} +			// replace ']' +			buf[len-1] = '\0'; + +			int index; +			if ((index = list_seq_find(dirs, subdir_str_cmp, buf+1)) != -1) { +				current_subdir = (dirs->items[index]); +			} +		} + +		if (strncmp(size, buf, sizeof(size) - 1) == 0) { +			if (current_subdir) { +				current_subdir->size = atoi(buf + sizeof(size)); +			} +		} +	} +	free(buf); +fail: +	free(path); +	if (index) { +		fclose(index); +	} +	return dirs; +} + +/* Returns the file of an icon given its name and size */ +static char *find_icon_file(const char *name, int size) { +	int namelen = strlen(name); +	list_t *dirs = find_all_theme_dirs(swaybar.config->icon_theme); +	if (!dirs) { +		return NULL; +	} +	int min_size_diff = INT_MAX; +	char *current_file = NULL; + +	for (int i = 0; i < dirs->length; ++i) { +		char *dir = dirs->items[i]; +		list_t *subdirs = find_theme_subdirs(dir); + +		if (!subdirs) { +			continue; +		} + +		for (int i = 0; i < subdirs->length; ++i) { +			struct subdir *subdir = subdirs->items[i]; + +			// Only use an unsized if we don't already have a +			// canidate this should probably change to allow svgs +			if (!subdir->size && current_file) { +				continue; +			} + +			int size_diff = abs(size - subdir->size); + +			if (size_diff >= min_size_diff) { +				continue; +			} + +			char *path = malloc(strlen(subdir->name) + strlen(dir) + 2); + +			strcpy(path, dir); +			path[strlen(dir)] = '/'; +			strcpy(path + strlen(dir) + 1, subdir->name); + +			DIR *icons = opendir(path); +			if (!icons) { +				free(path); +				continue; +			} + +			struct dirent *direntry; +			while ((direntry = readdir(icons)) != NULL) { +				int len = strlen(direntry->d_name); +				if (len <= namelen + 2) { //must have some ext +					continue; +				} +				if (strncmp(direntry->d_name, name, namelen) == 0) { +					char *ext = direntry->d_name + namelen + 1; +#ifdef WITH_GDK_PIXBUF +					if (strcmp(ext, "png") == 0 || +							strcmp(ext, "xpm") == 0 || +							strcmp(ext, "svg") == 0) { +#else +					if (strcmp(ext, "png") == 0) { +#endif +						free(current_file); +						char *icon_path = malloc(strlen(path) + len + 2); + +						strcpy(icon_path, path); +						icon_path[strlen(path)] = '/'; +						strcpy(icon_path + strlen(path) + 1, direntry->d_name); +						current_file = icon_path; +						min_size_diff = size_diff; +					} +				} +			} +			free(path); +			closedir(icons); +		} +		free_flat_list(subdirs); +	} +	free_flat_list(dirs); + +	return current_file; +} + +cairo_surface_t *find_icon(const char *name, int size) { +	char *image_path = find_icon_file(name, size); +	if (image_path == NULL) { +		return NULL; +	} + +	cairo_surface_t *image = NULL; +#ifdef WITH_GDK_PIXBUF +	GError *err = NULL; +	GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(image_path, &err); +	if (!pixbuf) { +		sway_log(L_ERROR, "Failed to load icon image: %s", err->message); +	} +	image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf); +	g_object_unref(pixbuf); +#else +	// TODO make svg work? cairo supports it. maybe remove gdk alltogether +	image = cairo_image_surface_create_from_png(image_path); +#endif //WITH_GDK_PIXBUF +	if (!image) { +		sway_log(L_ERROR, "Could not read icon image"); +		return NULL; +	} + +	free(image_path); +	return image; +} 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 @@ +#define _XOPEN_SOURCE 500 +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include <dbus/dbus.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include "swaybar/tray/dbus.h" +#include "swaybar/tray/sni.h" +#include "swaybar/tray/icon.h" +#include "swaybar/bar.h" +#include "client/cairo.h" +#include "log.h" + +// Not sure what this is but cairo needs it. +static const cairo_user_data_key_t cairo_user_data_key; + +struct sni_icon_ref *sni_icon_ref_create(struct StatusNotifierItem *item, +		int height) { +	struct sni_icon_ref *sni_ref = malloc(sizeof(struct sni_icon_ref)); +	if (!sni_ref) { +		return NULL; +	} +	sni_ref->icon = cairo_image_surface_scale(item->image, height, height); +	sni_ref->ref = item; + +	return sni_ref; +} + +void sni_icon_ref_free(struct sni_icon_ref *sni_ref) { +	if (!sni_ref) { +		return; +	} +	cairo_surface_destroy(sni_ref->icon); +	free(sni_ref); +} + +/* Gets the pixmap of an icon */ +static void reply_icon(DBusPendingCall *pending, void *_data) { +	struct StatusNotifierItem *item = _data; + +	DBusMessage *reply = dbus_pending_call_steal_reply(pending); + +	if (!reply) { +		sway_log(L_ERROR, "Did not get reply"); +		goto bail; +	} + +	int message_type = dbus_message_get_type(reply); + +	if (message_type == DBUS_MESSAGE_TYPE_ERROR) { +		char *msg; + +		dbus_message_get_args(reply, NULL, +				DBUS_TYPE_STRING, &msg, +				DBUS_TYPE_INVALID); + +		sway_log(L_ERROR, "Message is error: %s", msg); +		goto bail; +	} + +	DBusMessageIter iter; +	DBusMessageIter variant; /* v[a(iiay)] */ +	DBusMessageIter array; /* a(iiay) */ +	DBusMessageIter d_struct; /* (iiay) */ +	DBusMessageIter icon; /* ay */ + +	dbus_message_iter_init(reply, &iter); + +	// Each if here checks the types above before recursing +	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { +		sway_log(L_ERROR, "Relpy type incorrect"); +		sway_log(L_ERROR, "Should be \"v\", is \"%s\"", +				dbus_message_iter_get_signature(&iter)); +		goto bail; +	} +	dbus_message_iter_recurse(&iter, &variant); + +	if (strcmp("a(iiay)", dbus_message_iter_get_signature(&variant)) != 0) { +		sway_log(L_ERROR, "Relpy type incorrect"); +		sway_log(L_ERROR, "Should be \"a(iiay)\", is \"%s\"", +				dbus_message_iter_get_signature(&variant)); +		goto bail; +	} + +	if (dbus_message_iter_get_element_count(&variant) == 0) { +		// Can't recurse if there are no items +		sway_log(L_INFO, "Item has no icon"); +		goto bail; +	} +	dbus_message_iter_recurse(&variant, &array); + +	dbus_message_iter_recurse(&array, &d_struct); + +	int width; +	dbus_message_iter_get_basic(&d_struct, &width); +	dbus_message_iter_next(&d_struct); + +	int height; +	dbus_message_iter_get_basic(&d_struct, &height); +	dbus_message_iter_next(&d_struct); + +	int len = dbus_message_iter_get_element_count(&d_struct); + +	if (!len) { +		sway_log(L_ERROR, "No icon data"); +		goto bail; +	} + +	// Also implies len % 4 == 0, useful below +	if (len != width * height * 4) { +		sway_log(L_ERROR, "Incorrect array size passed"); +		goto bail; +	} + +	dbus_message_iter_recurse(&d_struct, &icon); + +	int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); +	// FIXME support a variable stride +	// (works on my machine though for all tested widths) +	if (!sway_assert(stride == width * 4, "Stride must be equal to byte length")) { +		goto bail; +	} + +	// Data is by reference, no need to free +	uint8_t *message_data; +	dbus_message_iter_get_fixed_array(&icon, &message_data, &len); + +	uint8_t *image_data = malloc(stride * height); +	if (!image_data) { +		sway_log(L_ERROR, "Could not allocate memory for icon"); +		goto bail; +	} + +	// Transform from network byte order to host byte order +	// Assumptions are safe because the equality above +	uint32_t *network = (uint32_t *) message_data; +	uint32_t *host = (uint32_t *)image_data; +	for (int i = 0; i < width * height; ++i) { +		host[i] = ntohl(network[i]); +	} + +	cairo_surface_t *image = cairo_image_surface_create_for_data( +			image_data, CAIRO_FORMAT_ARGB32, +			width, height, stride); + +	if (image) { +		if (item->image) { +			cairo_surface_destroy(item->image); +		} +		item->image = image; +		// Free the image data on surface destruction +		cairo_surface_set_user_data(image, +				&cairo_user_data_key, +				image_data, +				free); +		item->dirty = true; +		dirty = true; + +		dbus_message_unref(reply); +		return; +	} else { +		sway_log(L_ERROR, "Could not create image surface"); +		free(image_data); +	} + +bail: +	if (reply) { +		dbus_message_unref(reply); +	} +	sway_log(L_ERROR, "Could not get icon from item"); +	return; +} +static void send_icon_msg(struct StatusNotifierItem *item) { +	DBusPendingCall *pending; +	DBusMessage *message = dbus_message_new_method_call( +			item->name, +			"/StatusNotifierItem", +			"org.freedesktop.DBus.Properties", +			"Get"); +	const char *iface; +	if (item->kde_special_snowflake) { +		iface = "org.kde.StatusNotifierItem"; +	} else { +		iface = "org.freedesktop.StatusNotifierItem"; +	} +	const char *prop = "IconPixmap"; + +	dbus_message_append_args(message, +			DBUS_TYPE_STRING, &iface, +			DBUS_TYPE_STRING, &prop, +			DBUS_TYPE_INVALID); + +	bool status = +		dbus_connection_send_with_reply(conn, message, &pending, -1); + +	dbus_message_unref(message); + +	if (!(pending || status)) { +		sway_log(L_ERROR, "Could not get item icon"); +		return; +	} + +	dbus_pending_call_set_notify(pending, reply_icon, item, NULL); +} + +/* Get an icon by its name */ +static void reply_icon_name(DBusPendingCall *pending, void *_data) { +	struct StatusNotifierItem *item = _data; + +	DBusMessage *reply = dbus_pending_call_steal_reply(pending); + +	if (!reply) { +		sway_log(L_INFO, "Got no icon name reply from item"); +		goto bail; +	} + +	int message_type = dbus_message_get_type(reply); + +	if (message_type == DBUS_MESSAGE_TYPE_ERROR) { +		char *msg; + +		dbus_message_get_args(reply, NULL, +				DBUS_TYPE_STRING, &msg, +				DBUS_TYPE_INVALID); + +		sway_log(L_INFO, "Could not get icon name: %s", msg); +		goto bail; +	} + +	DBusMessageIter iter; /* v[s] */ +	DBusMessageIter variant; /* s */ + +	dbus_message_iter_init(reply, &iter); +	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { +		sway_log(L_ERROR, "Relpy type incorrect"); +		sway_log(L_ERROR, "Should be \"v\", is \"%s\"", +				dbus_message_iter_get_signature(&iter)); +		goto bail; +	} +	dbus_message_iter_recurse(&iter, &variant); + + +	if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_STRING) { +		sway_log(L_ERROR, "Relpy type incorrect"); +		sway_log(L_ERROR, "Should be \"s\", is \"%s\"", +				dbus_message_iter_get_signature(&iter)); +		goto bail; +	} + +	char *icon_name; +	dbus_message_iter_get_basic(&variant, &icon_name); + +	cairo_surface_t *image = find_icon(icon_name, 256); + +	if (image) { +		sway_log(L_DEBUG, "Icon for %s found with size %d", icon_name, +				cairo_image_surface_get_width(image)); +		if (item->image) { +			cairo_surface_destroy(item->image); +		} +		item->image = image; +		item->dirty = true; +		dirty = true; + +		dbus_message_unref(reply); +		return; +	} + +bail: +	if (reply) { +		dbus_message_unref(reply); +	} +	// Now try the pixmap +	send_icon_msg(item); +	return; +} +static void send_icon_name_msg(struct StatusNotifierItem *item) { +	DBusPendingCall *pending; +	DBusMessage *message = dbus_message_new_method_call( +			item->name, +			"/StatusNotifierItem", +			"org.freedesktop.DBus.Properties", +			"Get"); +	const char *iface; +	if (item->kde_special_snowflake) { +		iface = "org.kde.StatusNotifierItem"; +	} else { +		iface = "org.freedesktop.StatusNotifierItem"; +	} +	const char *prop = "IconName"; + +	dbus_message_append_args(message, +			DBUS_TYPE_STRING, &iface, +			DBUS_TYPE_STRING, &prop, +			DBUS_TYPE_INVALID); + +	bool status = +		dbus_connection_send_with_reply(conn, message, &pending, -1); + +	dbus_message_unref(message); + +	if (!(pending || status)) { +		sway_log(L_ERROR, "Could not get item icon name"); +		return; +	} + +	dbus_pending_call_set_notify(pending, reply_icon_name, item, NULL); +} + +void get_icon(struct StatusNotifierItem *item) { +	send_icon_name_msg(item); +} + +void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y) { +	const char *iface = +		(item->kde_special_snowflake ? "org.kde.StatusNotifierItem" +		 : "org.freedesktop.StatusNotifierItem"); +	DBusMessage *message = dbus_message_new_method_call( +			item->name, +			"/StatusNotifierItem", +			iface, +			"Activate"); + +	dbus_message_append_args(message, +			DBUS_TYPE_INT32, &x, +			DBUS_TYPE_INT32, &y, +			DBUS_TYPE_INVALID); + +	dbus_connection_send(conn, message, NULL); + +	dbus_message_unref(message); +} + +void sni_context_menu(struct StatusNotifierItem *item, uint32_t x, uint32_t y) { +	const char *iface = +		(item->kde_special_snowflake ? "org.kde.StatusNotifierItem" +		 : "org.freedesktop.StatusNotifierItem"); +	DBusMessage *message = dbus_message_new_method_call( +			item->name, +			"/StatusNotifierItem", +			iface, +			"ContextMenu"); + +	dbus_message_append_args(message, +			DBUS_TYPE_INT32, &x, +			DBUS_TYPE_INT32, &y, +			DBUS_TYPE_INVALID); + +	dbus_connection_send(conn, message, NULL); + +	dbus_message_unref(message); +} +void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y) { +	const char *iface = +		(item->kde_special_snowflake ? "org.kde.StatusNotifierItem" +		 : "org.freedesktop.StatusNotifierItem"); +	DBusMessage *message = dbus_message_new_method_call( +			item->name, +			"/StatusNotifierItem", +			iface, +			"SecondaryActivate"); + +	dbus_message_append_args(message, +			DBUS_TYPE_INT32, &x, +			DBUS_TYPE_INT32, &y, +			DBUS_TYPE_INVALID); + +	dbus_connection_send(conn, message, NULL); + +	dbus_message_unref(message); +} + +static void get_unique_name(struct StatusNotifierItem *item) { +	// I think that we're fine being sync here becaues the message is +	// directly to the message bus. Could be async though. +	DBusMessage *message = dbus_message_new_method_call( +			"org.freedesktop.DBus", +			"/org/freedesktop/DBus", +			"org.freedesktop.DBus", +			"GetNameOwner"); + +	dbus_message_append_args(message, +			DBUS_TYPE_STRING, &item->name, +			DBUS_TYPE_INVALID); + +	DBusMessage *reply = dbus_connection_send_with_reply_and_block( +			conn, message, -1, NULL); + +	dbus_message_unref(message); + +	if (!reply) { +		sway_log(L_ERROR, "Could not get unique name for item: %s", +				item->name); +		return; +	} + +	char *unique_name; +	if (!dbus_message_get_args(reply, NULL, +				DBUS_TYPE_STRING, &unique_name, +				DBUS_TYPE_INVALID)) { +		sway_log(L_ERROR, "Error parsing method args"); +	} else { +		if (item->unique_name) { +			free(item->unique_name); +		} +		item->unique_name = strdup(unique_name); +	} + +	dbus_message_unref(reply); +} + +struct StatusNotifierItem *sni_create(const char *name) { +	struct StatusNotifierItem *item = malloc(sizeof(struct StatusNotifierItem)); +	item->name = strdup(name); +	item->unique_name = NULL; +	item->image = NULL; +	item->dirty = false; + +	// If it doesn't use this name then assume that it uses the KDE spec +	// This is because xembed-sni-proxy uses neither "org.freedesktop" nor +	// "org.kde" and just gives us the items "unique name" +	// +	// We could use this to our advantage and fill out the "unique name" +	// field with the given name if it is neither freedesktop or kde, but +	// that's makes us rely on KDE hackyness which is bad practice +	const char freedesktop_name[] = "org.freedesktop"; +	if (strncmp(name, freedesktop_name, sizeof(freedesktop_name) - 1) != 0) { +		item->kde_special_snowflake = true; +	} else { +		item->kde_special_snowflake = false; +	} + +	get_icon(item); + +	get_unique_name(item); + +	return item; +} +/* Return 0 if `item` has a name of `str` */ +int sni_str_cmp(const void *_item, const void *_str) { +	const struct StatusNotifierItem *item = _item; +	const char *str = _str; + +	return strcmp(item->name, str); +} +/* Returns 0 if `item` has a unique name of `str` */ +int sni_uniq_cmp(const void *_item, const void *_str) { +	const struct StatusNotifierItem *item = _item; +	const char *str = _str; + +	if (!item->unique_name) { +		return false; +	} +	return strcmp(item->unique_name, str); +} +void sni_free(struct StatusNotifierItem *item) { +	if (!item) { +		return; +	} +	free(item->name); +	if (item->unique_name) { +		free(item->unique_name); +	} +	if (item->image) { +		cairo_surface_destroy(item->image); +	} +	free(item); +} 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 @@ +#define _XOPEN_SOURCE 500 +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <dbus/dbus.h> +#include "swaybar/tray/dbus.h" +#include "list.h" +#include "log.h" + +static list_t *items = NULL; +static list_t *hosts = NULL; + +/** + * Describes the function of the StatusNotifierWatcher + * See https://freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierWatcher/ + * + * We also implement KDE's special snowflake protocol, it's like this but with + * all occurrences 'freedesktop' replaced with 'kde'. There is no KDE introspect. + */ +static const char *interface_xml = +	"<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'" +	"'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>" +	"<node>" +	"  <interface name='org.freedesktop.DBus.Introspectable'>" +	"    <method name='Introspect'>" +	"       <arg name='xml_data' direction='out' type='s'/>" +	"    </method>" +	"  </interface>" +	"  <interface name='org.freedesktop.DBus.Properties'>" +	"    <method name='Get'>" +	"       <arg name='interface' direction='in' type='s'/>" +	"       <arg name='propname' direction='in' type='s'/>" +	"       <arg name='value' direction='out' type='v'/>" +	"    </method>" +	"    <method name='Set'>" +	"       <arg name='interface' direction='in' type='s'/>" +	"       <arg name='propname' direction='in' type='s'/>" +	"       <arg name='value' direction='in' type='v'/>" +	"    </method>" +	"    <method name='GetAll'>" +	"       <arg name='interface' direction='in' type='s'/>" +	"       <arg name='props' direction='out' type='a{sv}'/>" +	"    </method>" +	"  </interface>" +	"  <interface name='org.freedesktop.StatusNotifierWatcher'>" +	"    <method name='RegisterStatusNotifierItem'>" +	"      <arg type='s' name='service' direction='in'/>" +	"    </method>" +	"    <method name='RegisterStatusNotifierHost'>" +	"      <arg type='s' name='service' direction='in'/>" +	"    </method>" +	"    <property name='RegisteredStatusNotifierItems' type='as' access='read'/>" +	"    <property name='IsStatusNotifierHostRegistered' type='b' access='read'/>" +	"    <property name='ProtocolVersion' type='i' access='read'/>" +	"    <signal name='StatusNotifierItemRegistered'>" +	"      <arg type='s' name='service' direction='out'/>" +	"    </signal>" +	"    <signal name='StatusNotifierItemUnregistered'>" +	"      <arg type='s' name='service' direction='out'/>" +	"    </signal>" +	"    <signal name='StatusNotifierHostRegistered'>" +	"      <arg type='' name='service' direction='out'/>" +	"    </signal>" +	"  </interface>" +	"</node>"; + +static void host_registered_signal(DBusConnection *connection) { +	// Send one signal for each protocol +	DBusMessage *signal = dbus_message_new_signal( +			"/StatusNotifierWatcher", +			"org.freedesktop.StatusNotifierWatcher", +			"StatusNotifierHostRegistered"); + +	dbus_connection_send(connection, signal, NULL); +	dbus_message_unref(signal); + + +	signal = dbus_message_new_signal( +			"/StatusNotifierWatcher", +			"org.kde.StatusNotifierWatcher", +			"StatusNotifierHostRegistered"); + +	dbus_connection_send(connection, signal, NULL); +	dbus_message_unref(signal); +} +static void item_registered_signal(DBusConnection *connection, const char *name) { +	DBusMessage *signal = dbus_message_new_signal( +			"/StatusNotifierWatcher", +			"org.freedesktop.StatusNotifierWatcher", +			"StatusNotifierItemRegistered"); +	dbus_message_append_args(signal, +			DBUS_TYPE_STRING, &name, +			DBUS_TYPE_INVALID); +	dbus_connection_send(connection, signal, NULL); +	dbus_message_unref(signal); + +	signal = dbus_message_new_signal( +			"/StatusNotifierWatcher", +			"org.kde.StatusNotifierWatcher", +			"StatusNotifierItemRegistered"); +	dbus_message_append_args(signal, +			DBUS_TYPE_STRING, &name, +			DBUS_TYPE_INVALID); +	dbus_connection_send(connection, signal, NULL); +	dbus_message_unref(signal); +} +static void item_unregistered_signal(DBusConnection *connection, const char *name) { +	DBusMessage *signal = dbus_message_new_signal( +			"/StatusNotifierWatcher", +			"org.freedesktop.StatusNotifierWatcher", +			"StatusNotifierItemUnregistered"); +	dbus_message_append_args(signal, +			DBUS_TYPE_STRING, &name, +			DBUS_TYPE_INVALID); +	dbus_connection_send(connection, signal, NULL); +	dbus_message_unref(signal); + +	signal = dbus_message_new_signal( +			"/StatusNotifierWatcher", +			"org.kde.StatusNotifierWatcher", +			"StatusNotifierItemUnregistered"); +	dbus_message_append_args(signal, +			DBUS_TYPE_STRING, &name, +			DBUS_TYPE_INVALID); +	dbus_connection_send(connection, signal, NULL); +	dbus_message_unref(signal); +} + +static void respond_to_introspect(DBusConnection *connection, DBusMessage *request) { +	DBusMessage *reply; + +	reply = dbus_message_new_method_return(request); +	dbus_message_append_args(reply, +			DBUS_TYPE_STRING, &interface_xml, +			DBUS_TYPE_INVALID); +	dbus_connection_send(connection, reply, NULL); +	dbus_message_unref(reply); +} + +static void register_item(DBusConnection *connection, DBusMessage *message) { +	DBusError error; +	char *name; + +	dbus_error_init(&error); +	if (!dbus_message_get_args(message, &error, +				DBUS_TYPE_STRING, &name, +				DBUS_TYPE_INVALID)) { +		sway_log(L_ERROR, "Error parsing method args: %s\n", error.message); +	} + +	name = strdup(name); +	sway_log(L_INFO, "RegisterStatusNotifierItem called with \"%s\"\n", name); + +	// Don't add duplicate or not real item +	if (list_seq_find(items, (int (*)(const void *, const void *))strcmp, name) != -1) { +		return; +	} +	if (!dbus_bus_name_has_owner(connection, name, &error)) { +		return; +	} + +	list_add(items, name); +	item_registered_signal(connection, name); + +	// It's silly, but xembedsniproxy wants a reply for this function +	DBusMessage *reply = dbus_message_new_method_return(message); +	dbus_connection_send(connection, reply, NULL); +	dbus_message_unref(reply); +} + +static void register_host(DBusConnection *connection, DBusMessage *message) { +	DBusError error; +	char *name; + +	dbus_error_init(&error); +	if (!dbus_message_get_args(message, &error, +				DBUS_TYPE_STRING, &name, +				DBUS_TYPE_INVALID)) { +		sway_log(L_ERROR, "Error parsing method args: %s\n", error.message); +	} + +	sway_log(L_INFO, "RegisterStatusNotifierHost called with \"%s\"\n", name); + +	// Don't add duplicate or not real host +	if (list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name) != -1) { +		return; +	} +	if (!dbus_bus_name_has_owner(connection, name, &error)) { +		return; +	} + +	list_add(hosts, strdup(name)); +	host_registered_signal(connection); +} + +static void get_property(DBusConnection *connection, DBusMessage *message) { +	DBusError error; +	char *interface; +	char *property; + +	dbus_error_init(&error); +	if (!dbus_message_get_args(message, &error, +				DBUS_TYPE_STRING, &interface, +				DBUS_TYPE_STRING, &property, +				DBUS_TYPE_INVALID)) { +		sway_log(L_ERROR, "Error parsing prop args: %s\n", error.message); +		return; +	} + +	if (strcmp(property, "RegisteredStatusNotifierItems") == 0) { +		sway_log(L_INFO, "Replying with items\n"); +		DBusMessage *reply; +		reply = dbus_message_new_method_return(message); +		DBusMessageIter iter; +		DBusMessageIter sub; +		DBusMessageIter subsub; + +		dbus_message_iter_init_append(reply, &iter); + +		dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, +				"as", &sub); +		dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, +				"s", &subsub); + +		for (int i = 0; i < items->length; ++i) { +			dbus_message_iter_append_basic(&subsub, +					DBUS_TYPE_STRING, &items->items[i]); +		} + +		dbus_message_iter_close_container(&sub, &subsub); +		dbus_message_iter_close_container(&iter, &sub); + +		dbus_connection_send(connection, reply, NULL); +		dbus_message_unref(reply); +	} else if (strcmp(property, "IsStatusNotifierHostRegistered") == 0) { +		DBusMessage *reply; +		DBusMessageIter iter; +		DBusMessageIter sub; +		int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1; + +		reply = dbus_message_new_method_return(message); + +		dbus_message_iter_init_append(reply, &iter); + +		dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, +				"b", &sub); +		dbus_message_iter_append_basic(&sub, +				DBUS_TYPE_BOOLEAN, ®istered); + +		dbus_message_iter_close_container(&iter, &sub); + +		dbus_connection_send(connection, reply, NULL); +		dbus_message_unref(reply); +	} else if (strcmp(property, "ProtocolVersion") == 0) { +		DBusMessage *reply; +		DBusMessageIter iter; +		DBusMessageIter sub; +		const int version = 0; + +		reply = dbus_message_new_method_return(message); + +		dbus_message_iter_init_append(reply, &iter); + +		dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, +				"i", &sub); +		dbus_message_iter_append_basic(&sub, +				DBUS_TYPE_INT32, &version); + +		dbus_message_iter_close_container(&iter, &sub); +		dbus_connection_send(connection, reply, NULL); +		dbus_message_unref(reply); +	} +} + +static void set_property(DBusConnection *connection, DBusMessage *message) { +	// All properties are read only and we don't allow new properties +	return; +} + +static void get_all(DBusConnection *connection, DBusMessage *message) { +	DBusMessage *reply; +	reply = dbus_message_new_method_return(message); +	DBusMessageIter iter; /* a{v} */ +	DBusMessageIter arr; +	DBusMessageIter dict; +	DBusMessageIter sub; +	DBusMessageIter subsub; +	int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1; +	const int version = 0; +	const char *prop; + +	// Could clean this up with a function for each prop +	dbus_message_iter_init_append(reply, &iter); +	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, +			"{sv}", &arr); + +	prop = "RegisteredStatusNotifierItems"; +	dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, +			NULL, &dict); +	dbus_message_iter_append_basic(&dict, +			DBUS_TYPE_STRING, &prop); +	dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, +			"as", &sub); +	dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, +			"s", &subsub); +	for (int i = 0; i < items->length; ++i) { +		dbus_message_iter_append_basic(&subsub, +				DBUS_TYPE_STRING, &items->items[i]); +	} +	dbus_message_iter_close_container(&sub, &subsub); +	dbus_message_iter_close_container(&dict, &sub); +	dbus_message_iter_close_container(&arr, &dict); + +	prop = "IsStatusNotifierHostRegistered"; +	dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, +			NULL, &dict); +	dbus_message_iter_append_basic(&dict, +			DBUS_TYPE_STRING, &prop); +	dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, +			"b", &sub); +	dbus_message_iter_append_basic(&sub, +			DBUS_TYPE_BOOLEAN, ®istered); +	dbus_message_iter_close_container(&dict, &sub); +	dbus_message_iter_close_container(&arr, &dict); + +	prop = "ProtocolVersion"; +	dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, +			NULL, &dict); +	dbus_message_iter_append_basic(&dict, +			DBUS_TYPE_STRING, &prop); +	dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, +			"i", &sub); +	dbus_message_iter_append_basic(&sub, +			DBUS_TYPE_INT32, &version); +	dbus_message_iter_close_container(&dict, &sub); +	dbus_message_iter_close_container(&arr, &dict); + +	dbus_message_iter_close_container(&iter, &arr); + +	dbus_connection_send(connection, reply, NULL); +	dbus_message_unref(reply); +} + +static DBusHandlerResult message_handler(DBusConnection *connection,  +		DBusMessage *message, void *data) { +	const char *interface_name = dbus_message_get_interface(message); +	const char *member_name = dbus_message_get_member(message); + +	// In order of the xml above +	if (strcmp(interface_name, "org.freedesktop.DBus.Introspectable") == 0 && +			strcmp(member_name, "Introspect") == 0) { +		// We don't have an introspect for KDE +		respond_to_introspect(connection, message); +		return DBUS_HANDLER_RESULT_HANDLED; +	} else if (strcmp(interface_name, "org.freedesktop.DBus.Properties") == 0) { +		if (strcmp(member_name, "Get") == 0) { +			get_property(connection, message); +			return DBUS_HANDLER_RESULT_HANDLED; +		} else if (strcmp(member_name, "Set") == 0) { +			set_property(connection, message); +			return DBUS_HANDLER_RESULT_HANDLED; +		} else if (strcmp(member_name, "GetAll") == 0) { +			get_all(connection, message); +			return DBUS_HANDLER_RESULT_HANDLED; +		} else { +			return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +		} +	} else if (strcmp(interface_name, "org.freedesktop.StatusNotifierWatcher") == 0 || +			strcmp(interface_name, "org.kde.StatusNotifierWatcher") == 0) { +		if (strcmp(member_name, "RegisterStatusNotifierItem") == 0) { +			register_item(connection, message); +			return DBUS_HANDLER_RESULT_HANDLED; +		} else if (strcmp(member_name, "RegisterStatusNotifierHost") == 0) { +			register_host(connection, message); +			return DBUS_HANDLER_RESULT_HANDLED; +		} else { +			return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +		} +	} +	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult signal_handler(DBusConnection *connection, +		DBusMessage *message, void *_data) { +	if (dbus_message_is_signal(message, "org.freedesktop.DBus", "NameOwnerChanged")) { +		// Only eat the message if it is name that we are watching +		const char *name; +		const char *old_owner; +		const char *new_owner; +		int index; +		if (!dbus_message_get_args(message, NULL, +				DBUS_TYPE_STRING, &name, +				DBUS_TYPE_STRING, &old_owner, +				DBUS_TYPE_STRING, &new_owner, +				DBUS_TYPE_INVALID)) { +			sway_log(L_ERROR, "Error getting LostName args"); +			return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +		} +		if (strcmp(new_owner, "") != 0) { +			// Name is not lost +			return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +		} +		if ((index = list_seq_find(items, (int (*)(const void *, const void *))strcmp, name)) != -1) { +			sway_log(L_INFO, "Status Notifier Item lost %s", name); +			free(items->items[index]); +			list_del(items, index); +			item_unregistered_signal(connection, name); + +			return DBUS_HANDLER_RESULT_HANDLED; +		} +		if ((index = list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name)) != -1) { +			sway_log(L_INFO, "Status Notifier Host lost %s", name); +			free(hosts->items[index]); +			list_del(hosts, index); + +			return DBUS_HANDLER_RESULT_HANDLED; +		} +	} +	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static const DBusObjectPathVTable vtable = { +	.message_function = message_handler, +	.unregister_function = NULL, +}; + +int init_sni_watcher() { +	DBusError error; +	dbus_error_init(&error); +	if (!conn) { +		sway_log(L_ERROR, "Connection is null, cannot initiate StatusNotifierWatcher"); +		return -1; +	} + +	items = create_list(); +	hosts = create_list(); + +	int status = dbus_bus_request_name(conn, "org.freedesktop.StatusNotifierWatcher", +			DBUS_NAME_FLAG_REPLACE_EXISTING, +			&error); +	if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { +		sway_log(L_DEBUG, "Got watcher name"); +	} else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) { +		sway_log(L_INFO, "Could not get watcher name, it may start later"); +	} +	if (dbus_error_is_set(&error)) { +		sway_log(L_ERROR, "dbus err getting watcher name: %s\n", error.message); +		return -1; +	} + +	status = dbus_bus_request_name(conn, "org.kde.StatusNotifierWatcher", +			DBUS_NAME_FLAG_REPLACE_EXISTING, +			&error); +	if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { +		sway_log(L_DEBUG, "Got kde watcher name"); +	} else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) { +		sway_log(L_INFO, "Could not get kde watcher name, it may start later"); +	} +	if (dbus_error_is_set(&error)) { +		sway_log(L_ERROR, "dbus err getting kde watcher name: %s\n", error.message); +		return -1; +	} + +	dbus_connection_try_register_object_path(conn, +			"/StatusNotifierWatcher", +			&vtable, NULL, &error); +	if (dbus_error_is_set(&error)) { +		sway_log(L_ERROR, "dbus_err: %s\n", error.message); +		return -1; +	} + +	dbus_bus_add_match(conn, +			"type='signal',\ +			sender='org.freedesktop.DBus',\ +			interface='org.freedesktop.DBus',\ +			member='NameOwnerChanged'", +			&error); + +	if (dbus_error_is_set(&error)) { +		sway_log(L_ERROR, "DBus error getting match args: %s", error.message); +	} + +	dbus_connection_add_filter(conn, signal_handler, NULL, NULL); +	return 0; +} 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 @@ +#define _XOPEN_SOURCE 500 +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/wait.h> +#include <dbus/dbus.h> +#include "swaybar/bar.h" +#include "swaybar/tray/tray.h" +#include "swaybar/tray/dbus.h" +#include "swaybar/tray/sni.h" +#include "swaybar/tray/sni_watcher.h" +#include "swaybar/bar.h" +#include "swaybar/config.h" +#include "list.h" +#include "log.h" + +struct tray *tray; + +static void register_host(char *name) { +	DBusMessage *message; + +	message = dbus_message_new_method_call( +			"org.freedesktop.StatusNotifierWatcher", +			"/StatusNotifierWatcher", +			"org.freedesktop.StatusNotifierWatcher", +			"RegisterStatusNotifierHost"); +	if (!message) { +		sway_log(L_ERROR, "Cannot allocate dbus method call"); +		return; +	} + +	dbus_message_append_args(message, +			DBUS_TYPE_STRING, &name, +			DBUS_TYPE_INVALID); + +	dbus_connection_send(conn, message, NULL); + +	dbus_message_unref(message); +} + +static void get_items_reply(DBusPendingCall *pending, void *_data) { +	DBusMessage *reply = dbus_pending_call_steal_reply(pending); + +	if (!reply) { +		sway_log(L_ERROR, "Got no items reply from sni watcher"); +		goto bail; +	} + +	int message_type = dbus_message_get_type(reply); + +	if (message_type == DBUS_MESSAGE_TYPE_ERROR) { +		char *msg; + +		dbus_message_get_args(reply, NULL, +				DBUS_TYPE_STRING, &msg, +				DBUS_TYPE_INVALID); + +		sway_log(L_ERROR, "Message is error: %s", msg); +		goto bail; +	} + +	DBusMessageIter iter; +	DBusMessageIter variant; +	DBusMessageIter array; + +	dbus_message_iter_init(reply, &iter); +	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { +		sway_log(L_ERROR, "Replyed with wrong type, not v(as)"); +		goto bail; +	} +	dbus_message_iter_recurse(&iter, &variant); +	if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_ARRAY || +			dbus_message_iter_get_element_type(&variant) != DBUS_TYPE_STRING) { +		sway_log(L_ERROR, "Replyed with wrong type, not v(as)"); +		goto bail; +	} + +	// Clear list +	list_foreach(tray->items, (void (*)(void *))sni_free); +	list_free(tray->items); +	tray->items = create_list(); + +	// O(n) function, could be faster dynamically reading values +	int len = dbus_message_iter_get_element_count(&variant); + +	dbus_message_iter_recurse(&variant, &array); +	for (int i = 0; i < len; i++) { +		const char *name; +		dbus_message_iter_get_basic(&array, &name); + +		struct StatusNotifierItem *item = sni_create(name); + +		sway_log(L_DEBUG, "Item registered with host: %s", name); +		list_add(tray->items, item); +		dirty = true; +	} + +bail: +	dbus_message_unref(reply); +	return; +} +static void get_items() { +	DBusPendingCall *pending; +	DBusMessage *message = dbus_message_new_method_call( +			"org.freedesktop.StatusNotifierWatcher", +			"/StatusNotifierWatcher", +			"org.freedesktop.DBus.Properties", +			"Get"); + +	const char *iface = "org.freedesktop.StatusNotifierWatcher"; +	const char *prop = "RegisteredStatusNotifierItems"; +	dbus_message_append_args(message, +			DBUS_TYPE_STRING, &iface, +			DBUS_TYPE_STRING, &prop, +			DBUS_TYPE_INVALID); + +	bool status = +		dbus_connection_send_with_reply(conn, message, &pending, -1); +	dbus_message_unref(message); + +	if (!(pending || status)) { +		sway_log(L_ERROR, "Could not get items"); +		return; +	} + +	dbus_pending_call_set_notify(pending, get_items_reply, NULL, NULL); +} + +static DBusHandlerResult signal_handler(DBusConnection *connection, +		DBusMessage *message, void *_data) { +	if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher", +				"StatusNotifierItemRegistered")) { +		const char *name; +		if (!dbus_message_get_args(message, NULL, +				DBUS_TYPE_STRING, &name, +				DBUS_TYPE_INVALID)) { +			sway_log(L_ERROR, "Error getting StatusNotifierItemRegistered args"); +			return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +		} + +		if (list_seq_find(tray->items, sni_str_cmp, name) == -1) { +			struct StatusNotifierItem *item = sni_create(name); + +			list_add(tray->items, item); +			dirty = true; +		} + +		return DBUS_HANDLER_RESULT_HANDLED; +	} else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher", +				"StatusNotifierItemUnregistered")) { +		const char *name; +		if (!dbus_message_get_args(message, NULL, +				DBUS_TYPE_STRING, &name, +				DBUS_TYPE_INVALID)) { +			sway_log(L_ERROR, "Error getting StatusNotifierItemUnregistered args"); +			return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +		} + +		int index; +		if ((index = list_seq_find(tray->items, sni_str_cmp, name)) != -1) { +			sni_free(tray->items->items[index]); +			list_del(tray->items, index); +			dirty = true; +		} else { +			// If it's not in our list, then our list is incorrect. +			// Fetch all items again +			sway_log(L_INFO, "Host item list incorrect, refreshing"); +			get_items(); +		} + +		return DBUS_HANDLER_RESULT_HANDLED; +	} else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierItem", +				"NewIcon") || dbus_message_is_signal(message, +				"org.kde.StatusNotifierItem", "NewIcon")) { +		const char *name; +		int index; +		struct StatusNotifierItem *item; + +		name = dbus_message_get_sender(message); +		if ((index = list_seq_find(tray->items, sni_uniq_cmp, name)) != -1) { +			item = tray->items->items[index]; +			sway_log(L_INFO, "NewIcon signal from item %s", item->name); +			get_icon(item); +		} + +		return DBUS_HANDLER_RESULT_HANDLED; +	} +	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static int init_host() { +	tray = (struct tray *)malloc(sizeof(tray)); + +	tray->items = create_list(); + +	DBusError error; +	dbus_error_init(&error); +	char *name = NULL; +	if (!conn) { +		sway_log(L_ERROR, "Connection is null, cannot init SNI host"); +		goto err; +	} +	name = calloc(sizeof(char), 256); + +	if (!name) { +		sway_log(L_ERROR, "Cannot allocate name"); +		goto err; +	} + +	pid_t pid = getpid(); +	if (snprintf(name, 256, "org.freedesktop.StatusNotifierHost-%d", pid) +			>= 256) { +		sway_log(L_ERROR, "Cannot get host name because string is too short." +				"This should not happen"); +		goto err; +	} + +	// We want to be the sole owner of this name +	if (dbus_bus_request_name(conn, name, DBUS_NAME_FLAG_DO_NOT_QUEUE, +			&error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { +		sway_log(L_ERROR, "Cannot get host name and start the tray"); +		goto err; +	} +	if (dbus_error_is_set(&error)) { +		sway_log(L_ERROR, "Dbus err getting host name: %s\n", error.message); +		goto err; +	} +	sway_log(L_DEBUG, "Got host name"); + +	register_host(name); + +	get_items(); + +	// Perhaps use addmatch helper functions like wlc does? +	dbus_bus_add_match(conn, +			"type='signal',\ +			sender='org.freedesktop.StatusNotifierWatcher',\ +			member='StatusNotifierItemRegistered'", +			&error); +	if (dbus_error_is_set(&error)) { +		sway_log(L_ERROR, "dbus_err: %s", error.message); +		goto err; +	} +	dbus_bus_add_match(conn, +			"type='signal',\ +			sender='org.freedesktop.StatusNotifierWatcher',\ +			member='StatusNotifierItemUnregistered'", +			&error); +	if (dbus_error_is_set(&error)) { +		sway_log(L_ERROR, "dbus_err: %s", error.message); +		return -1; +	} + +	// SNI matches +	dbus_bus_add_match(conn, +			"type='signal',\ +			interface='org.freedesktop.StatusNotifierItem',\ +			member='NewIcon'", +			&error); +	if (dbus_error_is_set(&error)) { +		sway_log(L_ERROR, "dbus_err %s", error.message); +		goto err; +	} +	dbus_bus_add_match(conn, +			"type='signal',\ +			interface='org.kde.StatusNotifierItem',\ +			member='NewIcon'", +			&error); +	if (dbus_error_is_set(&error)) { +		sway_log(L_ERROR, "dbus_err %s", error.message); +		goto err; +	} + +	dbus_connection_add_filter(conn, signal_handler, NULL, NULL); + +	free(name); +	return 0; + +err: +	// TODO better handle errors +	free(name); +	return -1; +} + +void tray_mouse_event(struct output *output, int x, int y, +		uint32_t button, uint32_t state) { + +	struct window *window = output->window; +	uint32_t tray_padding = swaybar.config->tray_padding; +	int tray_width = window->width * window->scale; + +	for (int i = 0; i < output->items->length; ++i) { +		struct sni_icon_ref *item = +			 output->items->items[i]; +		int icon_width = cairo_image_surface_get_width(item->icon); + +		tray_width -= tray_padding; +		if (x <= tray_width && x >= tray_width - icon_width) { +			if (button == swaybar.config->activate_button) { +				sni_activate(item->ref, x, y); +			} else if (button == swaybar.config->context_button) { +				sni_context_menu(item->ref, x, y); +			} else if (button == swaybar.config->secondary_button) { +				sni_secondary(item->ref, x, y); +			} +			break; +		} +		tray_width -= icon_width; +	} +} + +uint32_t tray_render(struct output *output, struct config *config) { +	struct window *window = output->window; +	cairo_t *cairo = window->cairo; + +	// Tray icons +	uint32_t tray_padding = config->tray_padding; +	uint32_t tray_width = window->width * window->scale; +	const int item_size = (window->height * window->scale) - (2 * tray_padding); + +	if (item_size < 0) { +		// Can't render items if the padding is too large +		return tray_width; +	} + +	if (config->tray_output && strcmp(config->tray_output, output->name) != 0) { +		return tray_width; +	} + +	for (int i = 0; i < tray->items->length; ++i) { +		struct StatusNotifierItem *item = +			tray->items->items[i]; +		if (!item->image) { +			continue; +		} + +		struct sni_icon_ref *render_item = NULL; +		int j; +		for (j = i; j < output->items->length; ++j) { +			struct sni_icon_ref *ref = +				output->items->items[j]; +			if (ref->ref == item) { +				render_item = ref; +				break; +			} else { +				sni_icon_ref_free(ref); +				list_del(output->items, j); +			} +		} + +		if (!render_item) { +			render_item = sni_icon_ref_create(item, item_size); +			list_add(output->items, render_item); +		} else if (item->dirty) { +			// item needs re-render +			sni_icon_ref_free(render_item); +			output->items->items[j] = render_item = +				sni_icon_ref_create(item, item_size); +		} + +		tray_width -= tray_padding; +		tray_width -= item_size; + +		cairo_operator_t op = cairo_get_operator(cairo); +		cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); +		cairo_set_source_surface(cairo, render_item->icon, tray_width, tray_padding); +		cairo_rectangle(cairo, tray_width, tray_padding, item_size, item_size); +		cairo_fill(cairo); +		cairo_set_operator(cairo, op); + +		item->dirty = false; +	} + + +	if (tray_width != window->width * window->scale) { +		tray_width -= tray_padding; +	} + +	return tray_width; +} + +void init_tray(struct bar *bar) { +	if (!bar->config->tray_output || strcmp(bar->config->tray_output, "none") != 0) { +		/* Connect to the D-Bus */ +		dbus_init(); + +		/* Start the SNI watcher */ +		init_sni_watcher(); + +		/* Start the SNI host */ +		init_host(); +	} +} | 
