1 /* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
2 /*
3  * Copyright (C) 2009-2011  Tiger Soldier
4  *
5  * This file is part of OSD Lyrics.
6  * OSD Lyrics is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * OSD Lyrics 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 OSD Lyrics.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 #include <stdio.h>
20 #include <time.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <unistd.h>
24 #include <signal.h>
25 #include <pwd.h>
26 #include "config.h"
27 #include "ol_lrc.h"
28 #include "ol_player.h"
29 #include "ol_utils.h"
30 #include "ol_lrc_fetch.h"
31 #include "ol_lrc_fetch_ui.h"
32 #include "ol_trayicon.h"
33 #include "ol_intl.h"
34 #include "ol_config.h"
35 #include "ol_display_module.h"
36 #include "ol_keybindings.h"
37 #include "ol_lrc_fetch_module.h"
38 #include "ol_lyric_manage.h"
39 #include "ol_stock.h"
40 #include "ol_app.h"
41 #include "ol_notify.h"
42 #include "ol_lrclib.h"
43 #include "ol_debug.h"
44 #include "ol_singleton.h"
45 #include "ol_player_chooser.h"
46 
47 #define REFRESH_INTERVAL 100
48 #define INFO_INTERVAL 500
49 #define TIMEOUT_WAIT_LAUNCH 5000
50 #define LRCDB_FILENAME "lrc.db"
51 
52 gboolean _arg_debug_cb (const gchar *option_name,
53                         const gchar *value,
54                         gpointer data,
55                         GError **error);
56 static gboolean _arg_version;
57 
58 static GOptionEntry cmdargs[] =
59 {
60   { "debug", 'd', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, _arg_debug_cb,
61     N_ ("The level of debug messages to log, can be 'none', 'error', 'debug', or 'info'"), "level" },
62   { "version", 'v', 0, G_OPTION_ARG_NONE, &_arg_version,
63     N_ ("Show version information"), NULL},
64   { NULL }
65 };
66 
67 static guint refresh_source = 0;
68 static guint info_timer = 0;
69 static guint _lost_action_delay_timer = 0;
70 static struct OlPlayer *player = NULL;
71 static OlMusicInfo music_info = {0};
72 static gchar *previous_title = NULL;
73 static gchar *previous_artist = NULL;
74 static gchar *previous_uri = NULL;
75 static enum OlPlayerStatus previous_status = OL_PLAYER_UNKNOWN;
76 static gint previous_duration = 0;
77 static gint previous_position = -1;
78 static struct OlLrc *lrc_file = NULL;
79 static char *display_mode = NULL;
80 static struct OlDisplayModule *module = NULL;
81 static int search_id = -1;
82 static enum _PlayerLostAction {
83   ACTION_NONE = 0,
84   ACTION_LAUNCH_DEFAULT,
85   ACTION_CHOOSE_PLAYER,
86   ACTION_CHOOSE_PLAYER_DISCONNECTED,
87   ACTION_WAIT_LAUNCH,
88   ACTION_QUIT,
89 } player_lost_action = ACTION_LAUNCH_DEFAULT;
90 static OlPlayerChooser *player_chooser = NULL;
91 
92 static void _initialize (int argc, char **argv);
93 static gint _refresh_music_info (gpointer data);
94 static gint _refresh_player_info (gpointer data);
95 static void _start_refresh_music_info (void);
96 static void _stop_refresh_music_info (void);
97 static void _start_refresh_player_info (void);
98 static void _wait_for_player_launch (void);
99 static void _set_player_lost_action (enum _PlayerLostAction action);
100 static void _set_player_lost_action_delay (enum _PlayerLostAction action,
101                                            guint delay_ms);
102 static void _player_lost_cb (void);
103 static void _player_chooser_response_cb (GtkDialog *dialog,
104                                          gint response_id,
105                                          gpointer user_data);
106 static void _check_music_change ();
107 static void _on_music_changed (void);
108 static gboolean _check_lyric_file (void);
109 static void _update_player_status (enum OlPlayerStatus status);
110 static gboolean _get_active_player (void);
111 static void _search_callback (struct OlLrcFetchResult *result,
112                             void *userdata);
113 static void _download_callback (struct OlLrcDownloadResult *result);
114 static void _on_config_changed (OlConfig *config,
115                                 gchar *group,
116                                 gchar *name,
117                                 gpointer userdata);
118 
119 static void
120 _on_config_changed (OlConfig *config,
121                     gchar *group,
122                     gchar *name,
123                     gpointer userdata)
124 {
125   if (module != NULL && strcmp (name, "display-mode") == 0)
126   {
127     char *mode = ol_config_get_string (config, group, name);
128     if (display_mode == NULL ||
129         ol_stricmp (mode, display_mode, -1) != 0)
130     {
131       if (display_mode != NULL)
132         g_free (display_mode);
133       display_mode = g_strdup (mode);
134       ol_display_module_free (module);
135       module = ol_display_module_new (display_mode);
136       ol_display_module_set_music_info (module, &music_info);
137       ol_display_module_set_duration (module, previous_duration);
138       ol_display_module_set_lrc (module, lrc_file);
139       ol_display_module_set_player (module, player);
140       ol_display_module_set_status (module, previous_status);
141     }
142     g_free (mode);
143   }
144 }
145 
146 static void
147 _download_callback (struct OlLrcDownloadResult *result)
148 {
149   ol_log_func ();
150   if (result->filepath != NULL)
151     ol_app_assign_lrcfile (result->info, result->filepath, TRUE);
152   else
153     ol_display_module_download_fail_message (module, _("Download failed"));
154 }
155 
156 static void
157 _search_msg_callback (int _search_id,
158                       enum OlLrcSearchMsgType msg_type,
159                       const char *message,
160                       void *userdata)
161 {
162   ol_assert (_search_id == search_id);
163   switch (msg_type)
164   {
165   case OL_LRC_SEARCH_MSG_ENGINE:
166     if (module != NULL)
167     {
168       char *msg = g_strdup_printf (_("Searching lyrics from %s"), _(message));
169       ol_display_module_search_fail_message (module, msg);
170       g_free (msg);
171     }
172     break;
173   }
174 }
175 
176 
177 static void
178 _search_callback (struct OlLrcFetchResult *result,
179                   void *userdata)
180 {
181   ol_log_func ();
182   ol_assert (result != NULL);
183   ol_assert (result->engine != NULL);
184   ol_assert (result->id == search_id);
185   search_id = -1;
186   if (result->count > 0 && result->candidates != 0)
187   {
188     char *filename = ol_lyric_download_path (&result->info);
189     if (filename == NULL)
190     {
191       ol_display_module_download_fail_message (module, _("Cannot create the lyric directory"));
192     }
193     else
194     {
195       if (module != NULL) {
196         ol_display_module_clear_message (module);
197       }
198       ol_lrc_fetch_ui_show (result->engine, result->candidates, result->count,
199                             &result->info,
200                             filename);
201       g_free (filename);
202     }
203   }
204   else
205   {
206     if (module != NULL)
207       ol_display_module_search_fail_message (module, _("Lyrics not found"));
208   }
209 }
210 
211 gboolean
212 ol_app_download_lyric (OlMusicInfo *music_info)
213 {
214   ol_log_func ();
215   if (search_id > 0)
216     ol_lrc_fetch_cancel_search (search_id);
217   OlConfig *config = ol_config_get_instance ();
218   char **engine_list = ol_config_get_str_list (config,
219                                                "Download",
220                                                "download-engine",
221                                                NULL);
222   search_id = ol_lrc_fetch_begin_search (engine_list,
223                                          music_info,
224                                          _search_msg_callback,
225                                          _search_callback,
226                                          NULL);
227   g_strfreev (engine_list);
228   return TRUE;
229 }
230 
231 struct OlLrc *
232 ol_app_get_current_lyric ()
233 {
234   ol_log_func ();
235   return lrc_file;
236 }
237 
238 gboolean
239 ol_app_assign_lrcfile (const OlMusicInfo *info,
240                        const char *filepath,
241                        gboolean update)
242 {
243   ol_log_func ();
244   ol_assert_ret (info != NULL, FALSE);
245   ol_assert_ret (filepath == NULL || ol_path_is_file (filepath), FALSE);
246   if (update)
247   {
248     ol_lrclib_assign_lyric (info, filepath);
249   }
250   if (ol_music_info_equal (&music_info, info))
251   {
252     if (lrc_file != NULL)
253     {
254       ol_lrc_free (lrc_file);
255       lrc_file = NULL;
256     }
257     if (filepath != NULL)
258       lrc_file = ol_lrc_new (filepath);
259     if (module != NULL)
260       ol_display_module_set_lrc (module, lrc_file);
261   }
262   return TRUE;
263 }
264 
265 static gboolean
266 _check_lyric_file ()
267 {
268   ol_log_func ();
269   gboolean ret = TRUE;
270   char *filename = NULL;
271   int code = ol_lrclib_find (&music_info, &filename);
272   if (code == 0)
273     filename = ol_lyric_find (&music_info);
274 
275   if (filename != NULL)
276   {
277     ret = ol_app_assign_lrcfile (&music_info, filename, code == 0);
278     g_free (filename);
279   }
280   else
281   {
282     ol_debugf("filename;%s\n", filename);
283     if (code == 0) ret = FALSE;
284   }
285   return ret;
286 }
287 
288 static void
289 _on_music_changed ()
290 {
291   ol_log_func ();
292   if (module != NULL)
293   {
294     ol_display_module_set_music_info (module, &music_info);
295     ol_display_module_set_duration (module, previous_duration);
296     ol_display_module_set_lrc (module, NULL);
297   }
298   if (!_check_lyric_file () &&
299       !ol_is_string_empty (ol_music_info_get_title (&music_info)))
300     ol_app_download_lyric (&music_info);
301   OlConfig *config = ol_config_get_instance ();
302   if (ol_config_get_bool (config, "General", "notify-music"))
303     ol_notify_music_change (&music_info, ol_player_get_icon_path (player));
304 }
305 
306 static void
307 _normalize_music_info (OlMusicInfo *music_info)
308 {
309   if (ol_is_string_empty (ol_music_info_get_title (music_info)) &&
310       ! ol_is_string_empty (ol_music_info_get_uri (music_info)))
311   {
312     const char *uri = ol_music_info_get_uri (music_info);
313     char *path = NULL;
314     if (uri[0] == '/')
315     {
316       path = g_strdup (uri);
317     }
318     else
319     {
320       GError *err = NULL;
321       path = g_filename_from_uri (uri, NULL, &err);
322       if (path == NULL)
323       {
324         ol_debugf ("Convert uri failed: %s\n", err->message);
325         g_error_free (err);
326       }
327     }
328     if (path != NULL)
329     {
330       char *basename = g_path_get_basename (path);
331       char *mainname = NULL;
332       ol_path_splitext (basename, &mainname, NULL);
333       if (mainname != NULL)
334       {
335         ol_music_info_set_title (music_info, mainname);
336         g_free (mainname);
337       }
338       g_free (basename);
339       g_free (path);
340     }
341   }
342 }
343 
344 static void
345 _check_music_change ()
346 {
347   ol_log_func ();
348   /* checks whether the music has been changed */
349   gboolean changed = FALSE;
350   /* compares the previous title with current title */
351   if (player && !ol_player_get_music_info (player, &music_info))
352   {
353     player = NULL;
354   }
355   else
356   {
357     _normalize_music_info (&music_info);
358   }
359   gint duration = 0;
360   if (player && !ol_player_get_music_length (player, &duration))
361   {
362     player = NULL;
363   }
364   if (!ol_streq (music_info.title, previous_title))
365     changed = TRUE;
366   ol_strptrcpy (&previous_title, music_info.title);
367   /* compares the previous artist with current  */
368   if (!ol_streq (music_info.artist, previous_artist))
369     changed = TRUE;
370   ol_strptrcpy (&previous_artist, music_info.artist);
371   if (!ol_streq (music_info.uri, previous_uri))
372     changed = TRUE;
373   ol_strptrcpy (&previous_uri, music_info.uri);
374   /* compares the previous duration */
375   /* FIXME: because the a of banshee, some lyrics may return different
376      duration for the same song when plays to different position, so the
377      comparison is commented out temporarily */
378   /* if (previous_duration != duration) */
379   /* { */
380   /*   ol_debugf ("change6:%d-%d\n", previous_duration, duration); */
381   /*   changed = TRUE; */
382   /* } */
383   if (previous_duration != duration)
384   {
385     previous_duration = duration;
386     if (module != NULL)
387       ol_display_module_set_duration (module, duration);
388   }
389   if (changed)
390   {
391     _on_music_changed ();
392   }
393 }
394 
395 static void
396 _update_player_status (enum OlPlayerStatus status)
397 {
398   ol_log_func ();
399   if (previous_status != status)
400   {
401     previous_status = status;
402     if (module != NULL)
403     {
404       ol_display_module_set_status (module, status);
405     }
406     ol_trayicon_status_changed (status);
407   }
408 }
409 
410 static gint
411 _refresh_player_info (gpointer data)
412 {
413   ol_log_func ();
414   if (player != NULL)
415   {
416     if (ol_player_get_capacity (player) & OL_PLAYER_STATUS)
417       _update_player_status (ol_player_get_status (player));
418     _check_music_change ();
419   }
420   else
421   {
422     if (_get_active_player ())
423       _start_refresh_music_info ();
424   }
425   return TRUE;
426 }
427 
428 static void
429 _set_player_lost_action (enum _PlayerLostAction action)
430 {
431   if (_lost_action_delay_timer)
432   {
433     g_source_remove (_lost_action_delay_timer);
434     _lost_action_delay_timer = 0;
435   }
436   player_lost_action = action;
437 }
438 
439 static gboolean
440 _player_lost_action_delay_cb (gpointer userdata)
441 {
442   enum _PlayerLostAction action = GPOINTER_TO_INT (userdata);
443   _set_player_lost_action (action);
444   return FALSE;
445 }
446 
447 static void
448 _set_player_lost_action_delay (enum _PlayerLostAction action,
449                                guint delay_seconds)
450 {
451   if (_lost_action_delay_timer)
452   {
453     g_source_remove (_lost_action_delay_timer);
454   }
455   _lost_action_delay_timer = g_timeout_add_seconds (delay_seconds,
456                                                     _player_lost_action_delay_cb,
457                                                     GINT_TO_POINTER ((int) action));
458 }
459 
460 static gboolean
461 _player_launch_timeout (gpointer userdata)
462 {
463   if (player_lost_action == ACTION_WAIT_LAUNCH)
464     _set_player_lost_action (ACTION_CHOOSE_PLAYER);
465   return FALSE;
466 }
467 
468 static void
469 _wait_for_player_launch (void)
470 {
471   _set_player_lost_action (ACTION_WAIT_LAUNCH);
472   g_timeout_add (TIMEOUT_WAIT_LAUNCH, _player_launch_timeout, NULL);
473 }
474 
475 static void
476 _player_chooser_response_cb (GtkDialog *dialog,
477                              gint response_id,
478                              gpointer user_data)
479 {
480   ol_assert (GTK_IS_DIALOG (dialog));
481   switch (response_id)
482   {
483   case OL_PLAYER_CHOOSER_RESPONSE_LAUNCH:
484     _wait_for_player_launch ();
485     break;
486   case GTK_RESPONSE_DELETE_EVENT:
487   case GTK_RESPONSE_CLOSE:
488     gtk_widget_hide (GTK_WIDGET (dialog));
489     if (player == NULL)
490       gtk_main_quit ();
491     break;
492   default:
493     ol_errorf ("Unknown response id: %d\n", response_id);
494   }
495 }
496 
497 static void
498 _player_lost_cb (void)
499 {
500   if (_lost_action_delay_timer > 0)
501   {
502     g_source_remove (_lost_action_delay_timer);
503     _lost_action_delay_timer = 0;
504   }
505   ol_music_info_clear (&music_info);
506   if (module != NULL)
507   {
508     ol_display_module_free (module);
509     module = NULL;
510   }
511   switch (player_lost_action)
512   {
513   case ACTION_LAUNCH_DEFAULT:
514   case ACTION_CHOOSE_PLAYER:
515   case ACTION_CHOOSE_PLAYER_DISCONNECTED:
516   {
517     if (player_lost_action == ACTION_LAUNCH_DEFAULT)
518     {
519       OlConfig *config = ol_config_get_instance ();
520       char *player_cmd = ol_config_get_string (config,
521                                                "General",
522                                                "startup-player");
523       if (!ol_is_string_empty (player_cmd))
524       {
525         ol_debugf ("Running %s\n", player_cmd);
526         ol_launch_app (player_cmd);
527         _wait_for_player_launch ();
528         g_free (player_cmd);
529         break;
530       }
531       else
532       {
533         g_free (player_cmd);
534       }
535     }
536     if (!player_chooser)
537     {
538       GList *supported_players = ol_player_get_support_players ();
539       player_chooser = OL_PLAYER_CHOOSER (ol_player_chooser_new (supported_players));
540       g_signal_connect (player_chooser,
541                         "response",
542                         G_CALLBACK (_player_chooser_response_cb),
543                         NULL);
544       ol_player_chooser_set_info_by_state (player_chooser,
545                                            OL_PLAYER_CHOOSER_STATE_NO_PLAYER);
546     }
547     else
548     {
549       if (player_lost_action == ACTION_CHOOSE_PLAYER_DISCONNECTED)
550         ol_player_chooser_set_info_by_state (player_chooser,
551                                              OL_PLAYER_CHOOSER_STATE_DISCONNECTED);
552       else
553         ol_player_chooser_set_info_by_state (player_chooser,
554                                              OL_PLAYER_CHOOSER_STATE_LAUNCH_FAIL);
555     }
556     gtk_widget_show (GTK_WIDGET (player_chooser));
557     _set_player_lost_action (ACTION_NONE);
558     break;
559   }
560   case ACTION_QUIT:
561     printf (_("Player is not running. OSD Lyrics exits now.\n"));
562     gtk_main_quit ();
563     break;
564   default:
565     break;
566   }
567 }
568 
569 static void
570 _player_connected_cb (void)
571 {
572   if (player_chooser != NULL &&
573       gtk_widget_get_visible (GTK_WIDGET (player_chooser)))
574     ol_player_chooser_set_info_by_state (player_chooser,
575                                          OL_PLAYER_CHOOSER_STATE_CONNECTED);
576   if (!module)
577   {
578     /* Initialize display modules */
579     OlConfig *config = ol_config_get_instance ();
580     ol_display_module_init ();
581     display_mode = ol_config_get_string (config, "General", "display-mode");
582     module = ol_display_module_new (display_mode);
583   }
584   /* If the player lost in 1 minute, it is possible that the player crashed or
585      something bad happen to the D-Bus connection. In this case, we should let
586      our use to choose another player rather than simply exit */
587   _set_player_lost_action (ACTION_CHOOSE_PLAYER_DISCONNECTED);
588   _set_player_lost_action_delay (ACTION_QUIT, 60);
589 }
590 
591 static gboolean
592 _get_active_player (void)
593 {
594   ol_log_func ();
595   player = ol_player_get_active_player ();
596   if (player == NULL)
597   {
598     _player_lost_cb ();
599   }
600   else
601   {
602     _player_connected_cb ();
603   }
604   if (module != NULL)
605     ol_display_module_set_player (module, player);
606   return player != NULL;
607 }
608 
609 static gint
610 _refresh_music_info (gpointer data)
611 {
612   ol_log_func ();
613   if (player == NULL && !_get_active_player ())
614   {
615     _stop_refresh_music_info ();
616     return FALSE;
617   }
618   gint time = 0;
619   if (player && !ol_player_get_played_time (player, &time))
620   {
621     player = NULL;
622   }
623   if (previous_position < 0 || time < previous_position ||
624       previous_title == NULL)
625     _check_music_change ();
626   previous_position = time;
627   if (player == NULL)
628   {
629     previous_position = -1;
630     return TRUE;
631   }
632   if (module != NULL)
633     ol_display_module_set_played_time (module, time);
634   return TRUE;
635 }
636 
637 struct OlPlayer*
638 ol_app_get_player ()
639 {
640   return player;
641 }
642 
643 OlMusicInfo*
644 ol_app_get_current_music ()
645 {
646   return &music_info;
647 }
648 
649 void
650 ol_app_adjust_lyric_offset (int offset_ms)
651 {
652   ol_log_func ();
653   struct OlLrc *lrc = ol_app_get_current_lyric ();
654   if (lrc == NULL)
655     return;
656   int old_offset = ol_lrc_get_offset (lrc);
657   int new_offset = old_offset - offset_ms;
658   ol_lrc_set_offset (lrc, new_offset);
659 }
660 
661 static void
662 _parse_cmd_args (int *argc, char ***argv)
663 {
664   ol_log_func ();
665   GError *error = NULL;
666   GOptionContext *context;
667 
668   context = g_option_context_new ("- Display your lyrics");
669   g_option_context_add_main_entries (context, cmdargs, PACKAGE);
670   g_option_context_add_group (context, gtk_get_option_group (TRUE));
671   if (!g_option_context_parse (context, argc, argv, &error))
672   {
673     ol_errorf ("option parsing failed: %s\n", error->message);
674   }
675   if (_arg_version)
676   {
677     printf ("%s %s\n", PROGRAM_NAME, VERSION);
678     exit (0);
679   }
680   g_option_context_free (context);
681 }
682 
683 gboolean
684 _arg_debug_cb (const gchar *option_name,
685                const gchar *value,
686                gpointer data,
687                GError **error)
688 {
689   if (value == NULL)
690     value = "debug";
691   if (strcmp (value, "none") == 0)
692   {
693     ol_log_set_level (OL_LOG_NONE);
694   }
695   else if (strcmp (value, "error") == 0)
696   {
697     ol_log_set_level (OL_ERROR);
698   }
699   else if (strcmp (value, "debug") == 0)
700   {
701     ol_log_set_level (OL_DEBUG);
702   }
703   else if (strcmp (value, "info") == 0)
704   {
705     ol_log_set_level (OL_INFO);
706   }
707   else
708   {
709     g_set_error_literal (error, g_quark_from_string (PACKAGE_NAME), 1,
710                          N_ ("debug level should be one of ``none'', ``error'', ``debug'', or ``info''"));
711     return FALSE;
712   }
713   return TRUE;
714 }
715 
716 static void
717 _initialize (int argc, char **argv)
718 {
719   ol_log_func ();
720 #if ENABLE_NLS
721   /* Set the text message domain.  */
722   bindtextdomain (PACKAGE, LOCALEDIR);
723   bind_textdomain_codeset(PACKAGE, "UTF-8");
724   /* textdomain (PACKAGE); */
725 #endif
726 
727   g_thread_init(NULL);
728   gtk_init (&argc, &argv);
729   _parse_cmd_args (&argc, &argv);
730   g_set_prgname (_(PROGRAM_NAME));
731   if (ol_is_running ())
732   {
733     printf ("%s\n", _("Another OSD Lyrics is running, exit."));
734     exit (0);
735   }
736   ol_stock_init ();
737   ol_player_init ();
738   OlConfig *config = ol_config_get_instance ();
739   g_signal_connect (config, "changed",
740                     G_CALLBACK (_on_config_changed),
741                     NULL);
742   ol_trayicon_inital ();
743   ol_notify_init ();
744   ol_keybinding_init ();
745   ol_lrc_fetch_module_init ();
746   char *lrcdb_file = g_strdup_printf ("%s/%s/%s",
747                                       g_get_user_config_dir (),
748                                       PACKAGE_NAME,
749                                       LRCDB_FILENAME);
750   if (!ol_lrclib_init (lrcdb_file))
751   {
752     ol_error ("Initialize lrclib failed");
753   }
754   g_free (lrcdb_file);
755   ol_lrc_fetch_add_async_download_callback (_download_callback);
756   _start_refresh_player_info ();
757 }
758 
759 static void
760 _start_refresh_player_info (void)
761 {
762   if (info_timer == 0)
763     info_timer = g_timeout_add (INFO_INTERVAL, _refresh_player_info, NULL);
764 }
765 
766 static void
767 _start_refresh_music_info (void)
768 {
769   if (refresh_source == 0)
770     refresh_source = g_timeout_add (REFRESH_INTERVAL, _refresh_music_info, NULL);
771 }
772 
773 static void
774 _stop_refresh_music_info (void)
775 {
776   if (refresh_source > 0)
777   {
778     g_source_remove (refresh_source);
779     refresh_source = 0;
780   }
781 }
782 
783 int
784 main (int argc, char **argv)
785 {
786   _initialize (argc, argv);
787   gtk_main ();
788   ol_player_unload ();
789   ol_notify_unload ();
790   if (module != NULL)
791   {
792     ol_display_module_free (module);
793     module = NULL;
794   }
795   if (display_mode != NULL)
796   {
797     g_free (display_mode);
798     display_mode = NULL;
799   }
800   if (player_chooser != NULL)
801   {
802     gtk_widget_destroy (GTK_WIDGET (player_chooser));
803     player_chooser = NULL;
804   }
805   ol_display_module_unload ();
806   ol_trayicon_free ();
807   ol_lrclib_unload ();
808   ol_config_unload ();
809   ol_lrc_fetch_module_unload ();
810   return 0;
811 }
812