1 /*
2 * This file is part of YAD.
3 *
4 * YAD is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * YAD is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with YAD. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Copyright (C) 2008-2019, Victor Ananjevsky <ananasik@gmail.com>
18 */
19
20 #include <sys/stat.h>
21 #include <fcntl.h>
22 #include <time.h>
23 #include <string.h>
24 #include <unistd.h>
25
26 #include "yad.h"
27
28 typedef struct {
29 gchar *name;
30 gchar *action;
31 gchar *icon;
32 } MenuData;
33
34 static GtkStatusIcon *status_icon;
35
36 static gchar *icon = NULL;
37 static gchar *action = NULL;
38
39 static GSList *menu_data = NULL;
40
41 static gint exit_code;
42
43 static void popup_menu_cb (GtkStatusIcon *, guint, guint, gpointer);
44
45 static void
free_menu_data(gpointer data)46 free_menu_data (gpointer data)
47 {
48 MenuData *m = (MenuData *) data;
49
50 if (m) {
51 g_free (m->name);
52 g_free (m->action);
53 g_free (m->icon);
54 g_free (m);
55 }
56 }
57
58 static void
parse_menu_str(gchar * str)59 parse_menu_str (gchar * str)
60 {
61 gchar **menu_vals;
62 gint i = 0;
63
64 if (menu_data)
65 {
66 g_slist_free_full (menu_data, free_menu_data);
67 menu_data = NULL;
68 }
69
70 menu_vals = g_strsplit (str, options.common_data.separator, -1);
71
72 while (menu_vals[i] != NULL)
73 {
74 MenuData *mdata = g_new0 (MenuData, 1);
75 gchar **s = g_strsplit (menu_vals[i], options.common_data.item_separator, 3);
76
77 if (s[0])
78 {
79 YadStock sit;
80 if (stock_lookup (s[0], &sit))
81 {
82 mdata->name = g_strdup (sit.label);
83 mdata->icon = g_strdup (sit.icon);
84 }
85 else
86 mdata->name = g_strdup (s[0]);
87 if (s[1])
88 {
89 mdata->action = g_strdup (s[1]);
90 if (s[2])
91 mdata->icon = g_strdup (s[2]);
92 }
93 }
94 menu_data = g_slist_append (menu_data, mdata);
95 g_strfreev (s);
96 i++;
97 }
98
99 g_strfreev (menu_vals);
100 }
101
102 static void
timeout_cb(gpointer data)103 timeout_cb (gpointer data)
104 {
105 exit_code = YAD_RESPONSE_TIMEOUT;
106 gtk_main_quit ();
107 }
108
109 static void
set_icon(void)110 set_icon (void)
111 {
112 GdkPixbuf *pixbuf;
113 GError *err = NULL;
114
115 if (icon == NULL)
116 {
117 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
118 gtk_status_icon_set_from_icon_name (status_icon, "yad");
119 G_GNUC_END_IGNORE_DEPRECATIONS;
120 return;
121 }
122
123 if (g_file_test (icon, G_FILE_TEST_EXISTS))
124 {
125 gint isize = (options.common_data.icon_size > 0) ? options.common_data.icon_size : 16;
126
127 pixbuf = gdk_pixbuf_new_from_file_at_scale (icon, isize, isize, TRUE, &err);
128 if (err)
129 {
130 g_printerr (_("Could not load notification icon '%s': %s\n"), icon, err->message);
131 g_clear_error (&err);
132 }
133 if (pixbuf)
134 {
135 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
136 gtk_status_icon_set_from_pixbuf (status_icon, pixbuf);
137 G_GNUC_END_IGNORE_DEPRECATIONS;
138 g_object_unref (pixbuf);
139 }
140 else
141 {
142 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
143 gtk_status_icon_set_from_icon_name (status_icon, "yad");
144 G_GNUC_END_IGNORE_DEPRECATIONS;
145 }
146 }
147 else
148 {
149 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
150 gtk_status_icon_set_from_icon_name (status_icon, icon);
151 G_GNUC_END_IGNORE_DEPRECATIONS;
152 }
153 }
154
155 static gboolean
activate_cb(GtkWidget * widget,YadData * data)156 activate_cb (GtkWidget * widget, YadData * data)
157 {
158 if ((action == NULL && !options.common_data.listen) || (action && g_ascii_strcasecmp (action, "quit") == 0))
159 {
160 exit_code = YAD_RESPONSE_OK;
161 gtk_main_quit ();
162 }
163 else if (action)
164 {
165 if (g_ascii_strcasecmp (action, "menu") == 0)
166 popup_menu_cb (GTK_STATUS_ICON (widget), 1, GDK_CURRENT_TIME, data);
167 else
168 run_command_async (action);
169 }
170
171 return TRUE;
172 }
173
174 static gboolean
middle_quit_cb(GtkStatusIcon * icon,GdkEventButton * ev,gpointer data)175 middle_quit_cb (GtkStatusIcon * icon, GdkEventButton * ev, gpointer data)
176 {
177 if (ev->button == 2)
178 {
179 if (options.data.escape_ok)
180 exit_code = YAD_RESPONSE_OK;
181 else
182 exit_code = YAD_RESPONSE_ESC;
183 gtk_main_quit ();
184 }
185
186 return FALSE;
187 }
188
189 static void
popup_menu_item_activate_cb(GtkWidget * w,gpointer data)190 popup_menu_item_activate_cb (GtkWidget * w, gpointer data)
191 {
192 gchar *cmd = (gchar *) data;
193
194 if (cmd)
195 {
196 if (g_ascii_strcasecmp (g_strstrip (cmd), "quit") == 0)
197 {
198 exit_code = YAD_RESPONSE_OK;
199 gtk_main_quit ();
200 }
201 else
202 run_command_async (cmd);
203 }
204 }
205
206 static void
popup_menu_cb(GtkStatusIcon * icon,guint button,guint activate_time,gpointer data)207 popup_menu_cb (GtkStatusIcon *icon, guint button, guint activate_time, gpointer data)
208 {
209 GtkWidget *menu;
210 GtkWidget *item;
211 GSList *m;
212
213 if (!menu_data)
214 return;
215
216 menu = gtk_menu_new ();
217 gtk_menu_set_reserve_toggle_size (GTK_MENU (menu), FALSE);
218
219 for (m = menu_data; m; m = m->next)
220 {
221 MenuData *d = (MenuData *) m->data;
222
223 if (d->name)
224 {
225 GtkWidget *b, *i, *l;
226
227 item = gtk_menu_item_new ();
228 b = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
229
230 if (d->icon)
231 {
232 GdkPixbuf *pb = get_pixbuf (d->icon, YAD_SMALL_ICON, TRUE);
233 if (pb)
234 {
235 i = gtk_image_new_from_pixbuf (pb);
236 gtk_container_add (GTK_CONTAINER (b), i);
237 g_object_unref (pb);
238 }
239 }
240 l = gtk_label_new_with_mnemonic (d->name);
241 gtk_label_set_xalign (GTK_LABEL (l), 0.0);
242 gtk_label_set_mnemonic_widget (GTK_LABEL (l), item);
243 gtk_box_pack_end (GTK_BOX (b), l, TRUE, TRUE, 0);
244
245 gtk_container_add (GTK_CONTAINER (item), b);
246
247 g_signal_connect (GTK_MENU_ITEM (item), "activate",
248 G_CALLBACK (popup_menu_item_activate_cb), (gpointer) d->action);
249 }
250 else
251 item = gtk_separator_menu_item_new ();
252
253 gtk_widget_show_all (item);
254 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
255 }
256
257 gtk_menu_popup_at_pointer (GTK_MENU (menu), NULL);
258 }
259
260 static gboolean
handle_stdin(GIOChannel * channel,GIOCondition condition,gpointer data)261 handle_stdin (GIOChannel * channel, GIOCondition condition, gpointer data)
262 {
263 if ((condition & G_IO_IN) != 0)
264 {
265 GString *string;
266 GError *err = NULL;
267
268 string = g_string_new (NULL);
269 while (channel->is_readable == FALSE)
270 usleep (100);
271
272 do
273 {
274 gint status;
275 gchar *command = NULL, *value = NULL, **args;
276
277 do
278 {
279 status = g_io_channel_read_line_string (channel, string, NULL, &err);
280
281 while (gdk_events_pending ())
282 gtk_main_iteration ();
283 }
284 while (status == G_IO_STATUS_AGAIN);
285
286 if (status != G_IO_STATUS_NORMAL)
287 {
288 if (err)
289 {
290 g_printerr ("yad_notification_handle_stdin(): %s\n", err->message);
291 g_error_free (err);
292 err = NULL;
293 }
294 /* stop handling but not exit */
295 g_io_channel_shutdown (channel, TRUE, NULL);
296 return FALSE;
297 }
298
299 strip_new_line (string->str);
300 if (!string->str[0])
301 continue;
302
303 args = g_strsplit (string->str, ":", 2);
304 command = g_strdup (args[0]);
305 if (args[1])
306 value = g_strdup (args[1]);
307 g_strfreev (args);
308 if (value)
309 g_strstrip (value);
310
311 if (!g_ascii_strcasecmp (command, "icon") && value)
312 {
313 g_free (icon);
314 icon = g_strdup (value);
315
316 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
317 if (gtk_status_icon_get_visible (status_icon) && gtk_status_icon_is_embedded (status_icon))
318 set_icon ();
319 G_GNUC_END_IGNORE_DEPRECATIONS;
320 }
321 else if (!g_ascii_strcasecmp (command, "tooltip"))
322 {
323 if (g_utf8_validate (value, -1, NULL))
324 {
325 gchar *message = g_strcompress (value);
326 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
327 if (!options.data.no_markup)
328 gtk_status_icon_set_tooltip_markup (status_icon, message);
329 else
330 gtk_status_icon_set_tooltip_text (status_icon, message);
331 G_GNUC_END_IGNORE_DEPRECATIONS;
332 g_free (message);
333 }
334 else
335 g_printerr (_("Invalid UTF-8 in tooltip!\n"));
336 }
337 else if (!g_ascii_strcasecmp (command, "visible"))
338 {
339 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
340 if (!g_ascii_strcasecmp (value, "false"))
341 gtk_status_icon_set_visible (status_icon, FALSE);
342 else
343 gtk_status_icon_set_visible (status_icon, TRUE);
344 G_GNUC_END_IGNORE_DEPRECATIONS;
345 }
346 else if (!g_ascii_strcasecmp (command, "action"))
347 {
348 g_free (action);
349 if (value)
350 action = g_strdup (value);
351 }
352 else if (!g_ascii_strcasecmp (command, "quit"))
353 {
354 exit_code = YAD_RESPONSE_OK;
355 gtk_main_quit ();
356 }
357 else if (!g_ascii_strcasecmp (command, "menu"))
358 {
359 if (value)
360 parse_menu_str (value);
361 }
362 else
363 g_printerr (_("Unknown command '%s'\n"), command);
364
365 g_free (command);
366 g_free (value);
367 }
368 while (g_io_channel_get_buffer_condition (channel) == G_IO_IN);
369 g_string_free (string, TRUE);
370 }
371
372 if ((condition & G_IO_HUP) != 0)
373 {
374 g_io_channel_shutdown (channel, TRUE, NULL);
375 gtk_main_quit ();
376 return FALSE;
377 }
378
379 return TRUE;
380 }
381
382 gint
yad_notification_run()383 yad_notification_run ()
384 {
385 GIOChannel *channel = NULL;
386
387 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
388 status_icon = gtk_status_icon_new ();
389 G_GNUC_END_IGNORE_DEPRECATIONS;
390
391 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
392 if (options.data.dialog_text)
393 {
394 if (!options.data.no_markup)
395 gtk_status_icon_set_tooltip_markup (status_icon, options.data.dialog_text);
396 else
397 gtk_status_icon_set_tooltip_text (status_icon, options.data.dialog_text);
398 }
399 else
400 gtk_status_icon_set_tooltip_text (status_icon, _("Yad notification"));
401 G_GNUC_END_IGNORE_DEPRECATIONS;
402
403 if (options.data.dialog_image)
404 icon = g_strdup (options.data.dialog_image);
405 if (options.common_data.command)
406 action = g_strdup (options.common_data.command);
407
408 set_icon ();
409
410 g_signal_connect (status_icon, "activate", G_CALLBACK (activate_cb), NULL);
411 g_signal_connect (status_icon, "popup_menu", G_CALLBACK (popup_menu_cb), NULL);
412
413 if (options.notification_data.menu)
414 parse_menu_str (options.notification_data.menu);
415
416 /* quit on middle click (like press Esc) */
417 if (options.notification_data.middle)
418 g_signal_connect (status_icon, "button-press-event", G_CALLBACK (middle_quit_cb), NULL);
419
420 if (options.common_data.listen)
421 {
422 channel = g_io_channel_unix_new (0);
423 if (channel)
424 {
425 g_io_channel_set_encoding (channel, NULL, NULL);
426 g_io_channel_set_flags (channel, G_IO_FLAG_NONBLOCK, NULL);
427 g_io_add_watch (channel, G_IO_IN | G_IO_HUP, handle_stdin, NULL);
428 }
429 }
430
431 /* Show icon and wait */
432 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
433 gtk_status_icon_set_visible (status_icon, !options.notification_data.hidden);
434 G_GNUC_END_IGNORE_DEPRECATIONS;
435
436 if (options.data.timeout > 0)
437 g_timeout_add_seconds (options.data.timeout, (GSourceFunc) timeout_cb, NULL);
438
439 gtk_main ();
440
441 return exit_code;
442 }
443