dunst/dunstify.c
Nikos Tsipinakis 51a98b8fd0 Dunstify: Print id before blocking
Print the notification id, if requested, before blocking to catch action
return or NotificationClosed signal since once of those events happens
the program will exit without further action, bypassing the id printing
logic.
2017-11-22 17:49:50 +02:00

333 lines
9.4 KiB
C

#include <glib.h>
#include <libnotify/notify.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
static gchar *appname = "dunstify";
static gchar *summary = NULL;
static gchar *body = NULL;
static NotifyUrgency urgency = NOTIFY_URGENCY_NORMAL;
static gchar *urgency_str = NULL;
static gchar **hint_strs = NULL;
static gchar **action_strs = NULL;
static gint timeout = NOTIFY_EXPIRES_DEFAULT;
static gchar *icon = NULL;
static gchar *raw_icon_path = NULL;
static gboolean capabilities = false;
static gboolean serverinfo = false;
static gboolean printid = false;
static guint32 replace_id = 0;
static guint32 close_id = 0;
static gboolean block = false;
static GOptionEntry entries[] =
{
{ "appname", 'a', 0, G_OPTION_ARG_STRING, &appname, "Name of your application", "NAME" },
{ "urgency", 'u', 0, G_OPTION_ARG_STRING, &urgency_str, "The urgency of this notification", "URG" },
{ "hints", 'h', 0, G_OPTION_ARG_STRING_ARRAY, &hint_strs, "User specified hints", "HINT" },
{ "action", 'A', 0, G_OPTION_ARG_STRING_ARRAY, &action_strs, "Actions the user can invoke", "ACTION" },
{ "timeout", 't', 0, G_OPTION_ARG_INT, &timeout, "The time until the notification expires", "TIMEOUT" },
{ "icon", 'i', 0, G_OPTION_ARG_STRING, &icon, "An Icon that should be displayed with the notification", "ICON" },
{ "raw_icon", 'I', 0, G_OPTION_ARG_STRING, &raw_icon_path, "Path to the icon to be sent as raw image data", "RAW_ICON"},
{ "capabilities", 'c', 0, G_OPTION_ARG_NONE, &capabilities, "Print the server capabilities and exit", NULL},
{ "serverinfo", 's', 0, G_OPTION_ARG_NONE, &serverinfo, "Print server information and exit", NULL},
{ "printid", 'p', 0, G_OPTION_ARG_NONE, &printid, "Print id, which can be used to update/replace this notification", NULL},
{ "replace", 'r', 0, G_OPTION_ARG_INT, &replace_id, "Set id of this notification.", "ID"},
{ "close", 'C', 0, G_OPTION_ARG_INT, &close_id, "Set id of this notification.", "ID"},
{ "block", 'b', 0, G_OPTION_ARG_NONE, &block, "Block until notification is closed and print close reason", NULL},
{ NULL }
};
void die(int exit_value)
{
if (notify_is_initted())
notify_uninit();
exit(exit_value);
}
void print_capabilities(void)
{
GList *caps = notify_get_server_caps();
for (GList *iter = caps; iter; iter = iter->next) {
if (strlen(iter->data) > 0) {
g_print("%s\n", (char *)iter->data);
}
}
}
void print_serverinfo(void)
{
char *name;
char *vendor;
char *version;
char *spec_version;
if (!notify_get_server_info(&name, &vendor, &version, &spec_version)) {
g_printerr("Unable to get server information");
exit(1);
}
g_print("name:%s\nvendor:%s\nversion:%s\nspec_version:%s\n", name,
vendor,
version,
spec_version);
}
void parse_commandline(int argc, char *argv[])
{
GError *error = NULL;
GOptionContext *context;
context = g_option_context_new("- Dunstify");
g_option_context_add_main_entries(context, entries, NULL);
if (!g_option_context_parse(context, &argc, &argv, &error)){
g_printerr("Invalid commandline: %s\n", error->message);
exit(1);
}
g_option_context_free(context);
if (capabilities) {
print_capabilities();
die(0);
}
if (serverinfo) {
print_serverinfo();
die(0);
}
if (argc < 2 && close_id < 1) {
g_printerr("I need at least a summary\n");
die(1);
} else if (argc < 2) {
summary = g_strdup("These are not the summaries you are looking for");
} else {
summary = g_strdup(argv[1]);
}
if (argc > 2) {
body = g_strdup(argv[2]);
}
if (urgency_str) {
switch (urgency_str[0]) {
case 'l':
case 'L':
case '0':
urgency = NOTIFY_URGENCY_LOW;
break;
case 'n':
case 'N':
case '1':
urgency = NOTIFY_URGENCY_NORMAL;
break;
case 'c':
case 'C':
case '2':
urgency = NOTIFY_URGENCY_CRITICAL;
break;
default:
g_printerr("Unknown urgency: %s\n", urgency_str);
g_printerr("Assuming normal urgency\n");
break;
}
}
}
typedef struct _NotifyNotificationPrivate
{
guint32 id;
char *app_name;
char *summary;
char *body;
/* NULL to use icon data. Anything else to have server lookup icon */
char *icon_name;
/*
* -1 = use server default
* 0 = never timeout
* > 0 = Number of milliseconds before we timeout
*/
gint timeout;
GSList *actions;
GHashTable *action_map;
GHashTable *hints;
gboolean has_nondefault_actions;
gboolean updates_pending;
gulong proxy_signal_handler;
gint closed_reason;
} knickers;
int get_id(NotifyNotification *n)
{
knickers *kn = n->priv;
/* I'm sorry for taking a peek */
return kn->id;
}
void put_id(NotifyNotification *n, guint32 id)
{
knickers *kn = n->priv;
/* And know I'm putting stuff into
* your knickers. I'm sorry.
* I'm so sorry.
* */
kn->id = id;
}
void actioned(NotifyNotification *n, char *a, gpointer foo)
{
notify_notification_close(n, NULL);
g_print("%s\n", a);
die(0);
}
void closed(NotifyNotification *n, gpointer foo)
{
g_print("%d\n", notify_notification_get_closed_reason(n));
die(0);
}
void add_action(NotifyNotification *n, char *str)
{
char *action = str;
char *label = strchr(str, ',');
if (!label || *(label+1) == '\0') {
g_printerr("Malformed action. Excpected \"action,label\", got \"%s\"", str);
return;
}
*label = '\0';
label++;
notify_notification_add_action(n, action, label, actioned, NULL, NULL);
}
void add_hint(NotifyNotification *n, char *str)
{
char *type = str;
char *name = strchr(str, ':');
if (!name || *(name+1) == '\0') {
g_printerr("Malformed hint. Expected \"type:name:value\", got \"%s\"", str);
return;
}
*name = '\0';
name++;
char *value = strchr(name, ':');
if (!value || *(value+1) == '\0') {
g_printerr("Malformed hint. Expected \"type:name:value\", got \"%s\"", str);
return;
}
*value = '\0';
value++;
if (strcmp(type, "int") == 0)
notify_notification_set_hint_int32(n, name, atoi(value));
else if (strcmp(type, "double") == 0)
notify_notification_set_hint_double(n, name, atof(value));
else if (strcmp(type, "string") == 0)
notify_notification_set_hint_string(n, name, value);
else if (strcmp(type, "byte") == 0) {
gint h_byte = g_ascii_strtoull(value, NULL, 10);
if (h_byte < 0 || h_byte > 0xFF)
g_printerr("Not a byte: \"%s\"", value);
else
notify_notification_set_hint_byte(n, name, (guchar) h_byte);
} else
g_printerr("Malformed hint. Expected a type of int, double, string or byte, got %s\n", type);
}
int main(int argc, char *argv[])
{
#if !GLIB_CHECK_VERSION(2,35,0)
g_type_init();
#endif
parse_commandline(argc, argv);
if (!notify_init(appname)) {
g_printerr("Unable to initialize libnotify\n");
die(1);
}
NotifyNotification *n;
n = notify_notification_new(summary, body, icon);
notify_notification_set_timeout(n, timeout);
notify_notification_set_urgency(n, urgency);
GError *err = NULL;
if (raw_icon_path) {
GdkPixbuf *raw_icon = gdk_pixbuf_new_from_file(raw_icon_path, &err);
if(err) {
g_printerr("Unable to get raw icon: %s\n", err->message);
die(1);
}
notify_notification_set_image_from_pixbuf(n, raw_icon);
}
if (close_id > 0) {
put_id(n, close_id);
notify_notification_close(n, &err);
if (err) {
g_printerr("Unable to close notification: %s\n", err->message);
die(1);
}
die(0);
}
if (replace_id > 0) {
put_id(n, replace_id);
}
GMainLoop *l = NULL;
if (block || action_strs) {
l = g_main_loop_new(NULL, false);
g_signal_connect(n, "closed", G_CALLBACK(closed), NULL);
}
if (action_strs)
for (int i = 0; action_strs[i]; i++) {
add_action(n, action_strs[i]);
}
if (hint_strs)
for (int i = 0; hint_strs[i]; i++) {
add_hint(n, hint_strs[i]);
}
notify_notification_show(n, &err);
if (err) {
g_printerr("Unable to send notification: %s\n", err->message);
die(1);
}
if (printid)
g_print("%d\n", get_id(n));
if (block || action_strs)
g_main_loop_run(l);
g_object_unref(G_OBJECT (n));
die(0);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */