1 /*
2 * alarm-applet.c -- Alarm Clock applet bootstrap
3 *
4 * Copyright (C) 2007-2008 Johannes H. Jensen <joh@pseudoberries.com>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 *
20 * Authors:
21 * Johannes H. Jensen <joh@pseudoberries.com>
22 */
23
24 #include <stdlib.h>
25
26 #include "alarm-applet.h"
27
28 #include "alarm.h"
29 #include "alarm-settings.h"
30
31 /*
32 * DEFINTIIONS {{
33 */
34
35 static const gchar *supported_sound_mime_types [] = {
36 "audio",
37 "video",
38 "application/ogg",
39 NULL
40 };
41
42 GHashTable *app_command_map = NULL;
43
44 /*
45 * }} DEFINTIIONS
46 */
47
48 /*
49 * Snooze any triggered alarms.
50 *
51 * Returns the number of snoozed alarms
52 */
53 guint
alarm_applet_alarms_snooze(AlarmApplet * applet)54 alarm_applet_alarms_snooze (AlarmApplet *applet)
55 {
56 GList *l;
57 Alarm *a;
58 guint n_snoozed = 0;
59
60 g_debug ("Snoozing alarms...");
61
62 // Loop through alarms and snooze all triggered ones
63 for (l = applet->alarms; l; l = l->next) {
64 a = ALARM (l->data);
65
66 if (a->triggered) {
67 alarm_applet_alarm_snooze (applet, a);
68 n_snoozed++;
69 }
70 }
71
72 // Reset the triggered counter
73 applet->n_triggered = 0;
74
75 // Update status icon
76 alarm_applet_status_update (applet);
77
78 return n_snoozed;
79 }
80
81 /*
82 * Stop any running (read: playing sound) alarms.
83 */
84 guint
alarm_applet_alarms_stop(AlarmApplet * applet)85 alarm_applet_alarms_stop (AlarmApplet *applet)
86 {
87 GList *l;
88 Alarm *a;
89 guint n_stopped = 0;
90
91 g_debug ("Stopping alarms...");
92
93 // Loop through alarms and clear all of 'em
94 for (l = applet->alarms; l; l = l->next) {
95 a = ALARM (l->data);
96
97 if (a->triggered) {
98 alarm_clear (a);
99 n_stopped++;
100 }
101 }
102
103 // Reset the triggered counter
104 applet->n_triggered = 0;
105
106 // Update status icon
107 alarm_applet_status_update (applet);
108
109 return n_stopped;
110 }
111
112 /**
113 * Snooze an alarm, according to UI settings
114 */
115 void
alarm_applet_alarm_snooze(AlarmApplet * applet,Alarm * alarm)116 alarm_applet_alarm_snooze (AlarmApplet *applet, Alarm *alarm)
117 {
118 guint mins = applet->snooze_mins;
119
120 if (alarm->type == ALARM_TYPE_CLOCK) {
121 // Clocks always snooze for 9 minutes
122 mins = ALARM_STD_SNOOZE;
123 }
124
125 g_debug ("AlarmApplet: snooze '%s' for %d minutes", alarm->message, mins);
126
127 alarm_snooze (alarm, mins * 60);
128
129 // Update status icon
130 alarm_applet_status_update (applet);
131 }
132
133 /**
134 * Stop an alarm, keeping UI consistent
135 */
136 void
alarm_applet_alarm_stop(AlarmApplet * applet,Alarm * alarm)137 alarm_applet_alarm_stop (AlarmApplet *applet, Alarm *alarm)
138 {
139 g_debug ("Stopping alarm #%d...", alarm->id);
140
141 // Stop the alarm
142 alarm_clear (alarm);
143
144 // Update status icon
145 alarm_applet_status_update (applet);
146 }
147
148
149
150 /*
151 * Sounds list {{
152 */
153
154 // Load sounds into list
155 // TODO: Refactor to use a GHashTable with string hash
156 void
alarm_applet_sounds_load(AlarmApplet * applet)157 alarm_applet_sounds_load (AlarmApplet *applet)
158 {
159 Alarm *alarm;
160 AlarmListEntry *entry;
161 GList *l, *l2;
162 gboolean found;
163
164 const gchar* const *sysdirs;
165 gchar *sounds_dir = NULL;
166 gchar *tmp;
167 gint i;
168
169 //g_assert (applet->alarms);
170
171 // Free old list
172 if (applet->sounds != NULL)
173 alarm_list_entry_list_free (&(applet->sounds));
174
175 // Locate gnome sounds
176 sysdirs = g_get_system_data_dirs ();
177 for (i = 0; !sounds_dir && sysdirs[i] != NULL; i++) {
178 tmp = g_build_filename(sysdirs[i], "sounds/gnome/default/alerts", NULL);
179 if (g_file_test (tmp, G_FILE_TEST_IS_DIR)) {
180 // Load stock sounds
181 g_debug ("AlarmApplet: sounds_load: Found %s!", tmp);
182 sounds_dir = g_strdup_printf ("file://%s", tmp);
183 applet->sounds = alarm_list_entry_list_new (sounds_dir, supported_sound_mime_types);
184 g_free (sounds_dir);
185 }
186 g_free(tmp);
187 }
188
189 if (!sounds_dir) {
190 g_warning ("AlarmApplet: Could not locate sounds!");
191 }
192
193 // Load custom sounds from alarms
194 for (l = applet->alarms; l != NULL; l = l->next) {
195 alarm = ALARM (l->data);
196 found = FALSE;
197 for (l2 = applet->sounds; l2 != NULL; l2 = l2->next) {
198 entry = (AlarmListEntry *)l2->data;
199 if (strcmp (alarm->sound_file, entry->data) == 0) {
200 // FOUND
201 found = TRUE;
202 break;
203 }
204 }
205
206 if (!found) {
207 // Add to list
208 entry = alarm_list_entry_new_file (alarm->sound_file, NULL, NULL);
209 if (entry) {
210 applet->sounds = g_list_append (applet->sounds, entry);
211 }
212 }
213 }
214 }
215
216 // Notify callback for changes to an alarm's sound_file
217 static void
alarm_sound_file_changed(GObject * object,GParamSpec * param,gpointer data)218 alarm_sound_file_changed (GObject *object,
219 GParamSpec *param,
220 gpointer data)
221 {
222 Alarm *alarm = ALARM (object);
223 AlarmApplet *applet = (AlarmApplet *)data;
224
225 g_debug ("alarm_sound_file_changed: #%d", alarm->id);
226
227 // Reload sounds list
228 alarm_applet_sounds_load (applet);
229 }
230
231
232 /*
233 * }} Sounds list
234 */
235
236
237 /*
238 * Apps list {{
239 */
240
241 static gchar *
gnome_da_xml_get_string(const xmlNode * parent,const gchar * val_name)242 gnome_da_xml_get_string (const xmlNode *parent, const gchar *val_name)
243 {
244 const gchar * const *sys_langs;
245 xmlChar *node_lang;
246 xmlNode *element;
247 gchar *ret_val = NULL;
248 xmlChar *xml_val_name;
249 gint len;
250 gint i;
251
252 g_return_val_if_fail (parent != NULL, ret_val);
253 g_return_val_if_fail (parent->children != NULL, ret_val);
254 g_return_val_if_fail (val_name != NULL, ret_val);
255
256 #if GLIB_CHECK_VERSION (2, 6, 0)
257 sys_langs = g_get_language_names ();
258 #endif
259
260 xml_val_name = xmlCharStrdup (val_name);
261 len = xmlStrlen (xml_val_name);
262
263 for (element = parent->children; element != NULL; element = element->next) {
264 if (!xmlStrncmp (element->name, xml_val_name, len)) {
265 node_lang = xmlNodeGetLang (element);
266
267 if (node_lang == NULL) {
268 ret_val = (gchar *) xmlNodeGetContent (element);
269 } else {
270 for (i = 0; sys_langs[i] != NULL; i++) {
271 if (!strcmp (sys_langs[i], (const char *)node_lang)) {
272 ret_val = (gchar *) xmlNodeGetContent (element);
273 // since sys_langs is sorted from most desirable to
274 // least desirable, exit at first match
275 break;
276 }
277 }
278 }
279 xmlFree (node_lang);
280 }
281 }
282
283 xmlFree (xml_val_name);
284 return ret_val;
285 }
286
287 static const gchar *
get_app_command(const gchar * app)288 get_app_command (const gchar *app)
289 {
290 // TODO: Shouldn't be a global variable
291 if (app_command_map == NULL) {
292 app_command_map = g_hash_table_new (g_str_hash, g_str_equal);
293
294 // `rhythmbox-client --play' doesn't actually start playing unless
295 // Rhythmbox is already running. Sounds like a Bug.
296 g_hash_table_insert (app_command_map,
297 "rhythmbox", "rhythmbox-client --play");
298
299 g_hash_table_insert (app_command_map,
300 "banshee", "banshee --play");
301
302 // Note that totem should already be open with a file for this to work.
303 g_hash_table_insert (app_command_map,
304 "totem", "totem --play");
305
306 // Muine crashes and doesn't seem to have any play command
307 /*g_hash_table_insert (app_command_map,
308 "muine", "muine");*/
309 }
310
311 return g_hash_table_lookup (app_command_map, app);
312 }
313
314 // Load stock apps into list
315 void
alarm_applet_apps_load(AlarmApplet * applet)316 alarm_applet_apps_load (AlarmApplet *applet)
317 {
318 AlarmListEntry *entry;
319 gchar *filename, *name, *icon, *command;
320 xmlDoc *xml_doc;
321 xmlNode *root, *section, *element;
322 gchar *executable;
323 const gchar *tmp;
324 const gchar* const *sysdirs;
325 gint i;
326
327 if (applet->apps != NULL)
328 alarm_list_entry_list_free (&(applet->apps));
329
330 // Locate g-d-a.xml
331 sysdirs = g_get_system_data_dirs ();
332 for (i = 0; sysdirs[i] != NULL; i++) {
333 // We'll get the default media players from g-d-a.xml
334 // from gnome-control-center
335 filename = g_build_filename (sysdirs[i],
336 "gnome-control-center",
337 "default-apps",
338 "gnome-default-applications.xml",
339 NULL);
340
341 if (g_file_test (filename, G_FILE_TEST_EXISTS)) {
342 xml_doc = xmlParseFile (filename);
343
344 if (!xml_doc) {
345 g_warning ("Could not load %s.", filename);
346 continue;
347 }
348
349 root = xmlDocGetRootElement (xml_doc);
350
351 for (section = root->children; section != NULL; section = section->next) {
352 if (!xmlStrncmp (section->name, (const xmlChar *)"media-players", 13)) {
353 for (element = section->children; element != NULL; element = element->next) {
354 if (!xmlStrncmp (element->name, (const xmlChar *)"media-player", 12)) {
355 executable = gnome_da_xml_get_string (element, "executable");
356 if (is_executable_valid (executable)) {
357 name = gnome_da_xml_get_string (element, "name");
358 icon = gnome_da_xml_get_string (element, "icon-name");
359
360 // Lookup executable in app command map
361 tmp = get_app_command (executable);
362 if (tmp)
363 command = g_strdup (tmp);
364 else {
365 // Fall back to command specified in XML
366 command = gnome_da_xml_get_string (element, "command");
367 }
368
369
370
371 g_debug ("LOAD-APPS: Adding '%s': %s [%s]", name, command, icon);
372
373 entry = alarm_list_entry_new (name, command, icon);
374
375 g_free (name);
376 g_free (command);
377 g_free (icon);
378
379 applet->apps = g_list_append (applet->apps, entry);
380 }
381
382 if (executable)
383 g_free (executable);
384 }
385 }
386 }
387 }
388
389 g_free(filename);
390 break;
391 }
392
393 g_free(filename);
394 }
395
396 // entry = alarm_list_entry_new("Rhythmbox Music Player", "rhythmbox", "rhythmbox");
397 // applet->apps = g_list_append (applet->apps, entry);
398 }
399
400 /*
401 * Alarms list {{
402 */
403
404 // TODO: Refactor to use a GHashTable instead?
405 void
alarm_applet_alarms_load(AlarmApplet * applet)406 alarm_applet_alarms_load (AlarmApplet *applet)
407 {
408 GList *list = NULL;
409 GList *l = NULL;
410
411 if (applet->alarms != NULL) {
412 // Free old alarm objects
413 for (l = applet->alarms; l != NULL; l = l->next) {
414 g_object_unref (ALARM (l->data));
415 }
416
417 // Free list
418 g_list_free (applet->alarms);
419 }
420
421 // Fetch list of alarms and add them
422 applet->alarms = NULL;
423 list = alarm_get_list (ALARM_GCONF_DIR);
424
425 for (l = list; l != NULL; l = l->next) {
426 alarm_applet_alarms_add (applet, ALARM (l->data));
427 }
428 }
429
430 void
alarm_applet_alarms_add(AlarmApplet * applet,Alarm * alarm)431 alarm_applet_alarms_add (AlarmApplet *applet, Alarm *alarm)
432 {
433 applet->alarms = g_list_append (applet->alarms, alarm);
434
435 g_signal_connect (alarm, "notify", G_CALLBACK (alarm_applet_alarm_changed), applet);
436 g_signal_connect (alarm, "notify::sound-file", G_CALLBACK (alarm_sound_file_changed), applet);
437
438 g_signal_connect (alarm, "alarm", G_CALLBACK (alarm_applet_alarm_triggered), applet);
439 g_signal_connect (alarm, "cleared", G_CALLBACK (alarm_applet_alarm_cleared), applet);
440
441 // Update alarm list window model
442 if (applet->list_window) {
443 alarm_list_window_alarm_add (applet->list_window, alarm);
444 }
445 }
446
447 void
alarm_applet_alarms_remove(AlarmApplet * applet,Alarm * alarm)448 alarm_applet_alarms_remove (AlarmApplet *applet, Alarm *alarm)
449 {
450 // Remove from list
451 applet->alarms = g_list_remove (applet->alarms, alarm);
452
453 // Clear list store. This will decrease the refcount of our alarms by 1.
454 /*if (applet->list_alarms_store)
455 gtk_list_store_clear (applet->list_alarms_store);*/
456
457 g_debug ("alarm_applet_alarms_remove (..., %p): refcount = %d", alarm, G_OBJECT (alarm)->ref_count);
458
459 // Remove any signal handlers for this alarm instance.
460 g_signal_handlers_disconnect_matched (alarm, 0, 0, 0, NULL, NULL, NULL);
461
462 // Update alarm list window model
463 if (applet->list_window) {
464 alarm_list_window_alarm_remove (applet->list_window, alarm);
465 }
466
467 // Dereference alarm
468 g_object_unref (alarm);
469 }
470
471 /*
472 * }} Alarms list
473 */
474
475 // TODO: Is this function needed?
476 /*void
477 alarm_applet_destroy (AlarmApplet *applet)
478 {
479 GList *l;
480 Alarm *a;
481 AlarmSettingsDialog *dialog;
482
483 g_debug ("AlarmApplet DESTROY");
484
485 // TODO: Destroy alarms list
486 // if (applet->list_alarms_dialog) {
487 // list_alarms_dialog_close (applet);
488 // }
489
490 // Destroy preferences dialog
491 if (applet->prefs_dialog) {
492 gtk_widget_destroy (GTK_WIDGET (applet->prefs_dialog));
493 }
494
495 // Loop through all alarms and free like a mad man!
496 for (l = applet->alarms; l; l = l->next) {
497 a = ALARM (l->data);
498
499 // Check if a dialog is open for this alarm
500 //dialog = (AlarmSettingsDialog *)g_hash_table_lookup (applet->edit_alarm_dialogs, (gconstpointer)a->id);
501
502 g_object_unref (a);
503 }
504
505 // Remove sounds list
506 if (applet->sounds) {
507 alarm_list_entry_list_free(&(applet->sounds));
508 }
509
510 // Remove apps list
511 if (applet->apps) {
512 alarm_list_entry_list_free(&(applet->apps));
513 }
514
515 if (app_command_map) {
516 g_hash_table_destroy (app_command_map);
517 app_command_map = NULL;
518 }
519
520 // Free GConf dir
521 //g_free (applet->gconf_dir);
522
523 // Finally free the AlarmApplet struct itself
524 g_free (applet);
525 }*/
526
527
528 static UniqueResponse
unique_app_message_cb(UniqueApp * app,UniqueCommand command,UniqueMessageData * message,guint time_,gpointer user_data)529 unique_app_message_cb (UniqueApp *app,
530 UniqueCommand command,
531 UniqueMessageData *message,
532 guint time_,
533 gpointer user_data)
534 {
535 AlarmApplet *applet = (AlarmApplet *)user_data;
536
537 UniqueResponse res;
538
539 switch (command) {
540 case UNIQUE_ACTIVATE:
541 g_debug ("AlarmApplet: unique_app_message: ACTIVATE");
542 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (applet->action_toggle_list_win))) {
543 // Already visible, present to user
544 alarm_list_window_show (applet->list_window);
545 } else {
546 // Toggle list window visibility
547 gtk_action_activate (GTK_ACTION (applet->action_toggle_list_win));
548 }
549
550 res = UNIQUE_RESPONSE_OK;
551 break;
552 default:
553 g_warning ("AlarmApplet: unique_app_message: Unknown command %d",
554 command);
555
556 res = UNIQUE_RESPONSE_INVALID;
557 break;
558 }
559
560 return res;
561 }
562
563
564
565 /*
566 * INIT {{
567 */
568
569 static AlarmApplet*
alarm_applet_init(int * argc,char *** argv)570 alarm_applet_init (int *argc, char ***argv)
571 {
572 AlarmApplet *applet;
573 UniqueApp *unique_app;
574
575 GError *error = NULL;
576 GOptionContext *context;
577
578 gboolean hidden = FALSE; // Start hidden
579
580 // Command line options
581 GOptionEntry entries[] =
582 {
583 { "hidden", 0, 0, G_OPTION_ARG_NONE, &hidden, "Start hidden", NULL },
584 { NULL }
585 };
586
587 // Initialize unique app
588 unique_app = unique_app_new ("com.pseudoberries.AlarmClock", NULL);
589
590 // Check if we're already running
591 if (unique_app_is_running (unique_app)) {
592 g_printerr(_("%s is already running, exiting...\n"), PACKAGE);
593
594 // Send activate message
595 UniqueMessageData *message = unique_message_data_new ();
596
597 unique_app_send_message (unique_app, UNIQUE_ACTIVATE, message);
598
599 unique_message_data_free (message);
600 g_object_unref (unique_app);
601
602 exit (EXIT_SUCCESS);
603 }
604
605 // Parse command-line arguments
606 context = g_option_context_new (NULL);
607 g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
608 g_option_context_add_group (context, gtk_get_option_group (TRUE));
609
610 if (!g_option_context_parse (context, argc, argv, &error)) {
611 g_printerr ("%s\n", error->message);
612 exit (EXIT_FAILURE);
613 }
614
615 // Initialize applet struct
616 applet = g_new0 (AlarmApplet, 1);
617
618 // Set up unique app
619 applet->unique_app = unique_app;
620
621 g_signal_connect (unique_app, "message-received",
622 G_CALLBACK (unique_app_message_cb), applet);
623
624 //applet->edit_alarm_dialogs = g_hash_table_new (NULL, NULL);
625
626 // Preferences (defaults).
627 // ...gconf_get_string can return NULL if the key is not found. We can't
628 // assume the schema provides the default values for strings.
629
630 // TODO: Add to gconf
631 applet->snooze_mins = 5;
632
633 // Initialize gconf
634 alarm_applet_gconf_init (applet);
635
636 // Load alarms
637 alarm_applet_alarms_load (applet);
638
639 // Load sounds from alarms
640 alarm_applet_sounds_load (applet);
641
642 // Load apps for alarms
643 alarm_applet_apps_load (applet);
644
645 // Set up applet UI
646 alarm_applet_ui_init (applet);
647
648 // Show alarms window, unless --hidden
649 if (!hidden) {
650 gtk_action_activate (GTK_ACTION (applet->action_toggle_list_win));
651 }
652
653 return applet;
654 }
655
656 /**
657 * Cleanup
658 */
659 static void
alarm_applet_quit(AlarmApplet * applet)660 alarm_applet_quit (AlarmApplet *applet)
661 {
662 g_debug ("AlarmApplet: Quitting...");
663
664 g_object_unref (applet->unique_app);
665 }
666
667 /**
668 * Alarm Clock main()
669 */
670 int
main(int argc,char * argv[])671 main (int argc, char *argv[])
672 {
673 AlarmApplet *applet;
674
675 // Internationalization
676 bindtextdomain (GETTEXT_PACKAGE, ALARM_CLOCK_DATADIR "/locale");
677 bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
678 textdomain (GETTEXT_PACKAGE);
679
680 // Terminate on critical errors
681 //g_log_set_always_fatal (G_LOG_LEVEL_CRITICAL);
682
683 // Initialize GTK+
684 gtk_init (&argc, &argv);
685
686 // Initialize applet
687 applet = alarm_applet_init (&argc, &argv);
688
689 // Start main loop
690 gtk_main ();
691
692 // Clean up
693 alarm_applet_quit (applet);
694
695 return 0;
696 }
697
698 /*
699 * }} INIT
700 */
701