tmisu

Notification to stdout daemon
git clone https://git.inz.fi/tmisu/
Log | Files | Refs | README | LICENSE

commit 6f4bcef9f6d3298dfe75bfc2a56bc68b963d6376
parent 8eb946dae0e2f98d3850d89e1bb535640e8c3266
Author: Santtu Lakkala <inz@inz.fi>
Date:   Tue, 27 Apr 2021 20:59:13 +0300

Rewrite and rename

Diffstat:
MMakefile | 19++++++++++++-------
MREADME.md | 162++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/output.c | 438+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/output.h | 35++++++++++-------------------------
Dsrc/tiramisu.c | 138-------------------------------------------------------------------------------
Dsrc/tiramisu.h | 54------------------------------------------------------
Asrc/tmisu.c | 166+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/tmisu.h | 44++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 540 insertions(+), 516 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,21 +1,26 @@ -TARGET = tiramisu -SRC := src/tiramisu.c src/output.c +TARGET = tmisu +SRC := src/tmisu.c src/output.c +HEDRS := src/tmisu.h src/output.h PREFIX ?= /usr/local -CFLAGS += -Wall -Wno-unused-value -IFLAGS = $(shell pkg-config --cflags glib-2.0 gio-2.0) -LFLAGS = $(shell pkg-config --libs glib-2.0 gio-2.0) +CFLAGS += -W -Wall -std=c99 +IFLAGS = $(shell pkg-config --cflags dbus-1) +LFLAGS = $(shell pkg-config --libs dbus-1) +OBJ := $(patsubst %.c,%.o,$(SRC)) all: $(TARGET) +%.o: %.c $(HDRS) + $(CC) -c $< $(CFLAGS) $(IFLAGS) -o $@ + $(TARGET): $(OBJ) - $(CC) $(CFLAGS) $(IFLAGS) $(SRC) $(LFLAGS) $(LDFLAGS) -o $(TARGET) + $(CC) $(CFLAGS) $(IFLAGS) $(OBJ) $(LFLAGS) $(LDFLAGS) -o $(TARGET) install: $(TARGET) mkdir -p $(DESTDIR)$(PREFIX)/bin install $(TARGET) $(DESTDIR)$(PREFIX)/bin/$(TARGET) clean: - $(RM) ./tiramisu + $(RM) -f $(OBJ) $(TARGET) diff --git a/README.md b/README.md @@ -1,81 +1,81 @@ -# Desktop notifications, the UNIX way - -tiramisu is a notification daemon based on dunst that outputs notifications -to STDOUT in order to allow the user to process notifications any way they prefer. - -<div align="center"><img src="https://github.com/Sweets/tiramisu/blob/master/example.gif"/></div> - -# Why? - -By allowing users to determine what is done with notifications, there is infinitely more possibilities presented -to the end-user than a simple notification daemon that displays a block of text on the screen and nothing more. - -Users could have notifications display in a pre-existing bar, make a control panel of some sort that shows -notifications, push notifications to their phone if their computer has been idle for an amount of time, -make notifications more accessible with text-to-speech, and so much more. - -# Projects Using tiramisu - -- [polynotifications by alnj](https://github.com/alnj/polynotifications) - -# Installation - -Clone the repository and build the project. Then move tiramisu to a location that is specified in `$PATH`. - -``` -$ git clone https://github.com/Sweets/tiramisu -$ cd ./tiramisu -$ make - -# cp ./tiramisu /usr/bin/tiramisu -# chmod +x /usr/bin/tiramisu -``` - -#### Note that the use of a pound symbol (#) denotes escalated privileges. -#### On most Linux systems this can be done with the usage of `sudo` - -# Usage - -Redirecting output of tiramisu to the input of another program is the ideal methodology to using -tiramisu. - -``` -tiramisu | your-application -``` - -By default tiramisu outputs notifications in a psuedo-key-value line format. -You can supply the `-j` flag to output notification data in JSON format. - -### Example of default output - -``` -$ tiramisu -``` - -``` -app_name: evolution-mail-notification -app_icon: evolution -replaces_id: 0 -timeout: -1 -hints: - desktop-entry: org.gnome.Evolution - urgency: 1 -actions: - Show INBOX: default -summary: New email in Evolution -body: You have received 4 new messages. -``` - -### Example of JSON output - -``` -$ tiramisu -j -``` - -``` -{"app_name": "evolution-mail-notification", "app_icon": "evolution", "replaces_id": 0, "timeout": -1, "hints": {"desktop-entry": "org.gnome.Evolution", "urgency": 1}, "actions": {"Show INBOX": "default"}, "summary": "New email in Evolution", "body": "You have received 4 new messages."} -``` - -#### Note that only a single process can claim the org.freedesktop.Notifications name at a given time, so any other running notification daemon must be killed before running tiramisu. - -##### Thanks to [BanchouBoo](https://github.com/BanchouBoo) for helping test tiramisu and providing the gif of it. +# Desktop notifications, the UNIX way + +tmisu is a notification daemon based on tiramisu that outputs notifications +to STDOUT in order to allow the user to process notifications any way they prefer. + +tmisu is basically a rewrite without GLib, and using libdbus instead. + +# Why? + +By allowing users to determine what is done with notifications, there is infinitely more possibilities presented +to the end-user than a simple notification daemon that displays a block of text on the screen and nothing more. + +Users could have notifications display in a pre-existing bar, make a control panel of some sort that shows +notifications, push notifications to their phone if their computer has been idle for an amount of time, +make notifications more accessible with text-to-speech, and so much more. + +# Projects Using tiramisu + +- [polynotifications by alnj](https://github.com/alnj/polynotifications) + +# Installation + +Clone the repository and build the project. Then move tiramisu to a location that is specified in `$PATH`. + +``` +$ git clone https://github.com/Sweets/tiramisu +$ cd ./tiramisu +$ make + +# cp ./tiramisu /usr/bin/tiramisu +# chmod +x /usr/bin/tiramisu +``` + +#### Note that the use of a pound symbol (#) denotes escalated privileges. +#### On most Linux systems this can be done with the usage of `sudo` + +# Usage + +Redirecting output of tiramisu to the input of another program is the ideal methodology to using +tiramisu. + +``` +tmisu | your-application +``` + +By default tiramisu outputs notifications in a psuedo-key-value line format. +You can supply the `-j` flag to output notification data in JSON format. + +### Example of default output + +``` +$ tmisu +``` + +``` +app_name: evolution-mail-notification +app_icon: evolution +replaces_id: 0 +timeout: -1 +hints: + desktop-entry: org.gnome.Evolution + urgency: 1 +actions: + Show INBOX: default +summary: New email in Evolution +body: You have received 4 new messages. +``` + +### Example of JSON output + +``` +$ tiramisu -j +``` + +``` +{"app_name": "evolution-mail-notification", "app_icon": "evolution", "replaces_id": 0, "timeout": -1, "hints": {"desktop-entry": "org.gnome.Evolution", "urgency": 1}, "actions": {"Show INBOX": "default"}, "summary": "New email in Evolution", "body": "You have received 4 new messages."} +``` + +#### Note that only a single process can claim the org.freedesktop.Notifications name at a given time, so any other running notification daemon must be killed before running tiramisu. + +##### Thanks to [BanchouBoo](https://github.com/BanchouBoo) for helping test tiramisu and providing the gif of it. diff --git a/src/output.c b/src/output.c @@ -1,211 +1,227 @@ -#include <stdio.h> -#include <glib.h> - -#include "tiramisu.h" -#include "output.h" - -char *sanitize(const char *string) { - /* allocating double the size of the original string should be enough */ - char *out = calloc(strlen(string) * 2 + 1, 1); - - while (*string) { - if (*string == '"') - strcat(out, "\\\""); - else if (*string == '\n') - strcat(out, "\\n"); - else - out[strlen(out)] = *string; - string++; - } - - return out; -} - -void output_notification(GVariant *parameters) { - GVariantIter iterator; - gchar *app_name; - guint32 replaces_id; - gchar *app_icon; - gchar *summary; - gchar *body; - gchar **actions; - GVariant *hints; - gint32 timeout; - - g_variant_iter_init(&iterator, parameters); - g_variant_iter_next(&iterator, "s", &app_name); - g_variant_iter_next(&iterator, "u", &replaces_id); - g_variant_iter_next(&iterator, "s", &app_icon); - g_variant_iter_next(&iterator, "s", &summary); - g_variant_iter_next(&iterator, "s", &body); - g_variant_iter_next(&iterator, "^a&s", &actions); - g_variant_iter_next(&iterator, "@a{sv}", &hints); - g_variant_iter_next(&iterator, "i", &timeout); - - char *app_name_sanitized = sanitize(app_name); - char *app_icon_sanitized = sanitize(app_icon); - char *summary_sanitized = sanitize(summary); - char *body_sanitized = sanitize(body); - - if (print_json) - json_output(app_name_sanitized, app_icon_sanitized, replaces_id, - timeout, hints, actions, summary_sanitized, body_sanitized); - else - default_output(app_name_sanitized, app_icon_sanitized, replaces_id, - timeout, hints, actions, summary_sanitized, body_sanitized); - - free(app_name_sanitized); - free(app_icon_sanitized); - free(app_name); - free(app_icon); - - free(actions); - - free(summary); - free(body); - free(summary_sanitized); - free(body_sanitized); - - fflush(stdout); -} - -void hints_output_iterator(GVariant *hints, const char *str_format, - const char *int_format, const char *uint_format, - const char *double_format, const char *boolean_format, - const char *byte_format, const char *err_format, - const char *element_delimiter) { - - GVariantIter iterator; - gchar *key; - GVariant *value; - - unsigned int index = 0; - char *value_sanitized; - - g_variant_iter_init(&iterator, hints); - while (g_variant_iter_loop(&iterator, "{sv}", &key, NULL)) { - if (index) - printf(element_delimiter); - /* Strings */ - if ((value = g_variant_lookup_value(hints, key, GT_STRING))) { - value_sanitized = sanitize(g_variant_get_string(value, NULL)); - printf(str_format, key, value_sanitized); - free(value_sanitized); - } - /* Integers */ - else if ((value = g_variant_lookup_value(hints, key, GT_INT16))) - printf(int_format, key, g_variant_get_int16(value)); - else if ((value = g_variant_lookup_value(hints, key, GT_INT32))) - printf(int_format, key, g_variant_get_int32(value)); - else if ((value = g_variant_lookup_value(hints, key, GT_INT64))) - printf(int_format, key, g_variant_get_int64(value)); - /* Unsigned integers */ - else if ((value = g_variant_lookup_value(hints, key, GT_UINT16))) - printf(uint_format, key, g_variant_get_uint16(value)); - else if ((value = g_variant_lookup_value(hints, key, GT_UINT32))) - printf(uint_format, key, g_variant_get_uint32(value)); - else if ((value = g_variant_lookup_value(hints, key, GT_UINT64))) - printf(uint_format, key, g_variant_get_uint64(value)); - /* Doubles */ - else if ((value = g_variant_lookup_value(hints, key, GT_DOUBLE))) - printf(double_format, key, g_variant_get_double(value)); - /* Bytes */ - else if ((value = g_variant_lookup_value(hints, key, GT_BYTE))) - printf(byte_format, key, g_variant_get_byte(value)); - /* Booleans */ - else if ((value = g_variant_lookup_value(hints, key, GT_BOOL))) - printf(boolean_format, key, g_variant_get_boolean(value)); - else { - // value is of unknown type - printf(err_format, key); - index++; - continue; - } - - g_variant_unref(value); - index++; - } - - g_variant_unref(hints); -} - -void default_output(gchar *app_name, gchar *app_icon, guint32 replaces_id, - gint32 timeout, GVariant *hints, gchar **actions, gchar *summary, - gchar *body) { - - printf("app_name: %s%sapp_icon: %s%sreplaces_id: %u%stimeout: %d%s", - app_name, delimiter, app_icon, delimiter, replaces_id, - delimiter, timeout, delimiter); - - printf("hints:%s", delimiter); - - char *str_format = calloc(64, sizeof(char)), // should be enough - *int_format = calloc(64, sizeof(char)), - *uint_format = calloc(64, sizeof(char)), - *double_format = calloc(64, sizeof(char)), - *boolean_format = calloc(64, sizeof(char)), - *byte_format = calloc(64, sizeof(char)), - *err_format = calloc(64, sizeof(char)); - - sprintf(str_format, "\t%%s: %%s%s", delimiter); - sprintf(int_format, "\t%%s: %%d%s", delimiter); - sprintf(uint_format, "\t%%s: %%u%s", delimiter); - sprintf(double_format, "\t%%s: %%f%s", delimiter); - sprintf(boolean_format, "\t%%s: %%x%s", delimiter); - sprintf(byte_format, "\t%%s: %%d%s", delimiter); - sprintf(err_format, "\t%%s:%s", delimiter); - - hints_output_iterator(hints, - str_format, int_format, uint_format, double_format, boolean_format, - byte_format, err_format, ""); - - free(str_format); - free(int_format); - free(uint_format); - free(double_format); - free(boolean_format); - free(byte_format); - free(err_format); - - printf("actions:%s", delimiter); - - unsigned int index = 0; - while (actions[index] && actions[index + 1]) { - printf("\t%s: %s%s", actions[index + 1], actions[index], delimiter); - index += 2; - } - - printf("summary: %s%sbody: %s%s", - summary, delimiter, body, delimiter); - -} - -void json_output(gchar *app_name, gchar *app_icon, guint32 replaces_id, - gint32 timeout, GVariant *hints, gchar **actions, gchar *summary, - gchar *body) { - - printf("{" - "\"app_name\": \"%s\", " - "\"app_icon\": \"%s\", " - "\"replaces_id\": %u, " - "\"timeout\": %d, ", - app_name, app_icon, replaces_id, timeout); - - printf("\"hints\": {"); - hints_output_iterator(hints, "\"%s\": \"%s\"", "\"%s\": %d", "\"%s\": %u", - "\"%s\": %f", "\"%s\": %x", "\"%s\": %d", "\"%s\": \"\"", ", "); - printf("}, \"actions\": {"); - - unsigned int index = 0; - while (actions[index] && actions[index + 1]) { - if (index) - printf(", "); - printf("\"%s\": \"%s\"", actions[index + 1], actions[index]); - index += 2; - } - - printf("}, "); - printf("\"summary\": \"%s\", " - "\"body\": \"%s\"}\n", summary, body); - -} +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> +#include <string.h> + +#include "output.h" +#include "tmisu.h" + +static void print_sanitized(const char *string, const char *escape) { + while (*string) { + size_t len = strcspn(string, escape); + printf("%.*s", (int)len, string); + string += len; + while (*string && strchr(escape, *string)) { + if (*string == '\n') + printf("\\n"); + else + printf("\\%c", *string); + string++; + } + } +} + +static void hints_output_iterator(DBusMessageIter *hints, const char *key_prefix, const char *key_suffix, const char *str_prefix, const char *str_suffix, const char *escape, const char *delimiter) +{ + for (; dbus_message_iter_get_arg_type(hints) != DBUS_TYPE_INVALID; dbus_message_iter_next(hints)) { + DBusMessageIter dictentry; + DBusMessageIter variant; + DBusBasicValue value; + const char *key; + + dbus_message_iter_recurse(hints, &dictentry); + dbus_message_iter_get_basic(&dictentry, &key); + dbus_message_iter_next(&dictentry); + dbus_message_iter_recurse(&dictentry, &variant); + + printf("%s", key_prefix); + print_sanitized(key, escape); + printf("%s", key_suffix); + + if (!dbus_type_is_basic(dbus_message_iter_get_arg_type(&variant))) { + printf("null%s", delimiter); + continue; + } + + dbus_message_iter_get_basic(&variant, &value); + + switch (dbus_message_iter_get_arg_type(&variant)) { + case DBUS_TYPE_BYTE: + printf("%" PRIu8 "%s", value.byt, delimiter); + break; + case DBUS_TYPE_BOOLEAN: + printf("%s%s", value.bool_val ? "true" : "false", delimiter); + break; + case DBUS_TYPE_INT16: + printf("%" PRId16 "%s", value.i16, delimiter); + break; + case DBUS_TYPE_UINT16: + printf("%" PRIu16 "%s", value.u16, delimiter); + break; + case DBUS_TYPE_INT32: + printf("%" PRId32 "%s", value.i32, delimiter); + break; + case DBUS_TYPE_UINT32: + printf("%" PRIu32 "%s", value.u32, delimiter); + break; + case DBUS_TYPE_INT64: + printf("%" PRId64 "%s", value.i64, delimiter); + break; + case DBUS_TYPE_UINT64: + printf("%" PRIu64 "%s", value.u64, delimiter); + break; + case DBUS_TYPE_DOUBLE: + printf("%lf%s", value.dbl, delimiter); + break; + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + dbus_message_iter_get_basic(&variant, &value); + printf("%s", str_prefix); + print_sanitized(value.str, escape); + printf("%s%s", str_suffix, delimiter); + continue; + break; + default: + printf("null%s", delimiter); + continue; + } + } +} + +static void default_output(const char *app_name, const char *app_icon, dbus_uint32_t id, dbus_uint32_t replaces_id, + dbus_int32_t timeout, DBusMessageIter *hints, DBusMessageIter *actions, const char *summary, + const char *body, const char *delimiter) +{ + (void)id; + printf("app_name: "); + print_sanitized(app_name, "\n\\"); + printf("%sapp_icon: ", delimiter); + print_sanitized(app_icon, "\n\\"); + printf("%sreplaces_id: %" PRIu32 "%stimeout: %" PRId32 "%s", delimiter, + replaces_id, delimiter, + timeout, delimiter); + + printf("hints:%s", delimiter); + hints_output_iterator(hints, "\t", ": ", "", "", "\n\\", delimiter); + + printf("actions:%s", delimiter); + while (dbus_message_iter_get_arg_type(actions) != DBUS_TYPE_INVALID) { + if (!dbus_message_iter_has_next(actions)) + break; + const char *key; + const char *value; + + dbus_message_iter_get_basic(actions, &key); + dbus_message_iter_next(actions); + dbus_message_iter_get_basic(actions, &value); + dbus_message_iter_next(actions); + + printf("\t"); + print_sanitized(key, "\n\\"); + printf(": "); + print_sanitized(value, "\n\\"); + printf("%s", delimiter); + } + + printf("body: "); + print_sanitized(body, "\n\\"); + printf("%ssummary: ", delimiter); + print_sanitized(summary, "\n\\"); + printf("%s", delimiter); +} + +static void json_output(const char *app_name, const char *app_icon, dbus_uint32_t id, dbus_uint32_t replaces_id, + dbus_int32_t timeout, DBusMessageIter *hints, DBusMessageIter *actions, const char *summary, + const char *body, const char *delimiter) { + + printf("{" + "\"id\": %" PRIu32 ", " + "\"app_name\": \"", id); + print_sanitized(app_name, "\n\\\""); + printf("\", " + "\"app_icon\": \""); + print_sanitized(app_icon, "\n\\\""); + printf("\", " + "\"replaces_id\": %" PRIu32 ", " + "\"timeout\": %" PRId32 ", ", + replaces_id, timeout); + + printf("\"hints\": {"); + hints_output_iterator(hints, "\"", "\": ", "\"", "\"", "\n\\\"", ", "); + printf("}, \"actions\": {"); + + while (dbus_message_iter_get_arg_type(actions) != DBUS_TYPE_INVALID) { + if (!dbus_message_iter_has_next(actions)) + break; + const char *key; + const char *value; + + dbus_message_iter_get_basic(actions, &key); + dbus_message_iter_next(actions); + dbus_message_iter_get_basic(actions, &value); + dbus_message_iter_next(actions); + + printf("\""); + print_sanitized(key, "\n\\\""); + printf("\""); + print_sanitized(value, "\n\\\""); + printf("\","); + } + + printf("}, "); + printf("\"summary\": \""); + print_sanitized(summary, "\n\\\""); + printf("\", " + "\"body\": \""); + print_sanitized(body, "\n\\\""); + printf("\"}%s", delimiter); +} + +void output_notification(DBusMessage *message, dbus_uint32_t id, enum output_format fmt, const char *delimiter) +{ + DBusMessageIter i; + DBusMessageIter actions; + DBusMessageIter hints; + const char *app_name; + dbus_uint32_t replaces_id; + dbus_int32_t timeout; + const char *app_icon; + const char *summary; + const char *body; + + dbus_message_iter_init(message, &i); + dbus_message_iter_get_basic(&i, &app_name); + dbus_message_iter_next(&i); + dbus_message_iter_get_basic(&i, &replaces_id); + dbus_message_iter_next(&i); + dbus_message_iter_get_basic(&i, &app_icon); + dbus_message_iter_next(&i); + dbus_message_iter_get_basic(&i, &summary); + dbus_message_iter_next(&i); + dbus_message_iter_get_basic(&i, &body); + + dbus_message_iter_next(&i); + dbus_message_iter_recurse(&i, &actions); + + dbus_message_iter_next(&i); + dbus_message_iter_recurse(&i, &hints); + + dbus_message_iter_next(&i); + dbus_message_iter_get_basic(&i, &timeout); + + switch (fmt) { + case FORMAT_JSON: + json_output(app_name, app_icon, id, replaces_id, + timeout, &hints, &actions, summary, body, + delimiter); + break; + case FORMAT_TEXT: + default: + default_output(app_name, app_icon, id, replaces_id, + timeout, &hints, &actions, summary, body, + delimiter); + } + + fflush(stdout); +} + diff --git a/src/output.h b/src/output.h @@ -1,25 +1,10 @@ -#pragma once - -#include <gio/gio.h> -#include <glib.h> - -#define GT_STRING G_VARIANT_TYPE_STRING -#define GT_INT16 G_VARIANT_TYPE_INT16 -#define GT_INT32 G_VARIANT_TYPE_INT32 -#define GT_INT64 G_VARIANT_TYPE_INT64 -#define GT_UINT16 G_VARIANT_TYPE_UINT16 -#define GT_UINT32 G_VARIANT_TYPE_UINT32 -#define GT_UINT64 G_VARIANT_TYPE_UINT64 -#define GT_DOUBLE G_VARIANT_TYPE_DOUBLE -#define GT_BOOL G_VARIANT_TYPE_BOOLEAN -#define GT_BYTE G_VARIANT_TYPE_BYTE - -char *sanitize(const char*); - -void output_notification(GVariant*); -void hints_output_iterator(GVariant*, const char*, const char*, const char*, - const char*, const char*, const char*, const char*, const char*); -void default_output(gchar*, gchar*, guint32, gint32, GVariant*, gchar**, gchar*, - gchar*); -void json_output(gchar*, gchar*, guint32, gint32, GVariant*, gchar**, gchar*, - gchar*); +#pragma once + +#include <dbus/dbus.h> + +enum output_format { + FORMAT_TEXT, + FORMAT_JSON +}; + +void output_notification(DBusMessage *message, dbus_uint32_t id, enum output_format fmt, const char *delimiter); diff --git a/src/tiramisu.c b/src/tiramisu.c @@ -1,138 +0,0 @@ -#include <stdio.h> -#include <string.h> -#include <signal.h> -#include <unistd.h> - -#include <gio/gio.h> -#include <glib.h> -#include <glib-unix.h> - -#include "tiramisu.h" -#include "output.h" - -GDBusConnection *dbus_connection = NULL; -GDBusNodeInfo *introspection = NULL; -GMainLoop *main_loop = NULL; - -unsigned int notification_id = 0; -char print_json = 0; -char *delimiter = "\n"; - -gboolean stop_main_loop(gpointer user_data) { - g_main_loop_quit(main_loop); - - return G_SOURCE_CONTINUE; -} - -int main(int argc, char **argv) { - /* Parse arguments */ - - char argument; - while ((argument = getopt(argc, argv, "hjd:")) >= 0) { - switch (argument) { - case 'd': - delimiter = optarg; - break; - case 'h': - printf("%s\n", - "tiramisu -[h|d|j]\n" - "-h\tHelp dialog\n" - "-d\tDelimeter for default output style.\n" - "-j\tUse JSON output style\n"); - return EXIT_SUCCESS; - break; - case 'j': - print_json = 1; - break; - default: - break; - } - } - - guint owned_name; - - /* Connect to DBUS */ - - introspection = g_dbus_node_info_new_for_xml(INTROSPECTION_XML, NULL); - owned_name = g_bus_own_name(G_BUS_TYPE_SESSION, - "org.freedesktop.Notifications", - G_BUS_NAME_OWNER_FLAGS_NONE, - (GBusAcquiredCallback)bus_acquired, - (GBusNameAcquiredCallback)name_acquired, - (GBusNameLostCallback)name_lost, - NULL, /* user_data */ - NULL); /* user_data_free_func */ - - /* Setup and start the loop */ - - main_loop = g_main_loop_new(NULL, FALSE); - - guint signal_term = g_unix_signal_add(SIGTERM, stop_main_loop, NULL); - guint signal_int = g_unix_signal_add(SIGINT, stop_main_loop, NULL); - - g_main_loop_run(main_loop); - g_clear_pointer(&main_loop, g_main_loop_unref); - - g_source_remove(signal_term); - g_source_remove(signal_int); - - g_clear_pointer(&introspection, g_dbus_node_info_unref); - g_bus_unown_name(owned_name); - - return EXIT_SUCCESS; -} - -void bus_acquired(GDBusConnection *connection, const gchar *name, - gpointer user_data) { - print("%s\n", "Bus has been acquired."); - - guint registered_object = g_dbus_connection_register_object(connection, - "/org/freedesktop/Notifications", introspection->interfaces[0], - &(const GDBusInterfaceVTable){ method_handler }, NULL, NULL, NULL); - - if (!registered_object) { - print("%s\n", "Unable to register."); - stop_main_loop(NULL); - } -} - -void name_acquired(GDBusConnection *connection, const gchar *name, - gpointer user_data) { - dbus_connection = connection; - print("%s\n", "Name has been acquired."); -} - -void name_lost(GDBusConnection *connection, const gchar *name, - gpointer user_data) { - // we lost the Notifications daemon name or couldn't acquire it, shutdown - - if (!connection) { - printf("%s; %s\n", - "Unable to connect to acquire org.freedesktop.Notifications", - "could not connect to dbus."); - stop_main_loop(NULL); - } - else - print("%s\n", "Successfully acquired org.freedesktop.Notifications"); -} - -void method_handler(GDBusConnection *connection, const gchar *sender, - const gchar *object, const gchar *interface, const gchar *label, - GVariant *parameters, GDBusMethodInvocation *invocation, - gpointer user_data) { - - GVariant *return_value = NULL; - - if (!strcmp(label, "GetServerInformation")) - return_value = g_variant_new("(ssss)", - "tiramisu", "Sweets", "1.0", "1.2"); - else if (!strcmp(label, "Notify")) { - output_notification(parameters); - return_value = g_variant_new("(u)", ++notification_id); - } else - print("Unhandled: %s %s\n", label, sender); - - g_dbus_method_invocation_return_value(invocation, return_value); - g_dbus_connection_flush(connection, NULL, NULL, NULL); - -} diff --git a/src/tiramisu.h b/src/tiramisu.h @@ -1,54 +0,0 @@ -#pragma once - -#include <stdio.h> -#include <string.h> - -#include <gio/gio.h> -#include <glib.h> - -extern GDBusConnection *dbus_connection; -extern GDBusNodeInfo *introspection; -extern GMainLoop *main_loop; - -extern unsigned int notification_id; -extern char print_json; -extern char *delimiter; - -#ifdef DEBUG -#define print(...) fprintf(stderr, __VA_ARGS__); -#else -#define print(...) (void)(__VA_ARGS__); -#endif - -#define INTROSPECTION_XML "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"\ - "<node name=\"/org/freedesktop/Notifications\">\n"\ - " <interface name=\"org.freedesktop.Notifications\">\n"\ - " <method name=\"Notify\">\n"\ - " <arg direction=\"in\" type=\"s\" name=\"app_name\"/>\n"\ - " <arg direction=\"in\" type=\"u\""\ - " name=\"replaces_id\"/>\n"\ - " <arg direction=\"in\" type=\"s\" name=\"app_icon\"/>\n"\ - " <arg direction=\"in\" type=\"s\" name=\"summary\"/>\n"\ - " <arg direction=\"in\" type=\"s\" name=\"body\"/>\n"\ - " <arg direction=\"in\" type=\"as\" name=\"actions\"/>\n"\ - " <arg direction=\"in\" type=\"a{sv}\" name=\"hints\"/>\n"\ - " <arg direction=\"in\" type=\"i\""\ - " name=\"expire_timeout\"/>\n"\ - " <arg direction=\"out\" type=\"u\""\ - " name=\"id\"/>\n"\ - " </method>\n"\ - " <method name=\"GetServerInformation\">\n"\ - " <arg direction=\"out\" type=\"s\" name=\"name\"/>\n"\ - " <arg direction=\"out\" type=\"s\" name=\"vendor\"/>\n"\ - " <arg direction=\"out\" type=\"s\" name=\"version\"/>\n"\ - " <arg direction=\"out\" type=\"s\" name=\"spec_version\"/>\n"\ - " </method>\n"\ - " </interface>\n"\ - "</node>" - -gboolean stop_main_loop(gpointer); -void bus_acquired(GDBusConnection*, const gchar*, gpointer); -void name_acquired(GDBusConnection*, const gchar*, gpointer); -void name_lost(GDBusConnection*, const gchar*, gpointer); -void method_handler(GDBusConnection*, const gchar*, const gchar*, const gchar*, - const gchar*, GVariant*, GDBusMethodInvocation*, gpointer); diff --git a/src/tmisu.c b/src/tmisu.c @@ -0,0 +1,166 @@ +#include <stdio.h> +#include <string.h> +#include <signal.h> +#include <unistd.h> +#include <stdbool.h> +#include <stdlib.h> +#include <getopt.h> +#include <dbus/dbus.h> + +#include "tmisu.h" +#include "output.h" + +struct conf { + enum output_format fmt; + const char *delimiter; +}; + +DBusHandlerResult handle_message(DBusConnection *connection, DBusMessage *message, void *user_data) +{ + static unsigned notification_id = 0; + struct conf *cnf = user_data; + + if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { + static const char *notificationpath = "/org/freedesktop/Notifications"; + const char *path = dbus_message_get_path(message); + size_t pl = strlen(path); + DBusMessage *reply; + + if (strncmp(notificationpath, path, strlen(path))) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (pl == 1) + pl = 0; + + if (notificationpath[pl] == '/') { + size_t npl = strcspn(notificationpath + pl + 1, "/"); + char *buffer = malloc(sizeof(INTROSPECTION_NODE_XML) + npl); + sprintf(buffer, INTROSPECTION_NODE_XML, (int)npl, notificationpath + pl + 1); + reply = dbus_message_new_method_return(message); + dbus_message_append_args(reply, + DBUS_TYPE_STRING, &buffer, + DBUS_TYPE_INVALID); + free(buffer); + + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (notificationpath[pl]) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + reply = dbus_message_new_method_return(message); + dbus_message_append_args(reply, + DBUS_TYPE_STRING, &(const char *){ INTROSPECTION_XML }, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_method_call(message, "org.freedesktop.Notifications", "GetServerInformation")) { + DBusMessage *reply = dbus_message_new_method_return(message); + dbus_message_append_args(reply, + DBUS_TYPE_STRING, &(const char *){ "tiramisu" }, + DBUS_TYPE_STRING, &(const char *){ "Sweets" }, + DBUS_TYPE_STRING, &(const char *){ "1.0" }, + DBUS_TYPE_STRING, &(const char *){ "1.2" }, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_method_call(message, "org.freedesktop.Notifications", "GetCapabilities")) { + DBusMessage *reply = dbus_message_new_method_return(message); + DBusMessageIter i; + DBusMessageIter j; + + dbus_message_iter_init_append(reply, &i); + dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &j); + dbus_message_iter_append_basic(&j, DBUS_TYPE_STRING, &(const char *){ "body" }); + dbus_message_iter_append_basic(&j, DBUS_TYPE_STRING, &(const char *){ "body-markup" }); + dbus_message_iter_close_container(&i, &j); + + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_method_call(message, "org.freedesktop.Notifications", "Notify")) { + DBusMessage *reply; + if (!dbus_message_has_signature(message, "susssasa{sv}i")) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + reply = dbus_message_new_method_return(message); + output_notification(message, ++notification_id, cnf->fmt, cnf->delimiter); + dbus_message_append_args(reply, + DBUS_TYPE_UINT32, &(dbus_uint32_t){ notification_id }, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +DBusConnection *connection = NULL; + +void sig_handler(int signal) +{ + (void)signal; + dbus_connection_close(connection); +} + +int main(int argc, char **argv) { + /* Parse arguments */ + struct conf cnf = { FORMAT_TEXT, "\n" }; + + char argument; + while ((argument = getopt(argc, argv, "hjd:")) >= 0) { + switch (argument) { + case 'd': + cnf.delimiter = optarg; + break; + case 'h': + printf("%s\n", + "tiramisu -[h|d|j]\n" + "-h\tHelp dialog\n" + "-d\tDelimeter for default output style.\n" + "-j\tUse JSON output style\n"); + return EXIT_SUCCESS; + break; + case 'j': + cnf.fmt = FORMAT_JSON; + break; + default: + break; + } + } + + connection = dbus_bus_get_private(DBUS_BUS_SESSION, NULL); + + if (!connection) { + fprintf(stderr, "Could not connect to D-Bus\n"); + return 1; + } + + int result = dbus_bus_request_name(connection, "org.freedesktop.Notifications", DBUS_NAME_FLAG_REPLACE_EXISTING | DBUS_NAME_FLAG_DO_NOT_QUEUE, NULL); + + if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + dbus_connection_close(connection); + dbus_connection_unref(connection); + + fprintf(stderr, "Could not acquire service name, is another notification daemon running?\n"); + return 1; + } + + dbus_bus_add_match(connection, "interface=org.freedesktop.Notifications,path=/org/freedesktop/Notifications,type=method_call", NULL); + dbus_bus_add_match(connection, "interface=org.freedesktop.DBus.Introspectable,method=Introspect,type=method_call", NULL); + dbus_connection_add_filter(connection, handle_message, &cnf, NULL); + + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + while (dbus_connection_read_write_dispatch(connection, -1)); + + dbus_connection_unref(connection); + + return 0; +} diff --git a/src/tmisu.h b/src/tmisu.h @@ -0,0 +1,44 @@ +#ifndef TMISU_H +#define TMISU_H + +#include <stdio.h> +#include <string.h> + +extern char print_json; +extern const char *delimiter; + +#define INTROSPECTION_XML "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"\ + "<node>\n"\ + " <interface name=\"org.freedesktop.Notifications\">\n"\ + " <method name=\"Notify\">\n"\ + " <arg direction=\"in\" type=\"s\" name=\"app_name\"/>\n"\ + " <arg direction=\"in\" type=\"u\""\ + " name=\"replaces_id\"/>\n"\ + " <arg direction=\"in\" type=\"s\" name=\"app_icon\"/>\n"\ + " <arg direction=\"in\" type=\"s\" name=\"summary\"/>\n"\ + " <arg direction=\"in\" type=\"s\" name=\"body\"/>\n"\ + " <arg direction=\"in\" type=\"as\" name=\"actions\"/>\n"\ + " <arg direction=\"in\" type=\"a{sv}\" name=\"hints\"/>\n"\ + " <arg direction=\"in\" type=\"i\""\ + " name=\"expire_timeout\"/>\n"\ + " <arg direction=\"out\" type=\"u\""\ + " name=\"id\"/>\n"\ + " </method>\n"\ + " <method name=\"GetServerInformation\">\n"\ + " <arg direction=\"out\" type=\"s\" name=\"name\"/>\n"\ + " <arg direction=\"out\" type=\"s\" name=\"vendor\"/>\n"\ + " <arg direction=\"out\" type=\"s\" name=\"version\"/>\n"\ + " <arg direction=\"out\" type=\"s\" name=\"spec_version\"/>\n"\ + " </method>\n"\ + " <method name=\"GetCapabilities\">\n"\ + " <arg type=\"as\" name=\"capabilities\" direction=\"out\"/>\n"\ + " </method>\n"\ + " </interface>\n"\ + "</node>" + +#define INTROSPECTION_NODE_XML "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"\ + "<node>\n"\ + " <node name=\"%.*s\"/>\n"\ + "</node>" + +#endif