parent
fcc7e9be56
commit
1a1dbf4bc7
|
|
@ -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.*
|
||||||
|
|
|
||||||
18
Makefile
18
Makefile
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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_
|
||||||
|
|
|
||||||
|
|
@ -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, ®ister_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;
|
||||||
|
}
|
||||||
|
|
@ -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_
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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_
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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_
|
||||||
19
src/main.c
19
src/main.c
|
|
@ -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, ®istry_listener, NULL);
|
wl_registry_add_listener(registry, ®istry_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 _) {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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_
|
||||||
22
src/user.c
22
src/user.c
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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_
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
}
|
||||||
|
|
@ -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_
|
||||||
Loading…
Reference in New Issue