Dunst
Lightweight notification daemon
Loading...
Searching...
No Matches
dunstify.c
Go to the documentation of this file.
1/* SPDX-License-Identifier: BSD-3-Clause */
9
10#include <glib.h>
11#include <libnotify/notify.h>
12#include <locale.h>
13#include <stdbool.h>
14#include <stdlib.h>
15#include <string.h>
16#include <gdk-pixbuf/gdk-pixbuf.h>
17
18static gchar *appname = "dunstify";
19static gchar *summary = NULL;
20static gchar *body = NULL;
21static NotifyUrgency urgency = NOTIFY_URGENCY_NORMAL;
22static gchar *urgency_str = NULL;
23static gchar *category = NULL;
24static gchar **hint_strs = NULL;
25static gchar **action_strs = NULL;
26static gint timeout = NOTIFY_EXPIRES_DEFAULT;
27static gchar *icon = NULL;
28static gchar *raw_icon_path = NULL;
29static gboolean capabilities = false;
30static gboolean serverinfo = false;
31static gboolean printid = false;
32static guint32 replace_id = 0;
33static guint32 close_id = 0;
34static gboolean wait = false;
35static gchar **rest = NULL;
36static gboolean transient = false;
37static gboolean say_version = false;
38
39static GOptionEntry entries[] =
40{
41 { "urgency", 'u', 0, G_OPTION_ARG_STRING, &urgency_str, "The urgency of this notification", "URG" },
42 { "expire-time", 't', 0, G_OPTION_ARG_INT, &timeout, "The time in milliseconds until the notification expires", "TIMEOUT" },
43 { "app-name", 'a', 0, G_OPTION_ARG_STRING, &appname, "Name of your application", "NAME" },
44 { "icon", 'i', 0, G_OPTION_ARG_STRING, &icon, "An icon that should be displayed with the notification", "ICON" },
45 { "raw-icon", 'I', 0, G_OPTION_ARG_STRING, &raw_icon_path, "Path to the icon to be sent as raw image data", "RAW_ICON"},
46 { "category", 'c', 0, G_OPTION_ARG_STRING, &category, "The category of this notification", "TYPE" },
47 { "transient", 'e', 0, G_OPTION_ARG_INT, &transient, "Mark the notification as transient", NULL },
48 { "hint", 'h', 0, G_OPTION_ARG_STRING_ARRAY, &hint_strs, "User specified hints", "TYPE:NAME:VALUE" },
49 { "print-id", 'p', 0, G_OPTION_ARG_NONE, &printid, "Print id, which can be used to update/replace this notification", NULL },
50 { "replace-id", 'r', 0, G_OPTION_ARG_INT, &replace_id, "Set id of this notification", "ID" },
51 { "wait", 'w', 0, G_OPTION_ARG_NONE, &wait, "Block until notification is closed and print close reason", NULL },
52 { "action", 'A', 0, G_OPTION_ARG_STRING_ARRAY, &action_strs, "Actions the user can invoke", "ACTION" },
53 { "close", 'C', 0, G_OPTION_ARG_INT, &close_id, "Close the notification with the specified ID", "ID" },
54
55 // Legacy names
56 { "hints", 0, 0, G_OPTION_ARG_STRING_ARRAY, &hint_strs, "Legacy alias of '--hint'", "HINT" },
57 { "timeout", 0, 0, G_OPTION_ARG_INT, &timeout, "Legacy alias of '--expire-time'", "TIMEOUT" },
58 { "printid", 0, 0, G_OPTION_ARG_NONE, &printid, "Legacy alias of '--print-id'", NULL },
59 { "replace", 0, 0, G_OPTION_ARG_INT, &replace_id, "Legacy alias of '--replace-id'", "ID" },
60 { "block", 'b', 0, G_OPTION_ARG_NONE, &wait, "Legacy alias of '--wait'", NULL },
61 { "raw_icon", 0, 0, G_OPTION_ARG_STRING, &raw_icon_path, "Legacy alias of '--raw-icon'", NULL },
62
63 { "capabilities", 0, 0, G_OPTION_ARG_NONE, &capabilities, "Print the server capabilities and exit", NULL },
64 { "serverinfo", 0, 0, G_OPTION_ARG_NONE, &serverinfo, "Print server information and exit", NULL },
65 { "version", 'v', 0, G_OPTION_ARG_NONE, &say_version, "Print version information and exit", NULL },
66 { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &rest, NULL, NULL },
67 { NULL }
68};
69
70void die(int exit_value)
71{
72 if (notify_is_initted())
73 notify_uninit();
74 exit(exit_value);
75}
76
77void print_capabilities(void)
78{
79 GList *caps = notify_get_server_caps();
80 for (GList *iter = caps; iter; iter = iter->next) {
81 if (strlen(iter->data) > 0) {
82 g_print("%s\n", (char *)iter->data);
83 }
84 }
85}
86
87void print_serverinfo(void)
88{
89 char *name;
90 char *vendor;
91 char *version;
92 char *spec_version;
93
94 if (!notify_get_server_info(&name, &vendor, &version, &spec_version)) {
95 g_printerr("Unable to get server information");
96 exit(1);
97 }
98
99 g_print("name:%s\nvendor:%s\nversion:%s\nspec_version:%s\n", name,
100 vendor,
101 version,
102 spec_version);
103}
104
105void print_version(void)
106{
107 printf("dunstify (shipped with Dunst %s)\n", VERSION);
108}
109
110/*
111 * Glib leaves the option terminator "--" in the argv after parsing in some
112 * cases. This function gets the specified argv element ignoring the first
113 * terminator.
114 *
115 * See https://docs.gtk.org/glib/method.OptionContext.parse.html for details
116 */
117char *get_argv(char *argv[], int index)
118{
119 for (int i = 0; i <= index; i++) {
120 if (strcmp(argv[i], "--") == 0) {
121 return argv[index + 1];
122 }
123 }
124 return argv[index];
125}
126
127/* Count the number of arguments in argv excluding the terminator "--" */
128int count_args(char *argv[], int argc) {
129 for (int i = 0; i < argc; i++) {
130 if (strcmp(argv[i], "--") == 0)
131 return argc - 1;
132 }
133
134 return argc;
135}
136
137void parse_commandline(int argc, char *argv[])
138{
139 GError *error = NULL;
140 GOptionContext *context;
141
142 context = g_option_context_new("SUMMARY [BODY]");
143 g_option_context_add_main_entries(context, entries, NULL);
144 if (!g_option_context_parse(context, &argc, &argv, &error)){
145 g_printerr("Invalid commandline: %s\n", error->message);
146 exit(1);
147 }
148
149 g_option_context_free(context);
150
151 if (capabilities) {
152 print_capabilities();
153 die(0);
154 }
155
156 if (serverinfo) {
157 print_serverinfo();
158 die(0);
159 }
160
161 if (say_version) {
162 print_version();
163 die(0);
164 }
165
166 if (*appname == '\0') {
167 g_printerr("Provided appname was empty\n");
168 die(1);
169 }
170
171 if (rest != NULL && rest[0] != NULL) {
172 summary = rest[0];
173
174 if (rest[1] != NULL) {
175 body = g_strcompress(rest[1]);
176
177 if (rest[2] != NULL) {
178 g_printerr("Too many arguments!\n");
179 die(1);
180 }
181 }
182 }
183
184 if (summary == NULL && close_id < 1) {
185 g_printerr("I need at least a summary\n");
186 die(1);
187 }
188
189 if (urgency_str) {
190 switch (urgency_str[0]) {
191 case 'l':
192 case 'L':
193 case '0':
194 urgency = NOTIFY_URGENCY_LOW;
195 break;
196 case 'n':
197 case 'N':
198 case '1':
199 urgency = NOTIFY_URGENCY_NORMAL;
200 break;
201 case 'c':
202 case 'C':
203 case '2':
204 urgency = NOTIFY_URGENCY_CRITICAL;
205 break;
206 default:
207 g_printerr("Unknown urgency: %s\n", urgency_str);
208 g_printerr("Assuming normal urgency\n");
209 break;
210 }
211 }
212}
213
214gint get_id(NotifyNotification *n)
215{
216 gint32 id;
217 g_object_get(G_OBJECT(n), "id", &id, NULL);
218 return id;
219}
220
221void put_id(NotifyNotification *n, guint32 id)
222{
223 g_object_set(G_OBJECT(n), "id", id, NULL);
224}
225
226void actioned(NotifyNotification *n, char *a, gpointer foo)
227{
228 notify_notification_close(n, NULL);
229 g_print("%s\n", a);
230 die(0);
231}
232
233void closed(NotifyNotification *n, gpointer foo)
234{
235 g_print("%d\n", notify_notification_get_closed_reason(n));
236 die(0);
237}
238
239void add_action(NotifyNotification *n, char *str)
240{
241 char *action = str;
242 char *label = strchr(str, ',');
243
244 if (!label || *(label+1) == '\0') {
245 g_printerr("Malformed action. Expected \"action,label\", got \"%s\"", str);
246 return;
247 }
248
249 *label = '\0';
250 label++;
251
252 notify_notification_add_action(n, action, label, actioned, NULL, NULL);
253}
254
255void add_hint(NotifyNotification *n, char *str)
256{
257 char *type = str;
258 char *name = strchr(str, ':');
259 if (!name || *(name+1) == '\0') {
260 g_printerr("Malformed hint. Expected \"type:name:value\", got \"%s\"", str);
261 return;
262 }
263 *name = '\0';
264 name++;
265 char *value = strchr(name, ':');
266 if (!value || *(value+1) == '\0') {
267 g_printerr("Malformed hint. Expected \"type:name:value\", got \"%s\"", str);
268 return;
269 }
270 *value = '\0';
271 value++;
272
273 if (strcmp(type, "int") == 0)
274 notify_notification_set_hint_int32(n, name, atoi(value));
275 else if (strcmp(type, "double") == 0)
276 notify_notification_set_hint_double(n, name, atof(value));
277 else if (strcmp(type, "string") == 0)
278 notify_notification_set_hint_string(n, name, value);
279 else if (strcmp(type, "byte") == 0) {
280 gint h_byte = g_ascii_strtoull(value, NULL, 10);
281 if (h_byte < 0 || h_byte > 0xFF)
282 g_printerr("Not a byte: \"%s\"", value);
283 else
284 notify_notification_set_hint_byte(n, name, (guchar) h_byte);
285 } else
286 g_printerr("Malformed hint. Expected a type of int, double, string or byte, got %s\n", type);
287}
288
289int main(int argc, char *argv[])
290{
291 setlocale(LC_ALL, "");
292 g_set_prgname(argv[0]);
293
294 #if !GLIB_CHECK_VERSION(2,35,0)
295 g_type_init();
296 #endif
297
298 parse_commandline(argc, argv);
299
300 if (!notify_init(appname)) {
301 g_printerr("Unable to initialize libnotify\n");
302 die(1);
303 }
304
305 // NOTE: Needed to inform libnotify of the spec version
306 notify_get_server_info(NULL, NULL, NULL, NULL);
307
308 NotifyNotification *n = notify_notification_new(summary, body, icon);
309 notify_notification_set_timeout(n, timeout);
310 notify_notification_set_urgency(n, urgency);
311
312 if (category != NULL)
313 notify_notification_set_category(n, category);
314
315 if (transient)
316 notify_notification_set_hint(n, "transient", g_variant_new_boolean(TRUE));
317
318 GError *err = NULL;
319
320 if (raw_icon_path) {
321 GdkPixbuf *raw_icon = gdk_pixbuf_new_from_file(raw_icon_path, &err);
322
323 if(err) {
324 g_printerr("Unable to get raw icon: %s\n", err->message);
325 die(1);
326 }
327
328 notify_notification_set_image_from_pixbuf(n, raw_icon);
329 }
330
331 if (close_id > 0) {
332 put_id(n, close_id);
333 notify_notification_close(n, &err);
334 if (err) {
335 g_printerr("Unable to close notification: %s\n", err->message);
336 die(1);
337 }
338 die(0);
339 }
340
341 if (replace_id > 0) {
342 put_id(n, replace_id);
343 }
344
345 GMainLoop *l = NULL;
346
347 if (wait || action_strs) {
348 l = g_main_loop_new(NULL, false);
349 g_signal_connect(n, "closed", G_CALLBACK(closed), NULL);
350 }
351
352 if (action_strs)
353 for (int i = 0; action_strs[i]; i++) {
354 add_action(n, action_strs[i]);
355 }
356
357 if (hint_strs)
358 for (int i = 0; hint_strs[i]; i++) {
359 add_hint(n, hint_strs[i]);
360 }
361
362
363 notify_notification_show(n, &err);
364 if (err) {
365 g_printerr("Unable to send notification: %s\n", err->message);
366 die(1);
367 }
368
369 if (printid) {
370 g_print("%d\n", get_id(n));
371 fflush(stdout);
372 }
373
374 if (wait || action_strs)
375 g_main_loop_run(l);
376
377 g_object_unref(G_OBJECT (n));
378
379 die(0);
380}
urgency
Representing the urgencies according to the notification spec.