apply tray patch

Looks like this actually works?
feature/tray
Ryan 2023-09-26 21:00:07 -04:00
parent fcc7e9be56
commit 1a1dbf4bc7
Signed by: ryan
GPG Key ID: 7D7E2E94267DAD95
16 changed files with 1711 additions and 2 deletions

1
.gitignore vendored
View File

@ -3,4 +3,5 @@ dwl-bar
compile_flags.txt compile_flags.txt
bar.log bar.log
config.h config.h
src/lib.h
*-protocol.* *-protocol.*

View File

@ -5,6 +5,8 @@
# @version 0.0 # @version 0.0
VERSION = 0.0 VERSION = 0.0
PKG_CONFIG = pkg-config PKG_CONFIG = pkg-config
PKG_EXISTS = $(PKG_CONFIG) --exists
SD_BUS = $(shell { $(PKG_EXISTS) libsystemd && echo "libsystemd"; } || { $(PKG_EXISTS) libelogind && echo "libelogind"; } || { $(PKG_EXISTS) basu && echo "basu"; } || exit 1; )
# paths # paths
PREFIX = /usr/local PREFIX = /usr/local
@ -12,11 +14,15 @@ MANDIR = $(PREFIX)/share/man
SRCDIR = src SRCDIR = src
PKGS = wayland-client wayland-cursor pangocairo PKGS = wayland-client wayland-cursor pangocairo
PKGS += $(SD_BUS)
FILES = $(SRCDIR)/main.c $(SRCDIR)/main.h $(SRCDIR)/log.c $(SRCDIR)/log.h \ FILES = $(SRCDIR)/main.c $(SRCDIR)/main.h $(SRCDIR)/log.c $(SRCDIR)/log.h \
$(SRCDIR)/render.c $(SRCDIR)/render.h $(SRCDIR)/event.c $(SRCDIR)/event.h \ $(SRCDIR)/render.c $(SRCDIR)/render.h $(SRCDIR)/event.c $(SRCDIR)/event.h \
$(SRCDIR)/util.c $(SRCDIR)/util.h $(SRCDIR)/shm.c $(SRCDIR)/shm.h \ $(SRCDIR)/util.c $(SRCDIR)/util.h $(SRCDIR)/shm.c $(SRCDIR)/shm.h \
$(SRCDIR)/input.c $(SRCDIR)/input.h $(SRCDIR)/user.c $(SRCDIR)/user.h \ $(SRCDIR)/input.c $(SRCDIR)/input.h $(SRCDIR)/user.c $(SRCDIR)/user.h \
$(SRCDIR)/bar.c $(SRCDIR)/bar.h $(SRCDIR)/config.h $(SRCDIR)/bar.c $(SRCDIR)/bar.h $(SRCDIR)/config.h
FILES += $(SRCDIR)/lib.h $(SRCDIR)/icon.c $(SRCDIR)/icon.h $(SRCDIR)/item.c $(SRCDIR)/item.h \
$(SRCDIR)/host.c $(SRCDIR)/host.h $(SRCDIR)/watcher.c $(SRCDIR)/watcher.h \
$(SRCDIR)/tray.c $(SRCDIR)/tray.h
OBJS = $(SRCDIR)/xdg-output-unstable-v1-protocol.o $(SRCDIR)/xdg-shell-protocol.o \ OBJS = $(SRCDIR)/xdg-output-unstable-v1-protocol.o $(SRCDIR)/xdg-shell-protocol.o \
$(SRCDIR)/wlr-layer-shell-unstable-v1-protocol.o $(SRCDIR)/wlr-layer-shell-unstable-v1-protocol.o
OBJS := $(filter-out $(SRCDIR)/xdg-output-unstable-v1-protocol.o,$(OBJS)) OBJS := $(filter-out $(SRCDIR)/xdg-output-unstable-v1-protocol.o,$(OBJS))
@ -58,13 +64,21 @@ $(SRCDIR)/dwl-ipc-unstable-v2-protocol.c:
$(WAYLAND_SCANNER) private-code \ $(WAYLAND_SCANNER) private-code \
protocols/dwl-ipc-unstable-v2.xml $@ protocols/dwl-ipc-unstable-v2.xml $@
$(SRCDIR)/lib.h:
touch $(SRCDIR)/lib.h
echo -e "#ifndef LIB_H_\n#define LIB_H_\n" > $(SRCDIR)/lib.h
{ $(PKG_EXISTS) libsystemd && echo -e "#define SYSTEMD 1\n" | tee -a $(SRCDIR)/lib.h; } || echo -e "#define SYSTEMD 0\n" | tee -a $(SRCDIR)/lib.h;
{ $(PKG_EXISTS) libelogind && echo -e "#define ELOGIND 1\n" | tee -a $(SRCDIR)/lib.h; } || echo -e "#define ELOGIND 0\n" | tee -a $(SRCDIR)/lib.h;
{ $(PKG_EXISTS) basu && echo -e "#define BASU 1\n" | tee -a $(SRCDIR)/lib.h; } || echo -e "#define BASU 0\n" | tee -a $(SRCDIR)/lib.h;
echo "#endif // LIB_H_" | tee -a $(SRCDIR)/lib.h
$(SRCDIR)/config.h: $(SRCDIR)/config.h:
cp src/config.def.h $@ cp src/config.def.h $@
dev: clean $(SRCDIR)/config.h $(OBJS) dev: clean $(SRCDIR)/lib.h $(SRCDIR)/config.h $(OBJS)
clean: clean:
rm -f dwl-bar src/config.h src/*.o src/*-protocol.* rm -f dwl-bar src/config.h src/lib.h src/*.o src/*-protocol.*
dist: clean dist: clean
mkdir -p dwl-bar-$(VERSION) mkdir -p dwl-bar-$(VERSION)

View File

@ -10,6 +10,7 @@ static const int bar_top = 1; /* Boolean value, non-zero is true. If no
static const int status_on_active = 1; /* Display the status on active monitor only. If not then on all. */ static const int status_on_active = 1; /* Display the status on active monitor only. If not then on all. */
static const char *font = "Monospace 10"; static const char *font = "Monospace 10";
static const char *terminal[] = { "alacritty", NULL }; static const char *terminal[] = { "alacritty", NULL };
static const char *icon_theme = "Hicolor";
/* /*
* Colors: * Colors:
@ -53,6 +54,13 @@ static const Binding bindings[] = {
{ Click_Tag, BTN_MIDDLE, tag, 0, {0} }, { Click_Tag, BTN_MIDDLE, tag, 0, {0} },
{ Click_Tag, BTN_RIGHT, toggle_view, 0, {0} }, { Click_Tag, BTN_RIGHT, toggle_view, 0, {0} },
{ Click_Tag, BTN_LEFT, view, 0, {0} }, { Click_Tag, BTN_LEFT, view, 0, {0} },
{ Click_Systray, BTN_LEFT, systray, 0, {0} },
{ Click_Systray, BTN_MIDDLE, systray, 0, {0} },
{ Click_Systray, BTN_RIGHT, systray, 0, {0} },
{ Click_Systray, Scroll_Up, systray, 0, {0} },
{ Click_Systray, Scroll_Down, systray, 0, {0} },
{ Click_Systray, Scroll_Left, systray, 0, {0} },
{ Click_Systray, Scroll_Right, systray, 0, {0} },
}; };
#endif // CONFIG_H_ #endif // CONFIG_H_

173
src/host.c Normal file
View File

@ -0,0 +1,173 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <wayland-util.h>
#include "log.h"
#include "tray.h"
#include "host.h"
#include "item.h"
#include "util.h"
#include "main.h"
static int get_registered_items_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error);
static int handle_new_watcher(sd_bus_message *m, void *userdata, sd_bus_error *error);
static int handle_registered_item(sd_bus_message *m, void *userdata, sd_bus_error *error);
static int handle_unregistered_item(sd_bus_message *m, void *userdata, sd_bus_error *error);
static int register_to_watcher(struct Host *host);
int get_registered_items_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
if (sd_bus_message_is_method_error(m, NULL))
panic("get_registered_items_callback sd_bus_message_is_method_error %s", sd_bus_message_get_error(m)->message);
if (sd_bus_message_enter_container(m, 'v', NULL) < 0)
panic("get_registered_items_callback sd_bus_message_enter_container");
char **ids;
if (sd_bus_message_read_strv(m, &ids) < 0)
panic("get_registered_items_callback sd_bus_message_read_strv");
if (ids) {
struct Tray *tray = userdata;
for (char **id = ids; *id; id++) {
struct Item *item = item_create(tray, *id);
wl_list_insert(&tray->items, &item->link);
free(*id);
}
}
free(ids);
return 0;
}
int handle_new_watcher(sd_bus_message *m, void *userdata, sd_bus_error *error) {
char *service, *old_owner, *new_owner;
if (sd_bus_message_read(m, "sss", &service, &old_owner, &new_owner) < 0)
panic("handle_new_watcher sd_bus_message_read");
if (*old_owner)
return 0;
struct Host *host = userdata;
if (STRING_EQUAL(service, host->interface))
register_to_watcher(host);
return 0;
}
int handle_registered_item(sd_bus_message *m, void *userdata, sd_bus_error *error) {
char *id;
if (sd_bus_message_read(m, "s", &id) < 0)
panic("handle_registered_item sd_bus_message_read");
struct Tray *tray = userdata;
struct Item *item;
wl_list_for_each(item, &tray->items, link) {
if (STRING_EQUAL(item->watcher_id, id)) // If we already have this item.
return 0;
}
item = item_create(tray, id);
wl_list_insert(&tray->items, &item->link);
return 0;
}
int handle_unregistered_item(sd_bus_message *m, void *userdata, sd_bus_error *error) {
char *id;
if (sd_bus_message_read(m, "s", &id) < 0)
panic("sd_bus_message_read");
struct Tray *tray = userdata;
struct Item *item, *tmp;
wl_list_for_each_safe(item, tmp, &tray->items, link) {
if (!(STRING_EQUAL(item->watcher_id, id)))
continue;
wl_list_remove(&item->link);
item_destroy(item);
monitors_update();
break;
}
return 0;
}
struct Host *host_create(char *protocol, struct Tray *tray) {
if (!protocol || !tray)
return NULL;
struct Host *host = ecalloc(1, sizeof(*host));
sd_bus_slot *register_slot = NULL, *unregister_slot = NULL, *watcher_slot = NULL;
const char *error;
host->interface = string_create("org.%s.StatusNotifierWatcher", protocol);
host->tray = tray;
if (sd_bus_match_signal(tray->bus, &register_slot, host->interface, watcher_path, host->interface,
"StatusNotifierItemRegistered", handle_registered_item, tray) < 0) {
error = "sd_bus_match_signal StatusNotifierItemRegistered protocol: %s";
goto error;
}
if (sd_bus_match_signal(tray->bus, &unregister_slot, host->interface, watcher_path, host->interface,
"StatusNotifierItemUnregistered", handle_unregistered_item, tray) < 0) {
error = "sd_bus_match_signal StatusNotifierItemUnregistered protocol: %s";
goto error;
}
if (sd_bus_match_signal(tray->bus, &watcher_slot, "org.freedesktop.DBus",
"/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged",
handle_new_watcher, host) < 0) {
error = "sd_bus_match_signal NameOwnerChanged protocol: %s";
goto error;
}
pid_t pid = getpid();
host->name = string_create("org.%s.StatusNotifierHost-%d", protocol, pid);
if (!register_to_watcher(host)) {
error = "register_to_watcher protocol: %s";
goto error;
}
sd_bus_slot_set_floating(register_slot, 0);
sd_bus_slot_set_floating(unregister_slot, 0);
sd_bus_slot_set_floating(watcher_slot, 0);
return host;
error:
sd_bus_slot_unref(register_slot);
sd_bus_slot_unref(unregister_slot);
sd_bus_slot_unref(watcher_slot);
host_destroy(host);
panic(error, protocol);
return NULL;
}
void host_destroy(struct Host *host) {
if (!host) return;
sd_bus_release_name(host->tray->bus, host->name);
free(host->name);
free(host->interface);
}
int register_to_watcher(struct Host *host) {
if (sd_bus_call_method_async(host->tray->bus, NULL,
host->interface, watcher_path, host->interface,
"RegisterStatusNotifierHost", NULL, NULL, "s", host->name) < 0)
return 0;
if (sd_bus_call_method_async(host->tray->bus, NULL,
host->interface, watcher_path,
"org.freedesktop.DBus.Properties", "Get",
get_registered_items_callback, host, "ss",
host->interface, "RegisteredStatusNotifierItems") < 0)
return 0;
return 1;
}

15
src/host.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef HOST_H_
#define HOST_H_
#include "tray.h"
struct Host {
struct Tray *tray;
char *name;
char *interface;
};
struct Host *host_create(char *protocol, struct Tray *tray);
void host_destroy(struct Host *host);
#endif // HOST_H_

523
src/icon.c Normal file
View File

@ -0,0 +1,523 @@
#include "icon.h"
#include "util.h"
#include "log.h"
#include <bits/pthreadtypes.h>
#include <ctype.h>
#include <pthread.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <wayland-client-core.h>
#include <wayland-util.h>
static void add_dir(struct List *list, char *dir);
static int basedir_has_theme(char *theme, char *dir);
static int dir_exists(char *path);
static char *entry_handler(char *group, char *key, char *value, struct Theme *theme);
static void get_basedirs(struct List *basedirs);
static char *get_fallback_icon(struct List *themes, struct List *basedirs, char *name);
static char *group_handler(char *last_group, char *new_group, struct Theme *theme);
static void load_themes(struct List *themes, char *basedir);
static struct Theme *read_theme_file(char *dir, char *name);
static struct List *split_string(const char *str, const char *delim);
static char *subdir_get_icon(char *name, char *basedir, char *theme, char *subdir);
static void theme_destroy(struct Theme *theme);
static void theme_element_destroy(void *ptr);
static char *theme_get_icon(struct List *themes, struct List *basedirs, char *name, int size, const char *theme_name);
static int theme_name_compare(const void *left, const void *right);
void add_dir(struct List *list, char *dir) {
// If the directory doesn't exist then free it and don't think about it.
if (!dir_exists(dir)) {
free(dir);
return;
}
list_add(list, dir);
}
int basedir_has_theme(char *theme, char *dir) {
char *path = string_create("%s/%s", dir, theme);
int exist = dir_exists(path);
free(path);
return exist;
}
int dir_exists(char *path) {
struct stat sb;
return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode);
}
// Return error as string.
char *entry_handler(char *group, char *key, char *value, struct Theme *theme) {
struct List *temp = NULL;
if (STRING_EQUAL(group, "Icon Theme")) {
if (STRING_EQUAL(key, "Name")) {
theme->name = strdup(value);
} else if (STRING_EQUAL(key, "Comment")) {
theme->comment = strdup(value);
} else if (STRING_EQUAL(key, "Inherits")) {
temp = split_string(value, ","); // This only happens once
list_copy(theme->inherits, temp);
} else if (STRING_EQUAL(key, "Directories")) {
temp = split_string(value, ","); // This only happens once
list_copy(theme->directories, temp);
}
if (temp)
list_destroy(temp);
} else {
if (theme->subdirectories->length == 0)
return NULL;
struct Subdir *subdir = theme->subdirectories->data[theme->subdirectories->length-1];
if (strcmp(subdir->name, group) != 0) // Skip
return NULL;
if (STRING_EQUAL(key, "Context")) {
return NULL; // Ignore
} else if (STRING_EQUAL(key, "Type")) {
if (STRING_EQUAL(value, "Fixed")) {
subdir->type = FIXED;
} else if (STRING_EQUAL(value, "Scalable")) {
subdir->type = SCALABLE;
} else if (STRING_EQUAL(value, "Threshold")) {
subdir->type = THRESHOLD;
} else {
return "invalid value - expected 'Fixed', 'Scalable' or 'Threshold'.";
}
return NULL;
}
char *end;
long n = strtol(value, &end, 10);
if (*end != '\0')
return "invalid value - expected a number";
if (STRING_EQUAL(key, "Size")) {
subdir->size = n;
} else if (STRING_EQUAL(key, "MaxSize")) {
subdir->max_size = n;
} else if (STRING_EQUAL(key, "MinSize")) {
subdir->min_size = n;
} else if (STRING_EQUAL(key, "Threshold")) {
subdir->threshold = n;
} // Ignore scale.
}
return NULL;
}
void get_basedirs(struct List *basedirs) {
size_t len;
char *home_icons, *data_home_icons,
*dir, *path, *data_dirs;
const char *data_home, *home;
list_add(basedirs, strdup("/usr/share/pixmaps"));
// Create and add home data icons dir to array.
home = getenv("HOME");
data_home = getenv("XDG_DATA_HOME");
char *format = "%s/icons";
if (!(data_home && *data_home)) {
data_home = home;
format = "%s/.local/share/icons";
}
data_home_icons = string_create(format, data_home);
add_dir(basedirs, data_home_icons);
// Create and add data dirs to array.
data_dirs = getenv("XDG_DATA_DIRS");
if (!(data_dirs && *data_dirs))
data_dirs = "/usr/local/share:/usr/share";
data_dirs = strdup(data_dirs);
dir = strtok(data_dirs, ":");
do {
path = string_create("%s/icons", dir);
add_dir(basedirs, path);
} while ((dir = strtok(NULL, ":")));
free(data_dirs);
}
char *get_fallback_icon(struct List *themes, struct List *basedirs, char *name) {
char *basedir, *icon = NULL;
struct Theme *theme;
struct Subdir *subdir;
int i;
// Do it backwards because we only really need to do this for the icon_theme_path of an item.
for (i = basedirs->length-1; i >= 0; i--) {
basedir = basedirs->data[i];
icon = subdir_get_icon(name, basedir, "", "");
if (icon)
return icon;
}
for (i = 0; i < basedirs->length; i++) {
basedir = basedirs->data[i];
for (int n = 0; n < themes->length; n++) {
theme = themes->data[n];
for (int x = theme->subdirectories->length-1; x >= 0; x--) {
subdir = theme->subdirectories->data[x];
icon = subdir_get_icon(name, basedir, theme->dir, subdir->name);
if (icon)
return icon;
}
}
}
return NULL;
}
char *get_icon(struct List *themes, struct List *basedirs, char *name, int size, const char *theme) {
if (!themes || !basedirs || !name || !theme)
return NULL;
char *icon = NULL;
if (theme) {
icon = theme_get_icon(themes, basedirs, name, size, theme);
}
if (!icon && !(theme && STRING_EQUAL(theme, "Hicolor"))) {
icon = theme_get_icon(themes, basedirs, name, size, "Hicolor");
}
if (!icon) {
icon = get_fallback_icon(themes, basedirs, name);
}
return icon;
}
// Return error as string.
char *group_handler(char *last_group, char *new_group, struct Theme *theme) {
if (!last_group)
return new_group && STRING_EQUAL(new_group, "Icon Theme") ? NULL
: "first group must be 'Icon Theme'";
if (STRING_EQUAL(last_group, "Icon Theme")) {
if (!theme->name) {
return "missing required key 'Name'";
} else if (!theme->comment) {
return "missing required key 'Comment'";
} else if (theme->directories->length == 0) {
return "missing required key 'Directories'";
} else {
for (char *c = theme->name; *c; c++)
if (*c == ',' || *c == ' ')
return "malformed theme name";
}
} else {
if (theme->subdirectories->length == 0)
return NULL;
struct Subdir *subdir = theme->subdirectories->data[theme->subdirectories->length-1];
if (!subdir->size)
return "missing required key 'Size'";
switch (subdir->type) {
case FIXED:
subdir->max_size = subdir->min_size = subdir->size;
break;
case SCALABLE:
if (!subdir->max_size) subdir->max_size = subdir->size;
if (!subdir->min_size) subdir->min_size = subdir->size;
break;
case THRESHOLD:
subdir->max_size = subdir->size + subdir->threshold;
subdir->min_size = subdir->size - subdir->threshold;
}
}
if (!new_group)
return NULL;
if (list_cmp_find(theme->directories, new_group, cmp_id) != -1) {
struct Subdir *subdir = list_add(theme->subdirectories, ecalloc(1, sizeof(*subdir)));
subdir->name = strdup(new_group);
subdir->threshold = 2;
}
return NULL;
}
void load_themes(struct List *themes, char *basedir) {
DIR *dir;
if (!(dir = opendir(basedir)))
return;
struct dirent *entry;
while ((entry = readdir(dir))) {
if (entry->d_name[0] == '.') continue;
struct Theme *theme = read_theme_file(basedir, entry->d_name);
if (theme)
list_add(themes, theme);
}
struct Theme *theme;
struct Subdir *subdir;
for (int i = 0; i < themes->length; i++) {
theme = themes->data[i];
if (!theme) continue;
for (int n = 0; n < theme->subdirectories->length; n++) {
subdir = theme->subdirectories->data[n];
if (!subdir) continue;
}
}
closedir(dir);
}
struct Theme *read_theme_file(char *dir, char *name) {
char *path = string_create("%s/%s/index.theme", dir, name);
FILE *index_theme = fopen(path, "r");
free(path);
if (!index_theme)
return NULL;
struct Theme *theme = ecalloc(1, sizeof(*theme));
struct List *groups = list_create(0); // char*
theme->inherits = list_create(0);
theme->directories = list_create(0);
theme->subdirectories = list_create(0);
const char *error = NULL;
char *full_line = NULL;
int line_no = 0;
size_t full_len = 0;
ssize_t nread;
while ((nread = getline(&full_line, &full_len, index_theme)) > 0) {
line_no++;
char *line = full_line - 1;
while (isspace(*++line)); // Remove whitespace
if (!*line || line[0] == '#') continue; // Ignore blank or comments
int len = nread - (line - full_line);
while (isspace(line[--len])); // Remove whitespace
line[++len] = '\0';
if (line[0] == '[') { // group header
int i = 1;
for (; !iscntrl(line[i]) && line[i] != '[' && line[i] != ']'; i++);
if (i != --len || line[i] != ']') {
error = "malformed group header";
goto error;
}
line[len] = '\0';
// check for duplicate groups
if (list_cmp_find(groups, &line[1], cmp_id) != -1) {
error = "duplicate group";
goto error;
}
// Handler
char *last_group = groups->length != 0 ? groups->data[groups->length-1] : NULL;
error = group_handler(last_group, &line[1], theme);
if (error)
goto error;
list_add(groups, strdup(&line[1]));
} else {
if (!groups->length) { // If empty
error = "unexpected content before first header";
goto error;
}
int eok = 0;
for (; isalnum(line[eok]) || line[eok] == '-'; eok++);
int i = eok - 1;
while (isspace(line[++i]));
if (line[i] != '=') {
error = "malformed key-value pair";
goto error;
}
line[eok] = '\0';
char *value = &line[i];
while(isspace(*++value));
error = entry_handler(groups->data[groups->length-1], line, value, theme);
if (error)
goto error;
}
}
if (groups->length) { // Not empty
error = group_handler(groups->data[groups->length-1], NULL, theme);
} else {
error = "empty file";
}
if (!error)
theme->dir = strdup(name);
error:
if (error) {
theme_destroy(theme);
theme = NULL;
}
list_elements_destroy(groups, free);
free(full_line);
fclose(index_theme);
return theme;
}
struct List *split_string(const char *str, const char *delim) {
struct List *list = list_create(0);
char *copy = strdup(str),
*token = strtok(copy, delim);
while (token) {
list_add(list, strdup(token));
token = strtok(NULL, delim);
}
free(copy);
return list;
}
// Only support png cause I don't want to add another dependency lol.
char *subdir_get_icon(char *name, char *basedir, char *theme, char *subdir) {
// So that we don't include a / if the theme or subdir doesn't contain anything.
// So the icon path is usable.
theme = string_create(strlen(theme) ? "%s/" : "%s", theme);
subdir = string_create(strlen(subdir) ? "%s/" : "%s", subdir);
char *path = string_create("%s/%s%s%s.png", basedir, theme, subdir, name);
free(theme);
free(subdir);
if (access(path, R_OK) == 0)
return path;
free(path);
return NULL;
}
void themes_create(struct List *themes, struct List *basedirs) {
if (!themes || !basedirs)
return;
get_basedirs(basedirs); // char**
char *basedir;
for (int i = 0; i < basedirs->length; i++) {
basedir = basedirs->data[i];
load_themes(themes, basedir); // struct Theme**
}
}
void theme_destroy(struct Theme *theme) {
if (!theme) return;
free(theme->name);
free(theme->comment);
free(theme->dir);
list_elements_destroy(theme->inherits, free);
list_elements_destroy(theme->directories, free);
struct Subdir *subdir;
for (int i = 0; i < theme->subdirectories->length; i++) {
subdir = theme->subdirectories->data[i];
free(subdir->name);
free(subdir);
}
list_destroy(theme->subdirectories);
free(theme);
}
void theme_element_destroy(void *ptr) {
theme_destroy((struct Theme *)ptr);
}
void themes_destroy(struct List *themes, struct List *basedirs) {
if (!themes || !basedirs)
return;
list_elements_destroy(basedirs, free);
list_elements_destroy(themes, theme_element_destroy);
}
char *theme_get_icon(struct List *themes, struct List *basedirs, char *name, int size, const char *theme_name) {
// Find theme
int pos = 0;
struct Theme *theme;
if ((pos = list_cmp_find(themes, theme_name, theme_name_compare)) == -1) { // The desired theme isn't available.
return NULL;
} else {
theme = themes->data[pos];
}
char *icon = NULL, *basedir;
for (int i = 0; i < basedirs->length; i++) {
basedir = basedirs->data[i];
struct Subdir *subdir;
for (int n = theme->subdirectories->length-1; n >= 0; n--) {
subdir = theme->subdirectories->data[n];
if (size < subdir->min_size || size > subdir->max_size ||
!(icon = subdir_get_icon(name, basedir, theme->dir, subdir->name)))
continue;
return icon;
}
}
// find an inexact but close match.
uint smallest_error = -1; // The max num uint can store
for (int i = 0; i < basedirs->length; i++) {
basedir = basedirs->data[i];
if (!basedir_has_theme(theme->dir, basedir))
continue;
struct Subdir *subdir;
for (int n = theme->subdirectories->length-1; n >= 0; n--) {
subdir = theme->subdirectories->data[n];
uint error = (size > subdir->max_size ? size - subdir->max_size : 0)
+ (size < subdir->min_size ? subdir->min_size - size : 0);
if (error < smallest_error) {
char *test = subdir_get_icon(name, basedir, theme->dir, subdir->name);
if (!test)
continue;
icon = test;
smallest_error = error;
}
}
}
if (icon || theme->inherits->length == 0)
return icon;
// If we get here then we couldn't find an icon, so check the inherited themes. If there are any.
char *inherit;
for (int i = 0; i < theme->inherits->length; i++) {
inherit = theme->inherits->data[i];
icon = theme_get_icon(themes, basedirs, name, size, inherit);
if (icon)
break;
}
return icon;
}
int theme_name_compare(const void *left, const void *right) {
const char *theme_name = left;
const struct Theme *theme = right;
return strcmp(theme_name, theme->name);
}

29
src/icon.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef ICON_H_
#define ICON_H_
#include "tray.h"
struct Subdir {
char *name;
int size, max_size, min_size, threshold;
enum {
THRESHOLD,
SCALABLE,
FIXED
} type;
};
struct Theme {
char *name, *comment, *dir;
struct List *inherits /* char* */,
*directories /* char* */,
*subdirectories /* struct Subdir* */;
};
char *get_icon(struct List *themes, struct List *basedirs,
char *name, int size, const char *theme);
void themes_create(struct List *themes, struct List *basedirs);
void themes_destroy(struct List *themes, struct List *basedirs);
#endif // ICON_H_

442
src/item.c Normal file
View File

@ -0,0 +1,442 @@
#include "item.h"
#include "config.def.h"
#include "icon.h"
#include "cairo.h"
#include "config.h"
#include "icon.h"
#include "main.h"
#include "render.h"
#include "util.h"
#include "log.h"
#include <arpa/inet.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <systemd/sd-bus.h>
#include <wayland-util.h>
static int check_message_sender(struct Item *item, sd_bus_message *m, const char *signal);
static void get_item_property(struct Item *item, const char *property, const char *type, void *dest);
static int get_property_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error);
static int handle_new_attention_icon(sd_bus_message *m, void *userdata, sd_bus_error *ret_error);
static int handle_new_icon(sd_bus_message *m, void *userdata, sd_bus_error *ret_error);
static int handle_new_status(sd_bus_message *m, void *userdata, sd_bus_error *ret_error);
static void item_invalidate(struct Item *item);
static void item_match_signal(struct Item *item, char *signal, sd_bus_message_handler_t callback);
static cairo_surface_t *load_image(const char *path);
static cairo_surface_t *load_icon(struct Item *item, const char *theme, int size);
static int read_pixmap(sd_bus_message *m, struct Item *item, const char *property, struct wl_list *destination);
static cairo_surface_t *scale_icon(cairo_surface_t *icon, int width, int height);
const char *button_to_method(int button) {
switch (button) {
case BTN_LEFT:
return "Activate";
case BTN_MIDDLE:
return "SecondaryActivate";
case BTN_RIGHT:
return "ContextMenu";
case Scroll_Up:
return "ScrollUp";
case Scroll_Down:
return "ScrollDown";
case Scroll_Left:
return "ScrollLeft";
case Scroll_Right:
return "ScrollRight";
default:
return "nop";
}
}
int check_message_sender(struct Item *item, sd_bus_message *m, const char *signal) {
int has_well_known_names = sd_bus_creds_get_mask(sd_bus_message_get_creds(m)) & SD_BUS_CREDS_WELL_KNOWN_NAMES;
if (item->service[0] == ':' || has_well_known_names)
return 1;
return 0;
}
void get_item_property(struct Item *item, const char *property, const char *type, void *destination) {
struct ItemData *data = ecalloc(1, sizeof(*data));
data->item = item;
data->property = property;
data->type = type;
data->destination = destination;
if (sd_bus_call_method_async(item->tray->bus, &data->slot, item->service,
item->path, "org.freedesktop.DBus.Properties", "Get",
get_property_callback, data, "ss", item->interface, property) < 0) {
free(data);
return;
}
wl_list_insert(&item->item_data, &data->link);
}
int get_property_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
struct ItemData *data = userdata;
struct Item *item = data->item;
const char *property = data->property,
*type = data->type;
if (sd_bus_message_is_method_error(m, NULL))
goto cleanup;
if (sd_bus_message_enter_container(m, 'v', type) < 0)
panic("get_property_callback sd_bus_message_enter_container");
if (!type) {
if (!read_pixmap(m, item, property, (struct wl_list*)data->destination))
goto cleanup;
} else {
if (*type == 's' || *type == 'o')
free(*(char**)data->destination);
if (sd_bus_message_read(m, type, data->destination) < 0)
panic("get_property_callback sd_bus_message_read");
if (*type == 's' || *type == 'o') {
char **str = data->destination;
*str = strdup(*str);
}
}
item_invalidate(item);
cleanup:
wl_list_remove(&data->link);
free(data);
return 1;
}
int handle_new_attention_icon(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
struct ItemData *data = userdata;
struct Item *item = data->item;
struct Pixmap *pixmap, *tmp;
wl_list_for_each_safe(pixmap, tmp, &item->attention_pixmap, link) {
wl_list_remove(&pixmap->link);
free(pixmap->pixels);
free(pixmap);
}
get_item_property(item, "AttentionIconName", "s", &item->attention_icon_name);
get_item_property(item, "AttentionIconPixmap", NULL, &item->attention_pixmap);
return check_message_sender(item, m, "attention icon");
}
int handle_new_icon(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
struct ItemData *data = userdata;
struct Item *item = data->item;
struct Pixmap *pixmap, *tmp;
wl_list_for_each_safe(pixmap, tmp, &item->icon_pixmap, link) {
if (!pixmap) continue;
wl_list_remove(&pixmap->link);
free(pixmap->pixels);
free(pixmap);
}
get_item_property(item, "IconName", "s", &item->icon_name);
get_item_property(item, "IconPixmap", NULL, &item->icon_pixmap);
if (STRING_EQUAL(item->interface, "org.kde.StatusNotifierItem"))
get_item_property(item, "IconThemePath", "s", &item->icon_theme_path);
return check_message_sender(item, m, "icon");
}
int handle_new_status(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
struct ItemData *data = userdata;
struct Item *item = data->item;
int ret = check_message_sender(item, m, "status");
if (ret) {
char *status;
if (sd_bus_message_read(m, "s", &status) < 0) {
return 0;
} else {
if (item->status)
free(item->status);
item->status = strdup(status);
item_invalidate(item);
}
} else {
get_item_property(item, "Status", "s", &item->status);
}
return ret;
}
int is_passive(struct Item *item) {
if (!item) return 0;
return item->status && item->status[0] == 'P';
}
int is_ready(struct Item *item) {
if (!item) return 0;
return item->status && (item->status[0] == 'N' ?
item->attention_icon_name || !(wl_list_empty(&item->attention_pixmap)) :
item->icon_name || !(wl_list_empty(&item->icon_pixmap)));
}
struct Item *item_create(struct Tray* tray, char *id) {
if (!tray || !id) return NULL;
struct Item *item = ecalloc(1, sizeof(*item));
char *path;
item->tray = tray;
item->watcher_id = strdup(id);
item->status = item->icon_name = item->attention_icon_name =
item->menu_path = item->menu_path = NULL;
wl_list_init(&item->icon_pixmap);
wl_list_init(&item->attention_pixmap);
wl_list_init(&item->item_data);
path = strchr(id, '/');
if (!path) {
item->service = strdup(id);
item->path = strdup("/StatusNotifierItem");
item->interface = "org.freedesktop.StatusNotifierItem";
} else {
item->service = strndup(id, path - id);
item->path = strdup(path);
item->interface = "org.kde.StatusNotifierItem";
get_item_property(item, "IconThemePath", "s", (void**)&item->icon_theme_path);
}
get_item_property(item, "Status", "s", &item->status);
get_item_property(item, "IconName", "s", &item->icon_name);
get_item_property(item, "IconPixmap", NULL, &item->icon_pixmap);
get_item_property(item, "AttentionIconName", "s", &item->attention_icon_name);
get_item_property(item, "AttentionIconPixmap", NULL, &item->attention_pixmap);
get_item_property(item, "ItemIsMenu", "b", &item->is_menu);
get_item_property(item, "Menu", "o", &item->menu_path);
item_match_signal(item, "NewIcon", handle_new_icon);
item_match_signal(item, "NewAttentionIcon", handle_new_attention_icon);
item_match_signal(item, "NewStatus", handle_new_status);
return item;
}
void item_destroy(struct Item* item) {
if (!item) return;
free(item->watcher_id);
free(item->service);
free(item->path);
free(item->status);
free(item->icon_name);
free(item->attention_icon_name);
free(item->menu_path);
free(item->icon_theme_path);
cairo_surface_destroy(item->icon);
struct Pixmap *pixmap, *tmp;
wl_list_for_each_safe(pixmap, tmp, &item->icon_pixmap, link) {
wl_list_remove(&pixmap->link);
free(pixmap->pixels);
free(pixmap);
}
wl_list_for_each_safe(pixmap, tmp, &item->attention_pixmap, link) {
wl_list_remove(&pixmap->link);
free(pixmap->pixels);
free(pixmap);
}
struct ItemData *data, *temp;
wl_list_for_each_safe(data, temp, &item->item_data, link) {
wl_list_remove(&data->link);
sd_bus_slot_unref(data->slot);
free(data);
}
free(item);
}
void item_invalidate(struct Item *item) {
item->invalid = 1;
monitors_update();
}
int item_is_clicked(struct Item *item, double x, double y) {
if (!item) return 0;
return (x > item->x && y > item->y &&
x < (item->x + item->width) && y < (item->y + item->height));
}
void item_match_signal(struct Item *item, char *signal, sd_bus_message_handler_t callback) {
struct ItemData *data = ecalloc(1, sizeof(*data));
data->item = item;
if (sd_bus_match_signal_async(item->tray->bus, &data->slot,
item->service, item->path, item->interface, signal, callback, NULL, data) < 0) {
free(data);
return;
}
wl_list_insert(&item->item_data, &data->link);
}
void item_render(struct Pipeline *pipeline, cairo_t *painter, struct Item *item, int *x, int *y) {
if (is_passive(item) || !is_ready(item) || !pipeline || !painter || !item || !x || !y)
return;
int icon_size = pipeline->font->height;
item->x = *x;
item->y = *y;
item->width = icon_size+2;
item->height = pipeline->shm->height;
pipeline_set_colorscheme(pipeline, schemes[InActive_Scheme]);
pipeline_color_background(pipeline, painter);
cairo_rectangle(painter, *x, 0, item->width, item->height);
cairo_fill(painter);
if (item->invalid) {
cairo_surface_destroy(item->icon);
item->icon = load_icon(item, icon_theme, icon_size);
item->invalid = 0;
}
if (!item->icon) {
int sad_face_size = icon_size*0.8;
cairo_surface_t *item_icon = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
icon_size, icon_size);
cairo_t *sad_face = cairo_create(item_icon);
cairo_set_source_rgba(painter,
(double)schemes[Active_Scheme][0][0]/255,
(double)schemes[Active_Scheme][0][1]/255,
(double)schemes[Active_Scheme][0][2]/255,
(double)schemes[Active_Scheme][0][3]/255);
cairo_translate(sad_face, (double)icon_size/2, (double)icon_size/2);
cairo_scale(sad_face, (double)icon_size/2, (double)icon_size/2);
cairo_arc(sad_face, 0, 0, 1, 0, 7);
cairo_fill(sad_face);
cairo_set_operator(sad_face, CAIRO_OPERATOR_CLEAR);
cairo_arc(sad_face, 0.35, -0.3, 0.1, 0, 7);
cairo_fill(sad_face);
cairo_arc(sad_face, -0.35, -0.3, 0.1, 0, 7);
cairo_fill(sad_face);
cairo_arc(sad_face, 0, 0.75, 0.5, 3.71238898038469, 5.71238898038469);
cairo_set_line_width(sad_face, 0.1);
cairo_stroke(sad_face);
cairo_destroy(sad_face);
item->icon = item_icon;
}
cairo_operator_t op = cairo_get_operator(painter);
cairo_set_operator(painter, CAIRO_OPERATOR_OVER);
int actual_size = cairo_image_surface_get_height(item->icon);
icon_size = actual_size < icon_size ?
actual_size * (icon_size / actual_size) : icon_size;
cairo_surface_t *image = scale_icon(item->icon, icon_size, icon_size);
cairo_set_source_surface(painter, image, *x+1, item->height - icon_size - 1);
cairo_paint(painter);
cairo_set_operator(painter, op);
*x += item->width;
}
cairo_surface_t *load_image(const char *path) {
cairo_surface_t *image = cairo_image_surface_create_from_png(path);
return (!image || cairo_surface_status(image) != CAIRO_STATUS_SUCCESS) ? NULL : image;
}
cairo_surface_t *load_icon(struct Item *item, const char *theme, int size) {
cairo_surface_t *icon = NULL;
char *name = item->status[0] == 'N' ? item->attention_icon_name : item->icon_name;
if (name) {
struct List *search_paths = list_create(item->tray->basedirs->length+1);
list_copy(search_paths, item->tray->basedirs);
if (item->icon_theme_path)
list_add(search_paths, item->icon_theme_path);
char *path = get_icon(item->tray->themes, search_paths, name, size, theme);
list_destroy(search_paths);
if (path) {
icon = load_image(path);
free(path);
return icon;
}
}
struct wl_list *pixmaps = item->status[0] == 'N' ? &item->attention_pixmap : &item->icon_pixmap;
if (!wl_list_empty(pixmaps)) {
struct Pixmap *pixmap = NULL, *pos;
int min_error = INT_MAX;
wl_list_for_each(pos, pixmaps, link) {
int e = abs(size - pos->size);
if (e < min_error) {
pixmap = pos;
min_error = e;
}
}
icon = cairo_image_surface_create_for_data((unsigned char*)pixmap->pixels, CAIRO_FORMAT_ARGB32,
pixmap->size, pixmap->size, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pixmap->size));
}
return icon;
}
// We assume that when we get the list that it is valid.
int read_pixmap(sd_bus_message *m, struct Item *item, const char *property, struct wl_list *destination) {
if (sd_bus_message_enter_container(m, 'a', "(iiay)") < 0)
panic("read_pixmap sd_bus_message_enter_container 'a'");
if (sd_bus_message_at_end(m, 0)) // No Icon
return 0;
while(!sd_bus_message_at_end(m, 0)) {
if (sd_bus_message_enter_container(m, 'r', "iiay") < 0)
panic("read_pixmap sd_bus_message_enter_container 'r'");
int width, height;
if (sd_bus_message_read(m, "ii", &width, &height) < 0)
panic("read_pixmap sd_bus_message_read width height");
const void *pixels;
size_t npixels;
if (sd_bus_message_read_array(m, 'y', &pixels, &npixels) < 0)
panic("read_pixmap sd_bus_message_read_array pixels npixels");
if (height > 0 && width == height) { // If is valid icon
struct Pixmap *pixmap = ecalloc(1, sizeof(struct Pixmap));
pixmap->pixels = ecalloc(npixels, sizeof(uint32_t*));
pixmap->size = height;
// Covert to host byte order from network order.
for (int i = 0; i < height * width; i++)
pixmap->pixels[i] = ntohl(((uint32_t*)pixels)[i]);
wl_list_insert(destination, &pixmap->link);
}
sd_bus_message_exit_container(m);
}
if (wl_list_empty(destination))
return 0;
return 1;
}
cairo_surface_t *scale_icon(cairo_surface_t *icon, int width, int height) {
int image_height = cairo_image_surface_get_height(icon);
int image_width = cairo_image_surface_get_width(icon);
cairo_surface_t *new = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
cairo_t *painter = cairo_create(new);
cairo_scale(painter, (double)width / image_width,
(double)height / image_height);
cairo_set_source_surface(painter, icon, 0, 0);
cairo_paint(painter);
cairo_destroy(painter);
return new;
}

52
src/item.h Normal file
View File

@ -0,0 +1,52 @@
#ifndef ITEM_H_
#define ITEM_H_
#include <stdint.h>
#include <sys/types.h>
#include <wayland-util.h>
#include "cairo.h"
#include "tray.h"
#include "main.h"
#include "user.h"
struct Pixmap {
int size;
uint32_t *pixels;
struct wl_list link;
};
struct ItemData {
struct Item *item;
const char *property, *type;
void *destination;
sd_bus_slot *slot;
struct wl_list link;
};
struct ItemClick {
const struct Item *item;
const int x, y, button;
};
struct Item {
struct Tray *tray;
cairo_surface_t *icon;
int is_menu, invalid, x, y, width, height;
char *watcher_id, *service, *path, *interface,
*status, *icon_name, *attention_icon_name,
*menu_path, *icon_theme_path /* Non-standard kde property */;
struct wl_list icon_pixmap, attention_pixmap, item_data, link;
};
const char *button_to_method(int button);
int is_passive(struct Item *item);
int is_ready(struct Item *item);
struct Item *item_create(struct Tray* tray, char *id);
void item_destroy(struct Item* item);
int item_is_clicked(struct Item *item, double x, double y);
void item_render(struct Pipeline *pipeline, cairo_t *painter, struct Item *item, int *x, int *y);
#endif // ITEM_H_

View File

@ -4,6 +4,7 @@
#include "event.h" #include "event.h"
#include "log.h" #include "log.h"
#include "render.h" #include "render.h"
#include "tray.h"
#include "util.h" #include "util.h"
#include "main.h" #include "main.h"
#include "input.h" #include "input.h"
@ -29,6 +30,7 @@
static void check_global(void *global, const char *name); static void check_global(void *global, const char *name);
static void check_globals(void); static void check_globals(void);
static void cleanup(void); static void cleanup(void);
static void dbus_in(int fd, short mask, void *data);
static void display_in(int fd, short mask, void *data); static void display_in(int fd, short mask, void *data);
static void fifo_handle(const char *line); static void fifo_handle(const char *line);
static void fifo_in(int fd, short mask, void *data); static void fifo_in(int fd, short mask, void *data);
@ -80,6 +82,7 @@ static struct wl_list seats; // struct Seat*
static int self_pipe[2]; static int self_pipe[2];
struct zwlr_layer_shell_v1 *shell; struct zwlr_layer_shell_v1 *shell;
struct wl_shm *shm; struct wl_shm *shm;
static struct Tray *tray;
static int tagcount; static int tagcount;
static const struct xdg_wm_base_listener xdg_wm_base_listener = { static const struct xdg_wm_base_listener xdg_wm_base_listener = {
.ping = xdg_wm_base_ping, .ping = xdg_wm_base_ping,
@ -138,6 +141,18 @@ void cleanup(void) {
wl_display_disconnect(display); wl_display_disconnect(display);
} }
void dbus_in(int fd, short mask, void *data) {
if (mask & (POLLHUP | POLLERR)) {
running = 0;
return;
}
int return_value;
while((return_value = sd_bus_process(tray->bus, NULL)) > 0);
if (return_value < 0)
panic("dbus_in: sd_bus_process failed to process bus.");
}
void display_in(int fd, short mask, void *data) { void display_in(int fd, short mask, void *data) {
if (mask & (POLLHUP | POLLERR) || if (mask & (POLLHUP | POLLERR) ||
wl_display_dispatch(display) == -1) { wl_display_dispatch(display) == -1) {
@ -249,6 +264,7 @@ void monitor_initialize(struct Monitor *monitor) {
monitor->hotspots = list_create(1); monitor->hotspots = list_create(1);
monitor->pipeline = pipeline_create(); monitor->pipeline = pipeline_create();
monitor->bar = bar_create(monitor->hotspots, monitor->pipeline); monitor->bar = bar_create(monitor->hotspots, monitor->pipeline);
tray_register_to_monitor(tray, monitor->hotspots, monitor->pipeline);
if (!monitor->pipeline || !monitor->bar) if (!monitor->pipeline || !monitor->bar)
panic("Failed to create a pipline or bar for a monitor"); panic("Failed to create a pipline or bar for a monitor");
monitor_update(monitor); monitor_update(monitor);
@ -392,6 +408,8 @@ void setup(void) {
wl_list_init(&seats); wl_list_init(&seats);
wl_list_init(&monitors); wl_list_init(&monitors);
tray = tray_create();
struct wl_registry *registry = wl_display_get_registry(display); struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener, NULL); wl_registry_add_listener(registry, &registry_listener, NULL);
wl_display_roundtrip(display); wl_display_roundtrip(display);
@ -417,6 +435,7 @@ void setup(void) {
events_add(events, display_fd, POLLIN, NULL, display_in); events_add(events, display_fd, POLLIN, NULL, display_in);
events_add(events, self_pipe[0], POLLIN, NULL, pipe_in); events_add(events, self_pipe[0], POLLIN, NULL, pipe_in);
events_add(events, fifo_fd, POLLIN, NULL, fifo_in); events_add(events, fifo_fd, POLLIN, NULL, fifo_in);
events_add(events, tray->bus_fd, POLLIN, NULL, dbus_in);
} }
void sigaction_handler(int _) { void sigaction_handler(int _) {

169
src/tray.c Normal file
View File

@ -0,0 +1,169 @@
#include "log.h"
#include "main.h"
#include "icon.h"
#include "item.h"
#include "render.h"
#include "tray.h"
#include "user.h"
#include "watcher.h"
#include "host.h"
#include "util.h"
#include "input.h"
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <wayland-util.h>
#include <linux/input-event-codes.h>
// Thanks to the ianyfan from swaybar for the tray code,
// I don't have a clue of how to use sd-bus, so that code was useful.
static int handle_lost_watcher(sd_bus_message *m, void *userdata, sd_bus_error *ret_error);
static void tray_click(struct Monitor *monitor, void *data, uint32_t button, double x, double y);
static void tray_render(struct Pipeline *pipeline, void *data, cairo_t *painter, int *x, int *y);
static int tray_width(struct Pipeline *pipeline, void *data, unsigned int future_widths);
static void tray_bounds(void *data, double *x, double *y, double *width, double *height);
static const struct PipelineListener tray_pipeline_listener = { .render = tray_render, .width = tray_width, };
static const struct HotspotListener tray_hotspot_listener = { .click = tray_click, .bounds = tray_bounds };
int cmp_id(const void *left, const void *right) {
if (!left || !right)
return -1;
return strcmp(left, right);
}
int handle_lost_watcher(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
char *service, *old_owner, *new_owner;
if (sd_bus_message_read(m, "sss", &service, &old_owner, &new_owner) < 0)
panic("handle_lost_watcher sd_bus_message_read");
if (!*new_owner) {
struct Tray *tray = userdata;
if (STRING_EQUAL(service, "org.freedesktop.StatusNotifierWatcher")) {
tray->xdg_watcher = watcher_create("freedesktop", tray->bus);
} else if (STRING_EQUAL(service, "org.kde.StatusNotifierWatcher")) {
tray->kde_watcher = watcher_create("kde", tray->bus);
}
}
return 0;
}
struct Tray *tray_create(void) {
struct Tray *tray;
sd_bus *bus;
if (sd_bus_open_user(&bus) < 0)
panic("sd_bus_open_user");
tray = ecalloc(1, sizeof(*tray));
tray->bus = bus;
tray->bus_fd = sd_bus_get_fd(tray->bus);
tray->themes = list_create(0);
tray->basedirs = list_create(0);
wl_list_init(&tray->items);
tray->xdg_watcher = watcher_create("freedesktop", bus);
tray->kde_watcher = watcher_create("kde", bus);
if (sd_bus_match_signal(bus, NULL, "org.freedesktop.DBus",
"/org/freedesktop/DBus", "org.freedesktop.DBus",
"NameOwnerChanged", handle_lost_watcher, tray) < 0)
panic("Failed to subscribe to NameOwnerChange");
tray->xdg_host = host_create("freedesktop", tray);
tray->kde_host = host_create("kde", tray);
themes_create(tray->themes, tray->basedirs);
return tray;
}
void tray_destroy(struct Tray *tray) {
if (!tray) return;
host_destroy(tray->xdg_host);
host_destroy(tray->kde_host);
watcher_destroy(tray->kde_watcher);
watcher_destroy(tray->xdg_watcher);
struct Item *item, *tmp;
wl_list_for_each_safe(item, tmp, &tray->items, link) {
wl_list_remove(&item->link);
item_destroy(item);
}
themes_destroy(tray->themes, tray->basedirs);
free(tray);
}
void tray_click(struct Monitor *monitor, void *data, uint32_t button, double x, double y) {
struct Tray *tray = data;
struct Item *item = NULL;
wl_list_for_each(item, &tray->items, link) {
if (item_is_clicked(item, x, y))
break;
}
if (!item)
return;
union Arg arg;
struct ItemClick click = { item, x, y, button };
arg.v = &click;
const struct Binding *binding;
for (int i = 0; i < LENGTH(bindings); i++) {
binding = &bindings[i];
if (binding->button != button || binding->clicked != Click_Systray)
continue;
binding->callback(monitor, binding->bypass ? &binding->arg : &arg);
}
}
void tray_register_to_monitor(struct Tray *tray, struct List *hotspots, struct Pipeline *pipeline) {
if (!tray || !hotspots || !pipeline) return;
tray->pipeline = pipeline;
pipeline_add(pipeline, &tray_pipeline_listener, tray);
struct Hotspot *hotspot = list_add(hotspots, ecalloc(1, sizeof(*hotspot)));
hotspot->listener = &tray_hotspot_listener;
hotspot->data = tray;
}
void tray_render(struct Pipeline *pipeline, void *data, cairo_t *painter, int *x, int *y) {
struct Tray *tray = data;
tray->x = *x;
tray->y = *y;
struct Item *item;
wl_list_for_each(item, &tray->items, link)
item_render(pipeline, painter, item, x, y);
}
int tray_width(struct Pipeline *pipeline, void *data, unsigned int future_widths) {
struct Tray *tray = data;
int amount = 0;
struct Item *item;
wl_list_for_each(item, &tray->items, link) {
if (!is_passive(item) && is_ready(item))
amount++;
}
return (pipeline->font->height + 2) * amount;
}
static void tray_bounds(void *data, double *x, double *y, double *width, double *height) {
struct Tray *tray = data;
*x = tray->x;
*y = tray->y;
*width = tray_width(tray->pipeline, tray, 0);
*height = tray->pipeline->shm->height;
}

41
src/tray.h Normal file
View File

@ -0,0 +1,41 @@
#ifndef TRAY_H_
#define TRAY_H_
#include <wayland-util.h>
#include <cairo.h>
#include "render.h"
#include "lib.h"
#if SYSTEMD
#include <systemd/sd-bus.h>
#elif ELOGIND
#include <elogind/sd-bus.h>
#elif BASU
#include <basu/sd-bus.h>
#else
#error "No dbus library to use"
#endif
static const char *watcher_path = "/StatusNotifierWatcher";
struct Tray {
sd_bus *bus;
int bus_fd, x, y;
struct Pipeline *pipeline;
struct Watcher *xdg_watcher, *kde_watcher;
struct Host *xdg_host, *kde_host;
struct wl_list items; // struct Item*
struct List *themes /* struct Theme* */,
*basedirs /* char* */;
};
int cmp_id(const void *left, const void *right);
struct Tray *tray_create(void);
void tray_destroy(struct Tray *tray);
void tray_register_to_monitor(struct Tray *tray, struct List *hotspots, struct Pipeline *pipeline);
#endif // TRAY_H_

View File

@ -1,5 +1,7 @@
#include "user.h" #include "user.h"
#include "util.h" #include "util.h"
#include "item.h"
#include "log.h"
#include <unistd.h> #include <unistd.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -39,3 +41,23 @@ void toggle_view(struct Monitor *monitor, const union Arg *arg) {
void view(struct Monitor *monitor, const union Arg *arg) { void view(struct Monitor *monitor, const union Arg *arg) {
zdwl_ipc_output_v2_set_tags(monitor->dwl_output, 1<<arg->ui, 1); zdwl_ipc_output_v2_set_tags(monitor->dwl_output, 1<<arg->ui, 1);
} }
void systray(struct Monitor *monitor, const union Arg *arg) {
const struct ItemClick *click = arg->v;
const struct Item *item = click->item;
const char *method = button_to_method(click->button);
if (STRING_EQUAL(method, "nop"))
return;
if (STRINGN_EQUAL(method, "Scroll", strlen("Scroll"))) {
char direction = method[strlen("Scroll")];
char *orientation = (direction == 'U' || direction == 'D') ? "vertical" : "horizontal";
int sign = (direction == 'U' || direction == 'L') ? -1 : 1;
sd_bus_call_method_async(item->tray->bus, NULL, item->service, item->path,
item->interface, "Scroll", NULL, NULL, "is", sign, orientation);
} else {
sd_bus_call_method_async(item->tray->bus, NULL, item->service, item->path,
item->interface, method, NULL, NULL, "ii", click->x, click->y);
}
}

View File

@ -11,6 +11,7 @@ enum Clicked {
Click_Layout, Click_Layout,
Click_Title, Click_Title,
Click_Status, Click_Status,
Click_Systray,
}; };
enum ColorScheme { enum ColorScheme {
@ -52,5 +53,6 @@ void spawn(struct Monitor *monitor, const union Arg *arg);
void tag(struct Monitor *monitor, const union Arg *arg); void tag(struct Monitor *monitor, const union Arg *arg);
void toggle_view(struct Monitor *monitor, const union Arg *arg); void toggle_view(struct Monitor *monitor, const union Arg *arg);
void view(struct Monitor *monitor, const union Arg *arg); void view(struct Monitor *monitor, const union Arg *arg);
void systray(struct Monitor *monitor, const union Arg *arg);
#endif // USER_H_ #endif // USER_H_

181
src/watcher.c Normal file
View File

@ -0,0 +1,181 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "log.h"
#include "tray.h"
#include "watcher.h"
#include "util.h"
#include "main.h"
static int get_registered_items(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error);
static int handle_lost_service(sd_bus_message *m, void *userdata, sd_bus_error *ret_error);
static int is_registered_host(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error);
static int register_host(sd_bus_message *m, void *userdata, sd_bus_error *ret_error);
static int register_item(sd_bus_message *m, void *userdata, sd_bus_error *ret_error);
static int watcher_using_freedesktop(struct Watcher *watcher);
static const sd_bus_vtable watcher_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("RegisterStatusNotifierItem", "s", "", register_item,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("RegisterStatusNotifierHost", "s", "", register_host,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_PROPERTY("RegisteredStatusNotifierItems", "as", get_registered_items,
0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("IsStatusNotifierHostRegistered", "b", is_registered_host,
0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("ProtocolVersion", "i", NULL, offsetof(struct 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,
};
int get_registered_items(sd_bus *bus, const char *path, const char *interface,
const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) {
struct Watcher *watcher = userdata;
list_add(watcher->items, NULL); // Must be NULL terminated array.
int ret = sd_bus_message_append_strv(reply, (char**)watcher->items->data);
list_remove(watcher->items, watcher->items->length - 1);
return ret;
}
int handle_lost_service(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
char *service, *old_owner, *new_owner;
if (sd_bus_message_read(m, "sss", &service, &new_owner, &new_owner) < 0)
panic("handle_lost_service sd_bus_message_read");
struct Watcher *watcher = userdata;
if (*new_owner)
return 0;
int i;
char *id;
for (i = 0; i < watcher->items->length; i++) { // Check to see if item.
id = watcher->items->data[i];
int same_service = (watcher_using_freedesktop(watcher) ? strcmp(id, service) :
strncmp(id, service, strlen(service))) == 0;
if (!same_service)
continue;
list_remove(watcher->items, i--);
sd_bus_emit_signal(watcher->bus, watcher_path, watcher->interface,
"StatusNotifierItemUnregistered", "s", id);
free(id);
if (watcher_using_freedesktop(watcher))
break;
}
if ((i = list_cmp_find(watcher->hosts, service, cmp_id)) != -1) // If not item then host.
free(list_remove(watcher->hosts, i));
return 0;
}
int is_registered_host(sd_bus *bus, const char *path, const char *interface,
const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) {
struct Watcher *watcher = userdata;
int has = watcher->hosts->length > 0;
return sd_bus_message_append_basic(reply, 'b', &has);
}
int register_host(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
char *service;
if (sd_bus_message_read(m, "s", &service) < 0)
panic("register_host sd_bus_message_read");
struct Watcher *watcher = userdata;
if (list_cmp_find(watcher->hosts, service, cmp_id) == -1) {
list_add(watcher->hosts, strdup(service));
sd_bus_emit_signal(watcher->bus, watcher_path, watcher->interface,
"StatusNotifierHostRegistered", NULL);
}
return sd_bus_reply_method_return(m, "");
}
int register_item(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
char *service_path, *id;
if (sd_bus_message_read(m, "s", &service_path) < 0)
panic("register_item sd_bus_message_read");
struct Watcher *watcher = userdata;
if (watcher_using_freedesktop(watcher)) {
id = strdup(service_path);
} else {
const char *service, *path;
if (service_path[0] == '/') {
service = sd_bus_message_get_sender(m);
path = service_path;
} else {
service = service_path;
path = "/StatusNotifierItem";
}
id = string_create("%s%s", service, path);
}
if (list_cmp_find(watcher->items, id, cmp_id) != -1) {
free(id);
} else {
list_add(watcher->items, id);
sd_bus_emit_signal(watcher->bus, watcher_path, watcher->interface,
"StatusNotifierItemRegistered", "s", id);
}
return sd_bus_reply_method_return(m, "");
}
struct Watcher *watcher_create(char *protocol, sd_bus *bus) {
if (!protocol || !bus)
return NULL;
struct Watcher *watcher = ecalloc(1, sizeof(*watcher));
sd_bus_slot *signal = NULL, *vtable = NULL;
watcher->interface = string_create("org.%s.StatusNotifierWatcher", protocol);
if (sd_bus_add_object_vtable(bus, &vtable, watcher_path,
watcher->interface, watcher_vtable, watcher) < 0)
goto error;
if (sd_bus_match_signal(bus, &signal, "org.freedesktop.DBus", "/org/freedesktop/DBus",
"org.freedesktop.DBus", "NameOwnerChanged", handle_lost_service, watcher) < 0)
goto error;
if (sd_bus_request_name(bus, watcher->interface, 0) < 0)
goto error;
sd_bus_slot_set_floating(signal, 0);
sd_bus_slot_set_floating(vtable, 0);
watcher->bus = bus;
watcher->hosts = list_create(0);
watcher->items = list_create(0);
return watcher;
error:
sd_bus_slot_unref(vtable);
sd_bus_slot_unref(signal);
watcher_destroy(watcher);
panic("Creating %s watcher failed dying.", protocol);
return NULL;
}
void watcher_destroy(struct Watcher *watcher) {
if (!watcher) return;
list_elements_destroy(watcher->hosts, free);
list_elements_destroy(watcher->items, free);
free(watcher->interface);
free(watcher);
}
int watcher_using_freedesktop(struct Watcher *watcher) {
return watcher->interface[strlen("org.")] == 'f';
}

18
src/watcher.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef WATCHER_H_
#define WATCHER_H_
#include "tray.h"
// In case there isn't already a watcher available.
struct Watcher {
char *interface;
sd_bus *bus;
int version;
struct List *hosts, *items; /* char* */
};
struct Watcher *watcher_create(char *protocol, sd_bus *bus);
void watcher_destroy(struct Watcher *watcher);
#endif // WATCHER_H_