commit 6f4bcef9f6d3298dfe75bfc2a56bc68b963d6376
parent 8eb946dae0e2f98d3850d89e1bb535640e8c3266
Author: Santtu Lakkala <inz@inz.fi>
Date: Tue, 27 Apr 2021 20:59:13 +0300
Rewrite and rename
Diffstat:
M | Makefile | | | 19 | ++++++++++++------- |
M | README.md | | | 162 | ++++++++++++++++++++++++++++++++++++++++---------------------------------------- |
M | src/output.c | | | 438 | +++++++++++++++++++++++++++++++++++++++++-------------------------------------- |
M | src/output.h | | | 35 | ++++++++++------------------------- |
D | src/tiramisu.c | | | 138 | ------------------------------------------------------------------------------- |
D | src/tiramisu.h | | | 54 | ------------------------------------------------------ |
A | src/tmisu.c | | | 166 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/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