aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjakob.stendahl <jakob.stendahl@infomedia.dk>2024-07-12 18:02:47 +0200
committerjakob.stendahl <jakob.stendahl@infomedia.dk>2024-07-12 18:02:47 +0200
commited74ab2d27460ec11a29f6d005a003d69104d7c5 (patch)
tree0ca5295ed6ab640db1d6d5dd4b7fc0d79210ccc5
downloadsimple-notification-daemon-ed74ab2d27460ec11a29f6d005a003d69104d7c5.tar.gz
simple-notification-daemon-ed74ab2d27460ec11a29f6d005a003d69104d7c5.zip
Initial commit
-rw-r--r--.clangd2
-rw-r--r--.gitignore3
-rw-r--r--Makefile28
-rw-r--r--snotif.c74
-rw-r--r--snotif.h48
-rw-r--r--snotifc.c518
-rw-r--r--snotifd.c356
-rw-r--r--snotifd.h0
-rw-r--r--term.h16
9 files changed, 1045 insertions, 0 deletions
diff --git a/.clangd b/.clangd
new file mode 100644
index 0000000..666e966
--- /dev/null
+++ b/.clangd
@@ -0,0 +1,2 @@
+CompileFlags:
+ Add: [-I/usr/include/dbus-1.0, -I/usr/lib64/dbus-1.0/include, -ldbus-1]
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a9f5782
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+*.o
+snotifd
+snotifc
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..38c3773
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,28 @@
+GCC := gcc
+CFLAGS_DBUS := $(shell pkg-config --cflags --libs dbus-1)
+CFLAGS = -g -Wall -O ${CFLAGS_DBUS}
+
+DEST =
+
+.PHONY: clean
+
+all: snotifd snotifc
+o_files := $(patsubst %.c,%.o,$(wildcard *.c))
+
+%: %.c
+ ${GCC} $< -o $@ -c ${CFLAGS}
+
+snotifd: ${o_files}
+ ${GCC} -o $@ $@.o snotif.o ${CFLAGS}
+
+snotifc: ${o_files}
+ ${GCC} -o $@ $@.o snotif.o ${CFLAGS} -lncursesw
+
+install: snotifc snotifd
+ install snotifd ${DEST}/usr/local/bin/snotifd
+ install snotifc ${DEST}/usr/local/bin/snotifc
+
+clean:
+ rm -f *.o
+ rm -f snotifc
+ rm -f snotifd
diff --git a/snotif.c b/snotif.c
new file mode 100644
index 0000000..8d914e5
--- /dev/null
+++ b/snotif.c
@@ -0,0 +1,74 @@
+#include "snotif.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+char* strnotify_params(const struct NotifyParams* params) {
+ char time[80] = "";
+ if (params->time)
+ strftime(time, 80, "%R:%S", localtime(&params->time));
+ static char res[180];
+ sprintf(
+ res,
+ "%s [%s] (timeout: %d, replaces: %u) %s\n",
+ time, params->app_name, params->expire_timeout, params->replaces_id, params->summary
+ );
+ return res;
+}
+
+void notifs_list_init(struct NotifsList** list) {
+ (*list) = malloc(sizeof(struct NotifsList));
+ (*list)->list_size = 0;
+ (*list)->element_count = 0;
+
+#define INITIAL_SIZE 8
+ (*list)->list = calloc(INITIAL_SIZE, sizeof(struct NotifyParams*));
+ if ((*list)->list == NULL) {
+ fprintf(stderr, "Failed to allocate memory for notification log!\n");
+ exit(1);
+ }
+ (*list)->list_size = INITIAL_SIZE;
+}
+
+void notifs_list_set(struct NotifsList* list, struct NotifyParams* notif) {
+ while (notif->id > list->list_size) {
+ unsigned int new_size = list->list_size * 2;
+ struct NotifyParams** temp = realloc(list->list, new_size * sizeof(struct NotifyParams*));
+ if (!temp) {
+ fprintf(stderr, "Failed to reallocate memory for notification log!\n");
+ exit(1);
+ }
+ list->list = temp;
+ for (unsigned int i = list->list_size; i < new_size; i++) {
+ list->list[i] = NULL;
+ }
+ list->list_size = new_size;
+ }
+
+ if (notif->id > list->element_count) {
+ list->element_count = notif->id;
+ }
+ list->list[notif->id - 1] = notif;
+}
+
+int notifs_list_set_seen(struct NotifsList* list, unsigned int id, bool read) {
+ if (id > list-> list_size)
+ return 1;
+ list->list[id]->seen = read;
+ return 0;
+}
+
+void notifs_list_free(struct NotifsList* list) {
+ for (unsigned int i = 0; i < list->element_count; i++) {
+ if (list->list[i]->app_name)
+ free(list->list[i]->app_name);
+ if (list->list[i]->app_icon)
+ free(list->list[i]->app_icon);
+ if (list->list[i]->summary)
+ free(list->list[i]->summary);
+ if (list->list[i]->body)
+ free(list->list[i]->body);
+ free(list->list[i]);
+ }
+ free(list->list);
+ free(list);
+}
diff --git a/snotif.h b/snotif.h
new file mode 100644
index 0000000..dd52ac4
--- /dev/null
+++ b/snotif.h
@@ -0,0 +1,48 @@
+#ifndef SNOTIF_H
+#define SNOTIF_H
+
+#include <dbus/dbus.h>
+#include <stdbool.h>
+#include <time.h>
+
+// Constansts for the DBus interactions
+#define DBUS_NOTIF_INTERFACE "org.freedesktop.Notifications"
+#define DBUS_CLIENT_INTERFACE "snotifd.Notifications"
+#define DBUS_CLIENT_OBJECT "/snotifd/Notifications"
+#define DBUS_METHOD_GETLIST "GetNotifications"
+#define DBUS_METHOD_GETUNSEENCOUNT "GetUnseenCount"
+#define DBUS_METHOD_SETSEEN "SetSeen"
+
+// Curses related constants
+#define CPAIR_SEL 132
+#define CPAIR_B 4
+
+// Extended struct from the specification at:
+// https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html
+struct NotifyParams {
+ unsigned int id;
+ char* app_name;
+ dbus_uint32_t replaces_id;
+ char* app_icon;
+ char* summary;
+ char* body;
+ dbus_int32_t expire_timeout;
+ time_t time;
+ bool seen;
+};
+
+// Convenient struct for keeping a list of notifications
+struct NotifsList {
+ struct NotifyParams** list;
+ size_t element_count;
+ size_t list_size;
+};
+
+// Convenient methods for working with a list of notifications
+char* strnotify_params(const struct NotifyParams* params);
+void notifs_list_init(struct NotifsList** list);
+void notifs_list_set(struct NotifsList* list, struct NotifyParams* notif);
+void notifs_list_free(struct NotifsList* list);
+int notifs_list_set_seen(struct NotifsList* list, unsigned int id, bool read);
+
+#endif
diff --git a/snotifc.c b/snotifc.c
new file mode 100644
index 0000000..ebda38a
--- /dev/null
+++ b/snotifc.c
@@ -0,0 +1,518 @@
+#include "snotif.h"
+#include "term.h"
+#include <locale.h>
+#include <ncurses.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+void print_unseen_count(DBusConnection* conn) {
+ DBusMessage* msg;
+ DBusMessageIter args;
+ DBusPendingCall* pending;
+
+ msg = dbus_message_new_method_call(
+ DBUS_CLIENT_INTERFACE,
+ DBUS_CLIENT_OBJECT,
+ DBUS_CLIENT_INTERFACE,
+ DBUS_METHOD_GETUNSEENCOUNT
+ );
+
+ dbus_message_iter_init_append(msg, &args);
+
+ if (!dbus_connection_send_with_reply(conn, msg, &pending, -1)) {
+ fprintf(stderr, "Out Of Memory!\n");
+ exit(1);
+ }
+
+ dbus_connection_flush(conn);
+ dbus_message_unref(msg);
+
+ dbus_pending_call_block(pending);
+ msg = dbus_pending_call_steal_reply(pending);
+ if (msg == NULL) {
+ fprintf(stderr, "Reply Null\n"); exit(1);
+ }
+ dbus_pending_call_unref(pending);
+
+ unsigned int count;
+ if (!dbus_message_iter_init(msg, &args))
+ fprintf(stderr, "Message has no arguments!\n");
+ else if (DBUS_TYPE_UINT32 != dbus_message_iter_get_arg_type(&args))
+ fprintf(stderr, "Argument is not uint32!\n");
+ else
+ dbus_message_iter_get_basic(&args, &count);
+ printf("%d", count);
+ fflush(stdout);
+
+ dbus_message_unref(msg);
+}
+
+void set_notif_seen(DBusConnection* conn, uint id, bool seen) {
+ DBusMessage* msg;
+ DBusMessageIter args;
+ DBusPendingCall* pending;
+
+ msg = dbus_message_new_method_call(
+ DBUS_CLIENT_INTERFACE,
+ DBUS_CLIENT_OBJECT,
+ DBUS_CLIENT_INTERFACE,
+ DBUS_METHOD_SETSEEN
+ );
+
+ dbus_message_iter_init_append(msg, &args);
+ if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &id)) {
+ fprintf(stderr, "Out Of Memory!\n");
+ exit(1);
+ }
+
+ unsigned int _seen = seen;
+ if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_BOOLEAN, &_seen)) {
+ fprintf(stderr, "Out Of Memory!\n");
+ exit(1);
+ }
+
+ if (!dbus_connection_send_with_reply(conn, msg, &pending, -1)) {
+ fprintf(stderr, "Out Of Memory!\n");
+ exit(1);
+ }
+
+ dbus_connection_flush(conn);
+ dbus_message_unref(msg);
+
+ dbus_pending_call_block(pending);
+ msg = dbus_pending_call_steal_reply(pending);
+ if (msg == NULL) {
+ fprintf(stderr, "Reply Null\n"); exit(1);
+ }
+ dbus_pending_call_unref(pending);
+
+ dbus_message_unref(msg);
+}
+
+void print_notification_list(DBusConnection* conn) {
+ DBusMessage* msg;
+ DBusMessageIter args;
+ DBusPendingCall* pending;
+
+ msg = dbus_message_new_method_call(
+ DBUS_CLIENT_INTERFACE,
+ DBUS_CLIENT_OBJECT,
+ DBUS_CLIENT_INTERFACE,
+ DBUS_METHOD_GETLIST
+ );
+
+ dbus_message_iter_init_append(msg, &args);
+
+ if (!dbus_connection_send_with_reply(conn, msg, &pending, -1)) {
+ fprintf(stderr, "Out Of Memory!\n");
+ exit(1);
+ }
+
+ dbus_connection_flush(conn);
+ dbus_message_unref(msg);
+
+ dbus_pending_call_block(pending);
+ msg = dbus_pending_call_steal_reply(pending);
+ if (msg == NULL) {
+ fprintf(stderr, "Reply Null\n"); exit(1);
+ }
+ dbus_pending_call_unref(pending);
+
+ DBusMessageIter array_iter, struct_iter;
+ if (!dbus_message_iter_init(msg, &args))
+ fprintf(stderr, "Message has no arguments!\n");
+ else {
+ if (DBUS_TYPE_ARRAY == dbus_message_iter_get_arg_type(&args)) {
+ dbus_message_iter_recurse(&args, &array_iter);
+
+ while (dbus_message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRUCT) {
+ dbus_message_iter_recurse(&array_iter, &struct_iter);
+
+ unsigned int id;
+ const char* app_name;
+ const char* summary;
+ const char* body;
+
+ dbus_message_iter_get_basic(&struct_iter, &id);
+ dbus_message_iter_next(&struct_iter);
+ dbus_message_iter_get_basic(&struct_iter, &app_name);
+ dbus_message_iter_next(&struct_iter);
+ dbus_message_iter_get_basic(&struct_iter, &summary);
+ dbus_message_iter_next(&struct_iter);
+ dbus_message_iter_get_basic(&struct_iter, &body);
+ dbus_message_iter_next(&struct_iter);
+
+ //printf("Received Struct: { id: %d, app_name: %s, summary: %s, body: %s }\n", id, app_name, summary, body);
+ printf(KITAL"%10s"KNRM" ┃ "KMAG"%s"KNRM"\n", app_name, summary);
+
+ dbus_message_iter_next(&array_iter);
+ }
+ }
+ }
+
+ char* response = NULL;
+ if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &response, DBUS_TYPE_INVALID)) {
+ printf("Received: %s\n", response);
+ }
+ dbus_message_unref(msg);
+}
+
+void add_to_notiflist(struct NotifsList* notifs, DBusMessageIter* struct_iter) {
+ struct NotifyParams* notif = malloc(sizeof(struct NotifyParams));
+ if (!notif) {
+ fprintf(stderr, "Failed to allocate memory for notification parameters!\n");
+ exit(1);
+ }
+ notif->app_icon = NULL;
+ notif->time = 0;
+
+ const char* tmp_str;
+ dbus_message_iter_get_basic(struct_iter, &notif->id);
+
+ dbus_message_iter_next(struct_iter);
+ dbus_message_iter_get_basic(struct_iter, &tmp_str);
+ notif->app_name = strdup(tmp_str);
+
+ dbus_message_iter_next(struct_iter);
+ dbus_message_iter_get_basic(struct_iter, &tmp_str);
+ notif->summary = strdup(tmp_str);
+
+ dbus_message_iter_next(struct_iter);
+ dbus_message_iter_get_basic(struct_iter, &tmp_str);
+ notif->body = strdup(tmp_str);
+
+ dbus_message_iter_next(struct_iter);
+ dbus_message_iter_get_basic(struct_iter, &notif->time);
+
+ dbus_message_iter_next(struct_iter);
+ dbus_message_iter_get_basic(struct_iter, &notif->seen);
+
+ notifs_list_set(notifs, notif);
+}
+
+void get_notification_list(DBusConnection* conn, struct NotifsList* notifs) {
+ DBusMessage* msg;
+ DBusMessageIter args;
+ DBusPendingCall* pending;
+
+ msg = dbus_message_new_method_call(
+ DBUS_CLIENT_INTERFACE,
+ DBUS_CLIENT_OBJECT,
+ DBUS_CLIENT_INTERFACE,
+ DBUS_METHOD_GETLIST
+ );
+
+ dbus_message_iter_init_append(msg, &args);
+
+ if (!dbus_connection_send_with_reply(conn, msg, &pending, -1)) {
+ fprintf(stderr, "Out Of Memory!\n");
+ exit(1);
+ }
+
+ dbus_connection_flush(conn);
+ dbus_message_unref(msg);
+
+ dbus_pending_call_block(pending);
+ msg = dbus_pending_call_steal_reply(pending);
+ if (msg == NULL) {
+ fprintf(stderr, "Reply Null\n"); exit(1);
+ }
+ dbus_pending_call_unref(pending);
+
+ DBusMessageIter array_iter, struct_iter;
+ if (dbus_message_iter_init(msg, &args) && dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_ARRAY) {
+ dbus_message_iter_recurse(&args, &array_iter);
+
+ while (dbus_message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRUCT) {
+ dbus_message_iter_recurse(&array_iter, &struct_iter);
+ add_to_notiflist(notifs, &struct_iter);
+ dbus_message_iter_next(&array_iter);
+ }
+ }
+ dbus_message_unref(msg);
+}
+
+void init_dbus(DBusConnection** conn) {
+ DBusError err;
+ dbus_error_init(&err);
+ *conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
+
+ if (dbus_error_is_set(&err)) {
+ fprintf(stderr, "Connection Error: %s\n", err.message);
+ dbus_error_free(&err);
+ exit(EXIT_FAILURE);
+ }
+}
+
+void curses_init() {
+ initscr();
+ cbreak();
+ noecho();
+ curs_set(0);
+ keypad(stdscr, TRUE);
+ start_color();
+ use_default_colors();
+
+ init_pair(2, COLOR_BLACK, COLOR_CYAN);
+ init_pair(3, COLOR_YELLOW, COLOR_BLUE);
+
+ init_pair(CPAIR_B, -1, -1);
+ init_pair(CPAIR_B + 1, COLOR_YELLOW, -1);
+ init_pair(CPAIR_B + 2, COLOR_MAGENTA, -1);
+ init_pair(CPAIR_B + 3, COLOR_WHITE, -1);
+ init_pair(CPAIR_B | CPAIR_SEL, -1, COLOR_CYAN);
+ init_pair((CPAIR_B + 1) | CPAIR_SEL, COLOR_YELLOW, COLOR_CYAN);
+ init_pair((CPAIR_B + 2) | CPAIR_SEL, COLOR_MAGENTA, COLOR_CYAN);
+ init_pair((CPAIR_B + 3) | CPAIR_SEL, COLOR_WHITE, COLOR_CYAN);
+}
+
+void curses_display_menu(int c_id) {
+ mvprintw(LINES - 1, 0, " F7");
+ attron(COLOR_PAIR(2));
+ printw("Toogle read");
+ attroff(COLOR_PAIR(2));
+
+ printw(" F8");
+ attron(COLOR_PAIR(2));
+ printw("Mark all read");
+ attroff(COLOR_PAIR(2));
+
+ printw("F10");
+ attron(COLOR_PAIR(2));
+ printw("Quit");
+ attroff(COLOR_PAIR(2));
+
+ chgat(-1, A_NORMAL, 2, NULL);
+
+ attron(COLOR_PAIR(2));
+ mvprintw(LINES-1, COLS-3, "%3d", c_id);
+ attroff(COLOR_PAIR(2));
+}
+
+void curses_display_notifs(struct NotifsList* notifs, int start, int selected) {
+ int w_app_name = 0;
+ int w = 0;
+
+ for (size_t i = 0; i < notifs->element_count; i++) {
+ if (!notifs->list[i])
+ continue;
+
+ w = strlen(notifs->list[i]->app_name);
+ if (notifs->list[i]->app_name && w > w_app_name)
+ w_app_name = w;
+ }
+
+ const int items = (notifs->element_count > LINES) ? LINES : notifs->element_count;
+ int n_printed = 0;
+ size_t ri;
+
+ struct NotifyParams* notif;
+ for (size_t i = 0; i < items; i++) {
+ ri = notifs->element_count - i - 1;
+ if (!notifs->list[ri])
+ continue;
+ notif = notifs->list[ri];
+
+ char time[10] = "";
+ if (notif->time)
+ strftime(time, 10, "%R:%S", localtime(&notif->time));
+
+ short extra_bits = (i == selected) ? CPAIR_SEL : 0;
+ attron(COLOR_PAIR(CPAIR_B | extra_bits));
+ if (notif->seen) {
+ attron(COLOR_PAIR((CPAIR_B + 3) | extra_bits));
+ attron(A_ITALIC);
+ mvprintw(i, 0, " %s", time);
+ attroff(A_ITALIC);
+ printw(" │ ");
+ attron(A_ITALIC);
+ printw("%*s ", w_app_name, notif->app_name);
+ attroff(A_ITALIC);
+ printw(" │ ");
+ attron(A_ITALIC);
+ printw("%s", notif->summary);
+ attroff(A_ITALIC);
+ } else {
+ attron(A_ITALIC);
+ mvprintw(i, 0, " %s", time);
+ attroff(A_ITALIC);
+ printw(" │ ");
+ attron(COLOR_PAIR((CPAIR_B + 2) | extra_bits));
+ printw("%*s", w_app_name, notif->app_name);
+ attroff(COLOR_PAIR((CPAIR_B + 2) | extra_bits));
+ attron(COLOR_PAIR(CPAIR_B | extra_bits));
+ printw(" │ ");
+ printw("%s", notif->summary);
+ }
+ attroff(COLOR_PAIR(CPAIR_B | extra_bits));
+ chgat(-1, A_NORMAL, CPAIR_B | extra_bits, NULL);
+
+ n_printed++;
+ }
+}
+
+void show_full_notif(int id, struct NotifsList* notifs) {
+ if (id >= notifs->element_count || !notifs->list[id])
+ return;
+
+ int width = COLS;
+ int height = LINES - 1;
+ int start_x = 0;
+ int start_y = 0;
+
+#define MAX_WIDTH 80
+ if (width > MAX_WIDTH) {
+ width = MAX_WIDTH;
+ start_x = ((COLS - 1) / 2) - (MAX_WIDTH / 2);
+ }
+#define MAX_HEIGHT 20
+ if (height > MAX_HEIGHT) {
+ height = MAX_HEIGHT;
+ start_y = ((LINES - 1) / 2) - (MAX_HEIGHT / 2);
+ }
+
+ WINDOW *win = newwin(height, width, start_y, start_x);
+ WINDOW *win_content = newwin(height-2, width-2, start_y+1, start_x+1);
+ while (1) {
+ box(win, 0, 0);
+
+ wattron(win, COLOR_PAIR(2));
+ mvwprintw(win, 0, 1, "%s", notifs->list[id]->app_name);
+ mvwprintw(win, height-1, 1, "q: close");
+ wattroff(win, COLOR_PAIR(2));
+
+ mvwprintw(win_content, 1, 1, "%s", notifs->list[id]->summary);
+
+ for (size_t i = 0; i < width - 2; i++) {
+ mvwprintw(win_content, 2, i, "─");
+ }
+
+ mvwprintw(win_content, 3, 1, "%s", notifs->list[id]->body);
+
+ wrefresh(win);
+ wrefresh(win_content);
+ refresh();
+
+ int ch = getch();
+ if (ch == 'q' || ch == KEY_F(10))
+ break;
+ }
+
+ werase(win_content);
+ wrefresh(win_content);
+ delwin(win_content);
+ werase(win);
+ wrefresh(win);
+ delwin(win);
+}
+
+void curses_handle_input(DBusConnection* conn, int ch, int* start, int* selected, struct NotifsList* notifs) {
+ unsigned int id = notifs->element_count - (*selected) - 1;
+ switch (ch) {
+ case KEY_DOWN:
+ case 'j':
+ if ((*selected) < notifs->element_count-1) {
+ (*selected)++;
+ }
+ break;
+ case KEY_UP:
+ case 'k':
+ if ((*selected) > 0) {
+ (*selected)--;
+ }
+ break;
+ case KEY_ENTER:
+ case '\n':
+ case '\r':
+ case KEY_F(2):
+ show_full_notif(id, notifs);
+ break;
+ case KEY_F(7):
+ set_notif_seen(conn, id, !(notifs->list[id]->seen));
+ break;
+ case KEY_F(8):
+ for (size_t i = 0; i < notifs->element_count; i++) {
+ if (notifs->list[id] || !notifs->list[id]->seen) {
+ set_notif_seen(conn, i, true);
+ }
+ }
+ break;
+ }
+}
+
+void update_notif_list(DBusConnection* conn, struct NotifsList** notifs, int* unread_count) {
+ struct NotifsList* new_notifs = NULL;
+ notifs_list_init(&new_notifs);
+ get_notification_list(conn, new_notifs);
+
+ notifs_list_free(*notifs);
+ (*notifs) = new_notifs;
+
+ (*unread_count) = 0;
+ for (unsigned int i = 0; i < (*notifs)->element_count; i++) {
+ if ((*notifs)->list[i] && !(*notifs)->list[i]->seen)
+ (*unread_count)++;
+ }
+}
+
+void run_tui_client(DBusConnection* conn) {
+ int ch;
+ int start = 0;
+ int selected = 0;
+ int unread_count = 0;
+
+ setlocale(LC_ALL, "");
+ curses_init();
+
+ struct NotifsList* notifs = NULL;
+ notifs_list_init(&notifs);
+
+ while (1) {
+ timeout(500);
+ ch = getch();
+
+ if (ch == KEY_F(10) || ch == 'q') {
+ break;
+ }
+
+ curses_handle_input(conn, ch, &start, &selected, notifs);
+ update_notif_list(conn, &notifs, &unread_count);
+ curses_display_notifs(notifs, start, selected);
+ curses_display_menu(unread_count);
+ refresh();
+ }
+
+ endwin();
+ notifs_list_free(notifs);
+}
+
+void usage(char **argv) {
+ printf("Usage: %s [unread|ls]\n", argv[0]);
+}
+
+int main(int argc, char **argv) {
+ DBusConnection* conn;
+ init_dbus(&conn);
+
+ if (argc == 1) {
+ run_tui_client(conn);
+ exit(0);
+ }
+
+#define UNREAD "unread"
+#define LS "ls"
+
+ if (!strncmp(argv[1], UNREAD, strlen(UNREAD)))
+ print_unseen_count(conn);
+ else if (!strncmp(argv[1], LS, strlen(LS)))
+ print_notification_list(conn);
+ else {
+ usage(argv);
+ exit(1);
+ }
+
+ dbus_connection_unref(conn);
+ return 0;
+}
diff --git a/snotifd.c b/snotifd.c
new file mode 100644
index 0000000..d60f9f4
--- /dev/null
+++ b/snotifd.c
@@ -0,0 +1,356 @@
+#include "snotif.h"
+#include <dbus/dbus.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+struct NotifsList* notifs;
+
+void print_notification_log() {
+ printf("LOG:\n");
+ for (size_t i = 0; i < notifs->element_count; i++) {
+ if (notifs->list[i]) {
+ printf("[%zu] %s", i, strnotify_params(notifs->list[i]));
+ }
+ }
+}
+
+unsigned int notifid(struct NotifyParams* notify_params) {
+ if (!notify_params->replaces_id || notify_params->replaces_id > notifs->element_count) {
+ notifs->element_count++;
+ return notifs->element_count;
+ }
+ return notify_params->replaces_id;
+}
+
+void reply_to_notify_method_call(DBusMessage* msg, DBusConnection* conn, struct NotifyParams* notify_params) {
+ DBusMessage* reply;
+ DBusMessageIter args;
+ dbus_uint32_t serial = 0;
+
+ reply = dbus_message_new_method_return(msg);
+
+ dbus_message_iter_init_append(reply, &args);
+
+ unsigned int nid = notify_params->id;
+ if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &nid)) {
+ fprintf(stderr, "Out Of Memory!\n");
+ exit(1);
+ }
+
+ if (!dbus_connection_send(conn, reply, &serial)) {
+ fprintf(stderr, "Out Of Memory!\n");
+ exit(1);
+ }
+ dbus_connection_flush(conn);
+
+ dbus_message_unref(reply);
+ printf("> [%s] notification ID %i\n", notify_params->app_name, notify_params->id);
+}
+
+void handle_notify_method_call(DBusMessage* msg, DBusConnection* conn) {
+ //const char* sender = dbus_message_get_sender(msg);
+
+ struct NotifyParams* notify_params = malloc(sizeof(struct NotifyParams));
+ if (!notify_params) {
+ fprintf(stderr, "Failed to allocate memory for notification parameters!\n");
+ exit(1);
+ }
+
+ notify_params->time = time(NULL);
+ notify_params->app_name = "";
+ notify_params->app_icon = "";
+ notify_params->summary = "";
+ notify_params->body = "";
+ notify_params->expire_timeout = -1;
+ notify_params->seen = false;
+
+ const char* tmp_str;
+ DBusMessageIter args;
+ dbus_message_iter_init(msg, &args);
+ size_t msg_i = 0;
+ do {
+ switch (msg_i) {
+ case 0:
+ dbus_message_iter_get_basic(&args, &tmp_str);
+ notify_params->app_name = strdup(tmp_str);
+ break;
+ case 1:
+ dbus_message_iter_get_basic(&args, &notify_params->replaces_id);
+ break;
+ case 2:
+ dbus_message_iter_get_basic(&args, &tmp_str);
+ notify_params->app_icon = strdup(tmp_str);
+ break;
+ case 3:
+ dbus_message_iter_get_basic(&args, &tmp_str);
+ notify_params->summary = strdup(tmp_str);
+ break;
+ case 4:
+ dbus_message_iter_get_basic(&args, &tmp_str);
+ notify_params->body = strdup(tmp_str);
+ break;
+ case 7:
+ dbus_message_iter_get_basic(&args, &notify_params->expire_timeout);
+ break;
+ }
+
+ msg_i++;
+ } while (dbus_message_iter_next(&args));
+ notify_params->id = notifid(notify_params);
+
+ printf("< %s", strnotify_params(notify_params));
+ reply_to_notify_method_call(msg, conn, notify_params);
+ notifs_list_set(notifs, notify_params);
+}
+
+void reply_to_get_capabilities_method_call(DBusMessage* msg, DBusConnection* conn) {
+ DBusMessage* reply;
+ DBusMessageIter args, array_iter;
+ dbus_uint32_t serial = 0;
+
+ reply = dbus_message_new_method_return(msg);
+
+ dbus_message_iter_init_append(reply, &args);
+ dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY, "s", &array_iter);
+
+ const char* capabilities[] = {"body", "body-hyperlinks", "body-images", "body-markup"};
+ for (int i = 0; i < 4; ++i) {
+ if (!dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, &capabilities[i])) {
+ fprintf(stderr, "Out Of Memory!\n");
+ exit(1);
+ }
+ }
+
+ dbus_message_iter_close_container(&args, &array_iter);
+
+ if (!dbus_connection_send(conn, reply, &serial)) {
+ fprintf(stderr, "Out Of Memory!\n");
+ exit(1);
+ }
+ dbus_connection_flush(conn);
+
+ dbus_message_unref(reply);
+}
+
+void reply_to_get_server_information_method_call(DBusMessage* msg, DBusConnection* conn) {
+ DBusMessage* reply;
+ DBusMessageIter args;
+ dbus_uint32_t serial = 0;
+
+ reply = dbus_message_new_method_return(msg);
+
+ dbus_message_iter_init_append(reply, &args);
+ const char* name = "NotifdbNotificationServer";
+ const char* vendor = "jakobst1n";
+ const char* version = "1.0";
+ const char* spec_version = "1.2";
+
+ if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &name) ||
+ !dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &vendor) ||
+ !dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &version) ||
+ !dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &spec_version)) {
+ fprintf(stderr, "Out Of Memory!\n");
+ exit(1);
+ }
+
+ if (!dbus_connection_send(conn, reply, &serial)) {
+ fprintf(stderr, "Out Of Memory!\n");
+ exit(1);
+ }
+ dbus_connection_flush(conn);
+
+ dbus_message_unref(reply);
+}
+
+void reply_to_get_notification_list(DBusMessage* msg, DBusConnection* conn) {
+ DBusMessage* reply;
+ DBusMessageIter args, array_iter, struct_iter;
+
+ reply = dbus_message_new_method_return(msg);
+ dbus_message_iter_init_append(reply, &args);
+
+ dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY,
+ DBUS_STRUCT_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_UINT32_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_TYPE_UINT32_AS_STRING
+ DBUS_TYPE_BOOLEAN_AS_STRING
+ DBUS_STRUCT_END_CHAR_AS_STRING,
+ &array_iter);
+
+ for (size_t i = 0; i < notifs->element_count; i++) {
+ if (notifs->list[i]) {
+ dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter);
+ dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &notifs->list[i]->id);
+ dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &notifs->list[i]->app_name);
+ dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &notifs->list[i]->summary);
+ dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &notifs->list[i]->body);
+ dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &notifs->list[i]->time);
+ dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_BOOLEAN, &notifs->list[i]->seen);
+ dbus_message_iter_close_container(&array_iter, &struct_iter);
+ }
+ }
+ dbus_message_iter_close_container(&args, &array_iter);
+
+ dbus_connection_send(conn, reply, NULL);
+ dbus_message_unref(reply);
+}
+
+void reply_to_get_unseen_count(DBusMessage* msg, DBusConnection* conn) {
+ DBusMessage* reply;
+ DBusMessageIter args;
+
+ unsigned int unseen_count = 0;
+ for (size_t i = 0; i < notifs->element_count; i++) {
+ if (notifs->list[i] && !notifs->list[i]->seen)
+ unseen_count++;
+ }
+
+ reply = dbus_message_new_method_return(msg);
+ dbus_message_iter_init_append(reply, &args);
+ if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &unseen_count)) {
+ fprintf(stderr, "Out Of Memory!\n");
+ exit(1);
+ }
+
+ dbus_connection_send(conn, reply, NULL);
+ dbus_message_unref(reply);
+}
+
+void reply_to_set_seen(DBusMessage* msg, DBusConnection* conn) {
+ DBusMessage* reply;
+ DBusMessageIter args;
+
+ int notif_id = -1;
+ unsigned int seen = false;
+
+ if (!dbus_message_iter_init(msg, &args)) {
+ fprintf(stderr, "Message has no arguments!\n");
+ return;
+ }
+
+ if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_UINT32) {
+ fprintf(stderr, "First argument is not an integer!\n");
+ return;
+ }
+
+ dbus_message_iter_get_basic(&args, &notif_id);
+
+ if (!dbus_message_iter_next(&args)) {
+ fprintf(stderr, "Message has too few arguments!\n");
+ return;
+ }
+
+ if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_BOOLEAN) {
+ fprintf(stderr, "Second argument is not an boolean!\n");
+ return;
+ }
+ dbus_message_iter_get_basic(&args, &seen);
+
+ printf("< Received notif_id: %d, seen: %d\n", notif_id, seen);
+ printf("< Current notification_id: %lu\n", notifs->element_count);
+
+ if (notif_id < notifs->element_count) {
+ printf("< Setting notification_log[%d]->seen to %d\n", notif_id, seen);
+ notifs->list[notif_id]->seen = seen;
+ } else {
+ fprintf(stderr, "Invalid notif_id: %d (max: %lu)\n", notif_id, notifs->element_count - 1);
+ }
+
+ reply = dbus_message_new_method_return(msg);
+
+ dbus_connection_send(conn, reply, NULL);
+ dbus_message_unref(reply);
+}
+
+
+void init_dbus(DBusConnection** conn) {
+ DBusError err;
+ dbus_error_init(&err);
+
+ *conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
+ if (dbus_error_is_set(&err)) {
+ fprintf(stderr, "Connection Error (%s)\n", err.message);
+ dbus_error_free(&err);
+ }
+ if (NULL == conn) {
+ fprintf(stderr, "Failed to connect to the session bus\n");
+ exit(1);
+ }
+ printf("Connected to the DBus session bus\n");
+
+ int ret = dbus_bus_request_name(*conn, DBUS_NOTIF_INTERFACE, DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
+ if (dbus_error_is_set(&err)) {
+ fprintf(stderr, "Name Error (%s)\n", err.message);
+ dbus_error_free(&err);
+ }
+ if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) {
+ fprintf(stderr, "Failed to request name\n");
+ exit(1);
+ }
+ printf("DBus name \"%s\" requested successfully\n", DBUS_NOTIF_INTERFACE);
+
+ ret = dbus_bus_request_name(*conn, DBUS_CLIENT_INTERFACE, DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
+ if (dbus_error_is_set(&err)) {
+ fprintf(stderr, "Name Error (%s)\n", err.message);
+ dbus_error_free(&err);
+ }
+ if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) {
+ fprintf(stderr, "Failed to request name\n");
+ exit(1);
+ }
+ printf("DBus name \"%s\" requested successfully\n", DBUS_CLIENT_INTERFACE);
+}
+
+void handle_dbus_message(DBusConnection* conn, DBusMessage* msg) {
+ if (NULL == msg) {
+ return;
+ }
+
+ if (dbus_message_is_method_call(msg, DBUS_NOTIF_INTERFACE, "Notify")) {
+ handle_notify_method_call(msg, conn);
+ } else if (dbus_message_is_method_call(msg, DBUS_NOTIF_INTERFACE, "GetCapabilities")) {
+ reply_to_get_capabilities_method_call(msg, conn);
+ } else if (dbus_message_is_method_call(msg, DBUS_NOTIF_INTERFACE, "GetServerInformation")) {
+ reply_to_get_server_information_method_call(msg, conn);
+ } else if (dbus_message_is_method_call(msg, DBUS_CLIENT_INTERFACE, DBUS_METHOD_GETLIST)) {
+ reply_to_get_notification_list(msg, conn);
+ } else if (dbus_message_is_method_call(msg, DBUS_CLIENT_INTERFACE, DBUS_METHOD_GETUNSEENCOUNT)) {
+ reply_to_get_unseen_count(msg, conn);
+ } else if (dbus_message_is_method_call(msg, DBUS_CLIENT_INTERFACE, DBUS_METHOD_SETSEEN)) {
+ reply_to_set_seen(msg, conn);
+ }
+
+ dbus_message_unref(msg);
+}
+
+void listen_dbus() {
+ DBusConnection* conn;
+ DBusMessage* msg;
+ init_dbus(&conn);
+
+ while (1) {
+ dbus_connection_read_write(conn, 0);
+ msg = dbus_connection_pop_message(conn);
+
+ if (NULL == msg) {
+ usleep(10000);
+ continue;
+ }
+
+ handle_dbus_message(conn, msg);
+ }
+}
+
+int main(int argc, char **argv) {
+ printf("Starting simple-notification-daemon\n");
+ notifs_list_init(&notifs);
+ listen_dbus();
+ return 0;
+}
diff --git a/snotifd.h b/snotifd.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/snotifd.h
diff --git a/term.h b/term.h
new file mode 100644
index 0000000..7460982
--- /dev/null
+++ b/term.h
@@ -0,0 +1,16 @@
+#ifndef TERM_H
+#define TERM_H
+
+#define KDIM "\x1B[2m"
+#define KITAL "\x1B[3m"
+
+#define KNRM "\x1B[0m"
+#define KRED "\x1B[31m"
+#define KGRN "\x1B[32m"
+#define KYEL "\x1B[33m"
+#define KBLU "\x1B[34m"
+#define KMAG "\x1B[35m"
+#define KCYN "\x1B[36m"
+#define KWHT "\x1B[37m"
+
+#endif