Modify version string to post-release version 0.18.101
[gmpc.git] / src / main.c
blob8604128357821c6e59e287886acf2682b33c9d3f
1 /* Gnome Music Player Client (GMPC)
2 * Copyright (C) 2004-2009 Qball Cow <qball@sarine.nl>
3 * Project homepage: http://gmpc.wikia.com/
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include <stdio.h>
21 #include <string.h>
22 #include <stdlib.h>
23 #include <strings.h>
25 #include <libxml/parser.h>
27 /** Gtk/glib glade stuff */
28 #include <gtk/gtk.h>
29 #include <glib/gstdio.h>
31 /* header files */
32 #include "main.h"
33 #include "playlist3.h"
35 /** session support */
36 #include "misc.h"
37 #include "advanced-search.h"
38 #include "gmpc_easy_download.h"
40 #include "setup-assistant.h"
41 /* as internall plugin */
42 #include "browsers/playlist3-playlist-editor.h"
43 #include "browsers/playlist3-file-browser.h"
44 #include "browsers/playlist3-find2-browser.h"
45 #include "browsers/playlist3-tag2-browser.h"
46 #include "browsers/playlist3-current-playlist-browser.h"
48 /* vala */
49 #include "vala/gmpc-easy-command.h"
50 #include "vala/gmpc-test-plugin.h"
51 #include "vala/gmpc-metadata-browser2.h"
52 #include "vala/gmpc-metadata-prefetcher.h"
53 #include "vala/gmpc-paned-size-group.h"
55 #include "gmpc-mpddata-model-playlist.h"
56 #include "metadata-cache.h"
57 #include "bug-information.h"
60 #define LOG_DOMAIN "Gmpc"
61 /**
62 * Get revision
64 #include "revision.h"
65 #ifdef ENABLE_MMKEYS
66 #include "mm-keys.h"
67 #endif
69 #define RESET "\x1b[0m"
70 #define BOLD "\x1b[1m"
72 extern gmpcPlugin connection_plug;
73 extern gmpcPlugin metadata_plug;
74 extern gmpcPlugin playlist_plug;
75 extern gmpcPlugin cover_art_plug;
76 extern gmpcPlugin tray_icon2_plug;
77 extern gmpcPlugin proxyplug;
78 extern gmpcPlugin playlist_editor_plugin;
79 extern gmpcPlugin statistics_plugin;
81 GmpcMetadataBrowser *metadata_browser = NULL;
82 /**
83 * Global objects that give signals
85 /* gives signal on connection changes, and state changes of mpd.*/
86 GmpcConnection *gmpcconn = NULL;
87 /* Implements, and gives signals on profiles */
88 GmpcProfiles *gmpc_profiles = NULL;
89 /* Implements, and gives signals on meta_data*/
90 GmpcMetaWatcher *gmw = NULL;
91 /* Easy command */
92 GmpcEasyCommand *gmpc_easy_command = NULL;
93 /* Playlist3 messages */
94 Playlist3MessagePlugin *pl3_messages = NULL;
96 /* The playlist backend */
97 GtkTreeModel *playlist = NULL;
99 GObject *paned_size_group = NULL;
101 * This flag indicate the requested connection state by the user.
102 * If the user presses disconnect, you don't want to auto-connect anymore.
104 int gmpc_connected = FALSE;
106 static void connection_changed_real(GmpcConnection * gmpcconn, MpdObj * mi, int connect, gpointer data);
107 static void gmpc_status_changed_callback_real(GmpcConnection * gmpcconn,
108 MpdObj * mi, ChangedStatusType what, gpointer data);
111 * Define some local functions
114 * Error dialog
116 static GtkWidget *error_dialog = NULL;
117 static GtkListStore *error_list_store = NULL;
119 /** handle connection changed */
120 static void connection_changed(MpdObj * mi, int connect, gpointer data);
122 /** Error callback */
123 static int error_callback(MpdObj * mi, int error_id, char *error_msg, gpointer data);
125 /** init stock icons */
126 static void init_stock_icons(void);
129 * the xml fle pointer to the player window
131 static GtkBuilder *xml_password_window = NULL;
132 static int autoconnect_callback(void);
135 * the ID of the autoconnect timeout callback
137 static guint autoconnect_timeout = 0;
140 * The Config object
142 config_obj *config = NULL;
145 * The Connection object
147 MpdObj *connection = NULL;
149 /* Glade prototypes, these would be static otherwise */
150 void send_password(void);
153 * Set paths
155 static void create_gmpc_paths(void);
157 void print_version(void);
159 #ifndef WIN32
160 #include "bacon/bacon-message-connection.h"
161 static BaconMessageConnection *bacon_connection = NULL;
163 * Handle incoming (IPC) messages.
164 * GMPC ships a utility called "gmpc-remote" that uses this interface.
166 #define LOG_DOMAIN_IPC "IPC"
167 static void bacon_on_message_received(const char *message, gpointer data)
170 if (message) {
171 g_log(LOG_DOMAIN_IPC, G_LOG_LEVEL_DEBUG, "got message: '%s'\n", message);
173 * Makes mpd quit.
175 if (strcmp(message, "QUIT") == 0) {
176 printf("I've been told to quit, doing this now\n");
177 main_quit();
180 * Gives play,pause command
182 else if (strcmp(message, "PLAY") == 0 || strcmp(message, "PAUSE") == 0) {
183 play_song();
186 * Give next command
188 else if (strcmp(message, "NEXT") == 0) {
189 next_song();
192 * Give previous command
194 else if (strcmp(message, "PREV") == 0) {
195 prev_song();
198 * Stop playback
200 else if (strcmp(message, "STOP") == 0) {
201 stop_song();
202 } else if (strcmp(message, "TOGGLE_VIEW") == 0) {
203 pl3_toggle_hidden();
204 } else if (strcmp(message, "HIDE_VIEW") == 0) {
205 pl3_hide();
206 } else if (strcmp(message, "SHOW_VIEW") == 0) {
207 create_playlist3();
209 else if (strcmp(message, "CONNECT") == 0) {
210 connect_to_mpd();
213 * pass gmpc an url to parse with the url_parser.
215 else if (strncmp(message, "STREAM ", 7) == 0) {
216 url_start_real(&message[7]);
217 } else {
218 create_playlist3();
222 * Bring gmpc to front, as default action.
226 #endif
228 static GLogLevelFlags global_log_level = G_LOG_LEVEL_MESSAGE;
229 static void gmpc_log_func(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data)
231 if(log_level <= global_log_level)
233 g_log_default_handler(log_domain, log_level, message, user_data);
236 static gboolean set_log_filter(const gchar *option_name, const gchar *value, gpointer data, GError **error)
238 if(value == NULL || value[0] == 0){
239 g_set_error(error, 0, 0, "--log-filter requires a log domain as argument");
240 return FALSE;
243 g_log_set_handler(value, G_LOG_LEVEL_MASK|G_LOG_FLAG_FATAL|G_LOG_FLAG_RECURSION, g_log_default_handler, NULL);
244 return TRUE;
246 static gboolean hide_on_start(void)
248 pl3_hide();
249 return FALSE;
251 int main(int argc, char **argv)
253 int i;
255 #ifdef WIN32
256 gchar *packagedir;
257 #endif
258 #ifdef ENABLE_MMKEYS
259 MmKeys *keys = NULL;
260 #endif
262 GError *error = NULL;
263 GOptionContext *context = NULL;
264 gboolean show_version = FALSE;
265 gboolean disable_plugins = FALSE;
266 gboolean start_hidden = FALSE;
267 gboolean clean_config = FALSE;
268 gboolean quit = FALSE;
269 gboolean replace = FALSE;
270 gboolean do_debug_updates = FALSE;
271 gboolean show_bug_information = FALSE;
272 gboolean fullscreen = FALSE;
273 gchar *config_path = NULL;
274 gint debug_level = -1;
276 GOptionEntry entries[] =
278 { "fullscreen", 0, 0,G_OPTION_ARG_NONE,
279 &fullscreen, N_("Start the program in full screen"), NULL},
280 { "version", 'v', 0,G_OPTION_ARG_NONE,
281 &show_version, N_("Show program version and revision"), NULL},
282 { "quit", 'q', 0,G_OPTION_ARG_NONE,
283 &quit, N_("Quits the running gmpc"), NULL},
284 { "replace", 'r', 0,G_OPTION_ARG_NONE,
285 &replace, N_("Replace the running gmpc"), NULL},
286 { "disable-plugins", 0 , 0,G_OPTION_ARG_NONE,
287 &disable_plugins, N_("Don't load the plugins"), NULL},
288 { "config", 0 , 0,G_OPTION_ARG_FILENAME,
289 &config_path, N_("Load alternative config file"), "Path"},
290 { "debug-level", 'd', 0,G_OPTION_ARG_INT,
291 &debug_level, N_("Set the debug level"), "level"},
292 { "start-hidden", 'h', 0,G_OPTION_ARG_NONE,
293 &start_hidden, N_("Start gmpc hidden to tray"), NULL},
294 { "clean-cover-db", 0 , 0,G_OPTION_ARG_NONE,
295 &clean_config, N_("Remove all failed hits from metadata cache"), NULL},
296 { "debug-updates", 0 , G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE,
297 &do_debug_updates, N_("Show redraw events in GTK+"), NULL},
298 { "bug-information",'b', 0,G_OPTION_ARG_NONE,
299 &show_bug_information, N_("Show bug information dialog"), NULL},
300 { "log-filter", 'f', 0,G_OPTION_ARG_CALLBACK,
301 set_log_filter, N_("Shows all output from a certain log domain"), "<Log domain>"},
304 {NULL}
308 * A string used severall times to create a path
310 gchar *url = NULL;
312 INIT_TIC_TAC();
315 g_log_set_default_handler(gmpc_log_func, NULL);
316 /* *
317 * Set the default debug level
318 * Depending if it is a git build or not
320 if (revision && revision[0] != '\0') {
321 /* We run a svn version, so we want more default debug output */
322 debug_set_level(DEBUG_ERROR);
323 } else {
324 /* Ok, release version... no debug */
325 debug_set_level(0);
329 egg_sm_client_set_mode(EGG_SM_CLIENT_MODE_NO_RESTART);
331 * Setup NLS
333 #ifdef ENABLE_NLS
334 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Setting NLS");
335 bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
336 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
337 textdomain(GETTEXT_PACKAGE);
338 #endif
339 gtk_set_locale();
341 TEC("Setting up locale");
343 context = g_option_context_new (_("Gnome Music Player Client"));
344 g_option_context_add_main_entries (context, entries, "gmpc");
345 g_option_context_add_group (context, gtk_get_option_group (TRUE));
346 g_option_context_add_group (context, egg_sm_client_get_option_group());
347 g_option_context_parse (context, &argc, &argv, &error);
348 g_option_context_free(context);
349 if(error) {
350 g_log(NULL, G_LOG_LEVEL_ERROR, "Failed to parse commandline options: %s", error->message);
351 g_error_free(error);
354 /* Show the version, if requested */
355 if(show_version) {
356 print_version();
357 return EXIT_SUCCESS;
361 * Set debug level, options are
362 * 0 = No debug
363 * 1 = Error messages
364 * 2 = Error + Warning messages
365 * 3 = All messages
367 if (debug_level >=0){
368 if(debug_level == 3){
369 global_log_level = G_LOG_LEVEL_DEBUG;
370 }else if (debug_level == 2){
371 global_log_level = G_LOG_LEVEL_INFO;
373 debug_set_level(debug_level);
375 /* Show the bug-information dialog */
376 if(show_bug_information){
377 gtk_init(&argc, &argv);
378 bug_information_window_new(NULL);
379 return EXIT_SUCCESS;
381 TEC("Parsing command line options");
384 * initialize threading
386 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Initializing threading");
389 * Init libxml.
390 * Libxml is not used (directly) by gmpc.
391 * But via glade and several plugins use it.
392 * I need to initialize it before the threading is started.
393 * So moved to gmpc.
395 * This fixes the plugin crasher bug on windows.
397 xmlInitParser();
400 * Check if threading is supported, if so, start it.
401 * Don't fail here, stuff like can cause that it is allready initialized.
403 if (!g_thread_supported())
404 g_thread_init(NULL);
406 TEC("Initializing threading");
408 * initialize gtk
410 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Initializing gtk ");
412 #ifdef WIN32
414 * This loads an extra gtk rc file on windows.
415 * This is used to re-enable rule-hint in the treeview. (this is forced off on windows).
417 packagedir = g_win32_get_package_installation_directory_of_module(NULL);
418 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Got %s as package installation dir", packagedir);
419 url = g_build_filename(packagedir, "share", "gmpc", "gmpc-gtk-win32.rc", NULL);
420 q_free(packagedir);
421 gtk_rc_add_default_file(url);
422 g_free(url);
423 #endif
425 /* initialize gtk */
426 gtk_init(&argc, &argv);
427 TEC("Gtk init");
428 /* connect signal to Session manager to quit */
429 g_signal_connect( egg_sm_client_get(),
430 "quit",
431 G_CALLBACK(main_quit),
432 NULL);
434 TEC("EggSmClient");
435 gmpc_easy_command = gmpc_easy_command_new();
436 /* Add it to the plugin command */
437 plugin_add_new(GMPC_PLUGIN_BASE(gmpc_easy_command), 0, NULL);
439 gmpc_easy_command_add_entry(gmpc_easy_command, _("quit"), "",
440 _("Quit gmpc"), (GmpcEasyCommandCallback *) main_quit, NULL);
441 gmpc_easy_command_add_entry(gmpc_easy_command, _("hide"), "",
442 _("Hide gmpc"), (GmpcEasyCommandCallback *) pl3_hide, NULL);
443 gmpc_easy_command_add_entry(gmpc_easy_command, _("show"), "",
444 _("Show gmpc"), (GmpcEasyCommandCallback *)create_playlist3, NULL);
445 gmpc_easy_command_add_entry(gmpc_easy_command, _("show notification"),"",
446 _("Show trayicon notification"), (GmpcEasyCommandCallback *)tray_icon2_create_tooltip, NULL);
448 TEC("Init easy command");
450 advanced_search_init();
451 TEC("Init advanced search");
453 * Call create_gmpc_paths();
454 * This function checks if the path needed path are available, if not, create them
456 create_gmpc_paths();
457 TEC("Check version and create paths");
460 * COMMANDLINE_OPTION:
461 * Cleanup the metadata database and quit.
463 if (clean_config) {
464 /* start the metadata system */
465 meta_data_init();
466 printf("Cleaning up cover file..\n");
467 /* Call the cleanup */
468 metadata_cache_cleanup();
469 printf("Done..\n");
470 /* Destroy the meta data system and exit. */
471 meta_data_destroy();
472 TEC("Database cleanup");
473 return 1;
477 * Open the config file
480 * Check if the user has forced a different config file location.
481 * else set to ~/.gmpc/gmpc.cfg
483 if (!config_path) {
484 url = gmpc_get_user_path("gmpc.cfg");
485 } else {
486 url = config_path;
489 * Open it
491 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Trying to open the config file: %s", url);
492 config = cfg_open(url);
494 /** test if config opened correct */
495 if (config == NULL) {
497 * Show gtk error message and quit
499 g_log(LOG_DOMAIN, G_LOG_LEVEL_ERROR, "Failed to save/load configuration:\n%s\n", url);
500 show_error_message(_("Failed to load the configuration system."), TRUE);
501 /* this is an error so bail out correctly */
502 abort();
504 TEC("Opening config file: %s", url);
506 * cleanup
508 q_free(url);
511 * If requested, output debug info to file
513 if (cfg_get_single_value_as_int_with_default(config, "Default", "Debug-log", FALSE)) {
514 url = gmpc_get_user_path("debug-info.log");
515 if (url) {
516 FILE *fp = g_fopen(url, "a");
517 if (!fp) {
518 g_log(LOG_DOMAIN, G_LOG_LEVEL_ERROR, "Failed to open debug-log file: \"%s\"\n", url);
519 show_error_message(_("Failed to load debug-log file."), TRUE);
520 abort();
522 /* Set the output */
523 debug_set_output(fp);
524 /* Force highest level */
525 debug_set_level(DEBUG_INFO);
526 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "***** Opened debug log file\n");
527 q_free(url);
528 TEC("Enabled Debug log");
532 * TODO, Check if version changed, then say something about it
534 url = cfg_get_single_value_as_string(config, "Default", "version");
535 if (url == NULL || strcmp(url, VERSION)) {
536 int *new_version = split_version(VERSION);
537 if (url) {
538 int *old_version = split_version((const char *)url);
539 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Welcome to a new version of gmpc.\n");
540 /* Do possible cleanup of config files and stuff */
541 /* old version older then 0.1.15.4.98 */
542 if ((old_version[0] <= 0 && old_version[1] <= 15 && old_version[2] <= 4 && old_version[3] <= 98)) {
543 conf_mult_obj *iter, *cmo = cfg_get_class_list(config);
544 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Purging old keys from the config file.\n");
545 for (iter = cmo; iter; iter = iter->next) {
546 if (strstr(iter->key, "colpos")
547 || strstr(iter->key, "colshow")
548 || strstr(iter->key, "colsize")) {
549 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Removing entry: %s\n", iter->key);
550 cfg_remove_class(config, iter->key);
553 cfg_free_multiple(cmo);
556 /* old version older then 0.17.0-beta1 */
557 if ((old_version[0] <= 0 && old_version[1] <= 16 && old_version[2] <= 95)) {
558 printf("** Correct icon-size\n");
559 cfg_set_single_value_as_int(config, "gmpc-mpddata-model", "icon-size", 32);
561 q_free(old_version);
563 /* set new */
564 cfg_set_single_value_as_string(config, "Default", "version", VERSION);
565 q_free(new_version);
567 if (url) {
568 q_free(url);
570 TEC("New version check");
572 #ifndef WIN32
574 * Start IPC system.
576 if (cfg_get_single_value_as_int_with_default(config, "Default", "allow-multiple", FALSE) == FALSE) {
578 * bacon here we come
580 bacon_connection = bacon_message_connection_new("gmpc");
581 if (bacon_connection != NULL) {
582 if (!bacon_message_connection_get_is_server(bacon_connection)) {
583 if (replace || quit) {
584 bacon_message_connection_send(bacon_connection, "QUIT");
585 while (!bacon_message_connection_get_is_server(bacon_connection)) {
586 bacon_message_connection_free(bacon_connection);
587 bacon_connection = bacon_message_connection_new("gmpc");
588 g_usleep(G_USEC_PER_SEC);
590 } else {
591 g_log(LOG_DOMAIN_IPC, G_LOG_LEVEL_WARNING, "gmpc is allready running\n");
592 bacon_message_connection_send(bacon_connection, "PRESENT");
593 bacon_message_connection_free(bacon_connection);
594 cfg_close(config);
595 config = NULL;
596 TEC("IPC setup and quitting");
597 exit(0);
600 bacon_message_connection_set_callback(bacon_connection, bacon_on_message_received, NULL);
602 /* If user requested a quit, quit */
603 if (quit) {
604 cfg_close(config);
605 config = NULL;
606 if (bacon_connection)
607 bacon_message_connection_free(bacon_connection);
609 exit(0);
612 TEC("IPC setup");
613 #endif
614 if (quit) {
615 cfg_close(config);
616 return EXIT_SUCCESS;
618 /* PanedSizeGroup */
619 paned_size_group = (GObject *)gmpc_paned_size_group_new();
620 /** Signals */
622 gmpc_profiles = gmpc_profiles_new();
623 TEC("Setting up gmpc idle,signals and profiles");
625 * Initialize the new metadata subsystem.
626 * (Will spawn a new thread, so have to be after the init threading
628 meta_data_init();
630 TEC("Initializing metadata system");
633 * stock icons
635 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Loading stock icons");
636 init_stock_icons();
637 TEC("Init stock icons");
639 * Create connection object
641 connection = mpd_new_default();
642 if (connection == NULL) {
644 * if failed, print error message
646 g_log(LOG_DOMAIN, G_LOG_LEVEL_ERROR, "Failed to create connection object\n");
647 show_error_message(_("Failed to setup libmpd"), TRUE);
648 abort();
650 TEC("Setting up mpd connection object");
652 * Connect signals to the connection object
654 mpd_signal_connect_status_changed(connection, GmpcStatusChangedCallback, NULL);
655 mpd_signal_connect_error(connection, error_callback, NULL);
656 mpd_signal_connect_connection_changed(connection, connection_changed, NULL);
658 * Just some trick to provide glib signals
660 gmpcconn = (GmpcConnection *) gmpc_connection_new();
661 g_signal_connect(G_OBJECT(gmpcconn), "connection_changed", G_CALLBACK(connection_changed_real), NULL);
662 g_signal_connect(G_OBJECT(gmpcconn), "status_changed", G_CALLBACK(gmpc_status_changed_callback_real), NULL);
664 TEC("Setting up mpd object signal system");
666 * New Metadata object
668 gmw = gmpc_meta_watcher_new();
669 TEC("Initializing metadata watcher");
672 * Add the internall plugins
675 /** init the error messages */
676 pl3_messages = playlist3_message_plugin_new();
679 playlist = (GtkTreeModel *)gmpc_mpddata_model_playlist_new(gmpcconn,connection);
680 g_object_ref_sink(playlist);
681 gmpc_mpddata_model_disable_image(GMPC_MPDDATA_MODEL(playlist));
682 /** current playlist */
683 plugin_add_new((GmpcPluginBase *)play_queue_plugin_new("current-pl"), 0,NULL);
685 /** file browser */
686 plugin_add(&file_browser_plug, 0, NULL);
687 /** Find Browser */
688 plugin_add(&find2_browser_plug, 0, NULL);
689 /* this shows the connection preferences */
690 plugin_add(&connection_plug, 0, NULL);
691 /* this the server preferences */
692 plugin_add(&server_plug, 0, NULL);
693 /* this shows the playlist preferences */
694 plugin_add(&playlist_plug, 0, NULL);
695 /* this shows the markup stuff */
696 plugin_add(&tag2_plug, 0, NULL);
697 #ifdef ENABLE_MMKEYS
698 plugin_add(&mmkeys_plug, 0, NULL);
699 #endif
700 /* the tray icon */
701 plugin_add(&tray_icon2_plug, 0, NULL);
703 /* Info3 data browser */
704 /* Playlist editor */
705 plugin_add(&playlist_editor_plugin, 0, NULL);
707 plugin_add(&statistics_plugin, 0, NULL);
708 plugin_add(&metadata_plug, 0, NULL);
709 plugin_add(&proxyplug, 0, NULL);
711 TEC("Loading internal plugins");
712 plugin_add_new(GMPC_PLUGIN_BASE(gmpc_test_plugin_new()), 0, NULL);
713 metadata_browser = gmpc_metadata_browser_new();
714 plugin_add_new(GMPC_PLUGIN_BASE(metadata_browser), 0, NULL);
715 plugin_add_new(GMPC_PLUGIN_BASE(gmpc_now_playing_new()), 0, NULL);
716 plugin_add_new(GMPC_PLUGIN_BASE(gmpc_plugin_metadata_prefetcher_new()), 0,NULL);
717 TEC("Loading new plugins");
719 * load dynamic plugins
721 if (!disable_plugins) {
722 #ifdef WIN32
723 packagedir = g_win32_get_package_installation_directory_of_module(NULL);
724 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Got %s as package installation dir", packagedir);
725 url = g_build_filename(packagedir, "lib", "gmpc", "plugins", NULL);
726 q_free(packagedir);
728 plugin_load_dir(url);
729 q_free(url);
730 #else
731 /* This is the right location to load gmpc plugins */
732 url = g_build_path(G_DIR_SEPARATOR_S, PACKAGE_LIB_DIR, "plugins", NULL);
733 plugin_load_dir(url);
734 q_free(url);
735 #endif
737 if(g_getenv("PLUGIN_DIR") != NULL) {
738 gchar *path = g_build_filename(g_getenv("PLUGIN_DIR"),NULL);
739 if (path && g_file_test(path, G_FILE_TEST_IS_DIR)) {
740 plugin_load_dir(path);
742 if(path) g_free(path);
744 /* user space dynamic plugins */
745 url = gmpc_get_user_path("plugins");
747 * if dir exists, try to load the plugins.
749 if (g_file_test(url, G_FILE_TEST_IS_DIR)) {
750 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Trying to load plugins in: %s", url);
751 if (!disable_plugins)
752 plugin_load_dir(url);
754 TEC("Loading plugins from %s", url);
755 q_free(url);
757 /* time todo some initialisation of plugins */
758 for (i = 0; i < num_plugins && plugins[i] != NULL; i++) {
759 TEC("Initializing plugin: %s", gmpc_plugin_get_name(plugins[i]));
760 gmpc_plugin_init(plugins[i]);
764 * Ask user about added/removed provider plugins
766 if(!disable_plugins)
767 meta_data_check_plugin_changed();
768 TEC("Metadata plugin changed check");
770 /* Set window debug, this is used for developers to visualize redraws */
771 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Create main window\n");
772 gdk_window_set_debug_updates(do_debug_updates);
774 * Create the main window
776 create_playlist3();
777 /* Initialize the message system */
778 //playlist3_message_init()
779 plugin_add_new(GMPC_PLUGIN_BASE(pl3_messages), 0, NULL);
781 TEC("Creating playlist window");
784 * First run dialog
786 * If gmpc is ran for the first time, we want to show a wizard that helps
787 * the user getting started.
789 if (cfg_get_single_value_as_int_with_default(config, "Default", "first-run", 1)) {
790 setup_assistant();
791 cfg_set_single_value_as_int(config, "Default", "first-run", 0);
792 TEC("Setup first run assistant");
796 * If autoconnect is enabled, tell gmpc that it's in state it should connect
798 if (cfg_get_single_value_as_int_with_default(config, "connection", "autoconnect", DEFAULT_AUTOCONNECT)) {
799 gmpc_connected = TRUE;
802 * create timeouts
803 * get the status every 1/2 second should be enough, but it's configurable.
805 g_timeout_add(cfg_get_single_value_as_int_with_default(config,
806 "connection",
807 "mpd-update-speed",
808 500), (GSourceFunc) update_mpd_status, NULL);
810 * create the autoconnect timeout, if autoconnect enable, it will check every 5 seconds
811 * if you are still connected, and reconnects you if not.
813 autoconnect_timeout = g_timeout_add_seconds(5, (GSourceFunc) autoconnect_callback, NULL);
816 * Call this when entering the main loop, so you are connected on startup, not 5 seconds later
818 gtk_init_add((GSourceFunc) autoconnect_callback, NULL);
819 if(fullscreen)
820 gtk_init_add((GSourceFunc) pl3_window_fullscreen, NULL);
823 * If the user wants gmpc to be started hidden, call pl3_hide after the mainloop started running
825 if (cfg_get_single_value_as_int_with_default(config, "Default", "start-hidden", FALSE) || start_hidden) {
826 g_timeout_add(250, (GSourceFunc)hide_on_start, NULL);
828 TEC("Setting up timers");
830 #ifdef ENABLE_MMKEYS
832 * Setup Multimedia Keys
835 * Create mmkeys object
837 keys = mmkeys_new();
839 * Connect the wanted key's
841 g_signal_connect(G_OBJECT(keys), "mm_playpause", G_CALLBACK(play_song), NULL);
842 g_signal_connect(G_OBJECT(keys), "mm_next", G_CALLBACK(next_song), NULL);
843 g_signal_connect(G_OBJECT(keys), "mm_prev", G_CALLBACK(prev_song), NULL);
844 g_signal_connect(G_OBJECT(keys), "mm_stop", G_CALLBACK(stop_song), NULL);
845 g_signal_connect(G_OBJECT(keys), "mm_fastforward", G_CALLBACK(song_fastforward), NULL);
846 g_signal_connect(G_OBJECT(keys), "mm_fastbackward", G_CALLBACK(song_fastbackward), NULL);
847 g_signal_connect(G_OBJECT(keys), "mm_repeat", G_CALLBACK(repeat_toggle), NULL);
848 g_signal_connect(G_OBJECT(keys), "mm_random", G_CALLBACK(random_toggle), NULL);
849 g_signal_connect(G_OBJECT(keys), "mm_raise", G_CALLBACK(create_playlist3), NULL);
850 g_signal_connect(G_OBJECT(keys), "mm_hide", G_CALLBACK(pl3_hide), NULL);
851 g_signal_connect(G_OBJECT(keys), "mm_toggle_hidden", G_CALLBACK(pl3_toggle_hidden), NULL);
852 g_signal_connect(G_OBJECT(keys), "mm_volume_up", G_CALLBACK(volume_up), NULL);
853 g_signal_connect(G_OBJECT(keys), "mm_volume_down", G_CALLBACK(volume_down), NULL);
854 g_signal_connect(G_OBJECT(keys), "mm_toggle_mute", G_CALLBACK(volume_toggle_mute), NULL);
855 g_signal_connect(G_OBJECT(keys), "mm_show_notification", G_CALLBACK(tray_icon2_create_tooltip), NULL);
856 g_signal_connect_swapped(G_OBJECT(keys), "mm_show_easy_command", G_CALLBACK(gmpc_easy_command_popup), gmpc_easy_command);
857 TEC("Setting up multimedia keys");
859 #endif
861 * run the main loop
863 gtk_main();
866 * Shutting Down
867 * cleaning up.
870 #ifndef WIN32
871 if (bacon_connection) {
872 bacon_message_connection_free(bacon_connection);
873 bacon_connection = NULL;
875 #endif
876 /* Quit _all_ downloads */
877 gmpc_easy_async_quit();
879 /* tell the plugins to save themself. */
880 for (i = 0; i < num_plugins && plugins[i] != NULL; i++) {
881 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Telling '%s' to save itself\n", gmpc_plugin_get_name(plugins[i]));
882 gmpc_plugin_save_yourself(plugins[i]);
884 /* Should fix some possible crashes */
885 gtk_tree_view_set_model(playlist3_get_category_tree_view(), NULL);
888 * Clear metadata struct
890 meta_data_destroy();
892 /* time todo some destruction of plugins */
893 for (i = 0; i < num_plugins && plugins[i] != NULL; i++) {
894 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Telling '%s' to destroy itself\n", gmpc_plugin_get_name(plugins[i]));
895 gmpc_plugin_destroy(plugins[i]);
898 //playlist3_message_destroy();
899 playlist3_destroy();
901 g_object_unref(playlist);
902 g_object_unref(G_OBJECT(gmw));
904 /* Destroy PanedSizeGroup */
905 g_object_unref(paned_size_group);
907 * Close the config file
909 TOC("Starting save config");
910 cfg_close(config);
911 TOC("Saved config");
912 g_object_unref(gmpc_profiles);
913 g_object_unref(gmpcconn);
916 * This now gets destroyed with the plugins
918 advanced_search_destroy();
920 * Destroy the connection object
922 mpd_free(connection);
924 xmlCleanupParser();
925 /* cleanup */
926 gmpc_mpddata_treeview_cleanup();
928 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Quit....\n");
929 return 0;
933 * Function to quiet the program
935 void main_quit(void)
937 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Quiting gmpc....");
939 * close playlist and store size
941 pl3_hide();
943 * Remove the autoconnect timeout,
945 if (autoconnect_timeout)
946 g_source_remove(autoconnect_timeout);
949 * Call the connection changed.
950 * so it saves the playlist pos
952 mpd_signal_connect_connection_changed(connection, NULL, NULL);
955 * Disconnect when connected
957 if (mpd_check_connected(connection)) {
958 if (cfg_get_single_value_as_int_with_default(config, "connection", "stop-on-exit", FALSE)) {
959 mpd_player_stop(connection);
961 mpd_disconnect(connection);
965 * Exit main loop
967 gtk_main_quit();
971 * Callback that get's called every 5 seconds,
972 * and tries to autoconnect
973 * (when enabled)
976 static int autoconnect_backoff = 0;
977 static int autoconnect_callback(void)
979 /* check if there is an connection. */
980 if (!mpd_check_connected(connection)) {
981 /* connect when autoconnect is enabled, the user wants to be connected
983 if (gmpc_connected
984 && cfg_get_single_value_as_int_with_default(config, "connection", "autoconnect", DEFAULT_AUTOCONNECT)) {
985 connect_to_mpd();
988 if(autoconnect_backoff < 60) autoconnect_backoff += 1;
989 /* keep the timeout running */
990 if(autoconnect_timeout)
991 g_source_remove(autoconnect_timeout);
992 autoconnect_timeout = g_timeout_add_seconds(5+autoconnect_backoff, (GSourceFunc) autoconnect_callback, NULL);
993 return FALSE;
996 static void init_stock_icons(void)
998 char *path;
1000 path = gmpc_get_full_image_path();
1001 gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), path);
1002 q_free(path);
1004 gtk_window_set_default_icon_name("gmpc");
1006 #ifdef WIN32
1007 /* The Windows gtkrc sets this to 0, so images don't work on buttons */
1008 gtk_settings_set_long_property(gtk_settings_get_default(), "gtk-button-images", TRUE, "main");
1009 #endif
1011 return;
1015 * Handle status changed callback from the libmpd object
1016 * This involves propegating the signal
1018 void GmpcStatusChangedCallback(MpdObj * mi, ChangedStatusType what, void *userdata)
1020 g_signal_emit_by_name(gmpcconn, "status-changed", mi, what);
1023 /* The actual handling of the status changed signal */
1024 static void gmpc_status_changed_callback_real(GmpcConnection * conn, MpdObj * mi, ChangedStatusType what, gpointer data)
1026 int i;
1027 if(what&MPD_CST_PERMISSION){
1028 advanced_search_update_taglist();
1031 * Make the plugins recieve the signals
1033 for (i = 0; i < num_plugins; i++) {
1034 gmpc_plugin_status_changed(plugins[i], mi, what);
1039 /*******************************
1040 * Error handling
1041 * TODO: Needs to be redone/rethought
1044 static void password_dialog_response(GtkWidget * dialog, gint response, gpointer data)
1046 gchar *path;
1047 switch (response) {
1048 case 0:
1051 return;
1053 case GTK_RESPONSE_OK:
1055 path = (char *)
1056 gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(xml_password_window, "pass_entry")));
1057 mpd_set_password(connection, path);
1058 if (gtk_toggle_button_get_active
1059 (GTK_TOGGLE_BUTTON(gtk_builder_get_object(xml_password_window, "ck_save_pass")))) {
1060 connection_set_password(path);
1062 mpd_send_password(connection);
1064 break;
1065 default:
1066 if (mpd_server_check_command_allowed(connection, "status") != MPD_SERVER_COMMAND_ALLOWED) {
1067 playlist3_show_error_message(_("GMPC has insufficient permissions on the mpd server."), ERROR_CRITICAL);
1068 mpd_disconnect(connection);
1070 break;
1072 gtk_widget_destroy((GtkWidget *)
1073 gtk_builder_get_object(xml_password_window, "password-dialog"));
1074 g_object_unref(xml_password_window);
1075 xml_password_window = NULL;
1078 static void password_dialog(int failed)
1080 GtkWidget *pl3_win = playlist3_get_window();
1081 gchar *path = NULL;
1082 if (xml_password_window)
1083 return;
1084 path = gmpc_get_full_glade_path("password-dialog.ui");
1085 xml_password_window = gtk_builder_new(); //glade_xml_new(path, "password-dialog",NULL);
1086 gtk_builder_add_from_file(xml_password_window, path, NULL);
1087 gtk_window_set_transient_for(GTK_WINDOW
1088 (gtk_builder_get_object(xml_password_window, "password-dialog")), GTK_WINDOW(pl3_win));
1089 q_free(path);
1090 if (!xml_password_window)
1091 return;
1092 if (failed) {
1093 path = g_strdup_printf(_("Failed to set password on: '%s'\nPlease try again"), mpd_get_hostname(connection));
1094 } else {
1095 path = g_strdup_printf(_("Please enter your password for: '%s'"), mpd_get_hostname(connection));
1097 gtk_label_set_text(GTK_LABEL(gtk_builder_get_object(xml_password_window, "pass_label")), path);
1098 q_free(path);
1100 g_signal_connect(G_OBJECT
1101 (gtk_builder_get_object
1102 (xml_password_window, "password-dialog")), "response",
1103 G_CALLBACK(password_dialog_response), xml_password_window);
1106 void send_password(void)
1108 password_dialog(FALSE);
1111 static int error_callback(MpdObj * mi, int error_id, char *error_msg, gpointer data)
1113 int autoconnect = cfg_get_single_value_as_int_with_default(config, "connection",
1114 "autoconnect",
1115 DEFAULT_AUTOCONNECT);
1116 /* if we are not connected we show a reconnect */
1117 if (!mpd_check_connected(mi)) {
1118 GtkWidget *button;
1119 char *str;
1120 /* no response? then we just ignore it when autoconnecting. */
1121 if (error_id == 15 && autoconnect)
1122 return FALSE;
1124 str = g_markup_printf_escaped("<b>%s %i: %s</b>", _("error code"), error_id, error_msg);
1125 playlist3_show_error_message(str, ERROR_CRITICAL);
1126 button = gtk_button_new_from_stock(GTK_STOCK_CONNECT);
1127 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(connect_to_mpd), NULL);
1128 playlist3_error_add_widget(button);
1129 g_free(str);
1130 } else {
1131 if (setup_assistant_is_running()
1132 && (error_id == MPD_ACK_ERROR_PERMISSION || error_id == MPD_ACK_ERROR_PASSWORD)) {
1133 gchar *str = g_markup_printf_escaped("<b>%s</b>",
1134 _("Insufficient permission to connect to mpd. Check password"));
1135 setup_assistant_set_error(str);
1136 q_free(str);
1137 return TRUE;
1139 if (error_id == MPD_ACK_ERROR_PASSWORD) {
1140 password_dialog(TRUE);
1141 } else if (error_id == MPD_ACK_ERROR_PERMISSION) {
1142 password_dialog(FALSE);
1143 } else {
1144 gchar *str = g_markup_printf_escaped("<b>%s %i: %s</b>",
1145 _("error code"), error_id,
1146 error_msg);
1147 playlist3_show_error_message(str, ERROR_CRITICAL);
1148 g_free(str);
1151 return FALSE;
1155 * handle a connection changed
1157 static void connection_changed(MpdObj * mi, int connected, gpointer data)
1159 /* propagate the signal to the connection object */
1160 if (mpd_check_connected(mi) != connected) {
1161 g_log(LOG_DOMAIN, G_LOG_LEVEL_ERROR,
1162 "Connection state differs from actual state: act: %i\n", !connected);
1165 * Check version
1167 if (connected && !mpd_server_check_version(mi, 0, 13, 0)) {
1168 gchar *value = g_markup_printf_escaped("<b>%s</b>",
1169 _("MPD versions before 0.13.0 are not supported"));
1170 /* disable user connect ! */
1171 gmpc_connected = FALSE;
1172 mpd_disconnect(mi);
1173 /* Give error */
1174 playlist3_show_error_message(value, ERROR_CRITICAL);
1175 g_free(value);
1177 /* Remove timeout */
1178 if (connected) {
1179 if (autoconnect_timeout)
1180 g_source_remove(autoconnect_timeout);
1181 autoconnect_timeout = 0;
1182 autoconnect_backoff = 0;
1185 * send password, first thing we do, if connected
1188 if (connected) {
1189 if (connection_use_auth()) {
1190 mpd_send_password(connection);
1192 advanced_search_update_taglist();
1195 if(connected){
1196 advanced_search_update_taglist();
1199 * force an update of status, to check password
1201 if (connected) {
1202 mpd_status_update(mi);
1203 if (connected != mpd_check_connected(mi)) {
1204 g_log(LOG_DOMAIN, G_LOG_LEVEL_ERROR, "State differs, exit");
1205 /* Probly disconnected when getting status.. exiting */
1206 return;
1210 /* remove this when it does not fix it */
1211 g_signal_emit_by_name (gmpcconn, "connection-changed", mi, mpd_check_connected(mi));
1214 static void connection_changed_real(GmpcConnection * obj, MpdObj * mi, int connected, gpointer data)
1216 int i = 0;
1217 INIT_TIC_TAC();
1220 * propegate signals
1222 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Connection changed %i-%i \n", connected, mpd_check_connected(mi));
1223 for (i = 0; i < num_plugins; i++) {
1224 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Connection changed plugin: %s\n", gmpc_plugin_get_name(plugins[i]));
1225 gmpc_plugin_mpd_connection_changed(plugins[i], mi, connected, NULL);
1226 TEC("Connection changed plugin: %s", gmpc_plugin_get_name(plugins[i]));
1231 * force an update of status
1233 if (connected)
1234 mpd_status_update(mi);
1236 if (connected) {
1237 playlist3_show_error_message(_("Connected to mpd"), ERROR_INFO);
1238 } else {
1239 playlist3_show_error_message(_("Disconnected from mpd"), ERROR_INFO);
1242 if (!connected) {
1243 if (autoconnect_timeout)
1244 g_source_remove(autoconnect_timeout);
1245 autoconnect_timeout = g_timeout_add_seconds(5, (GSourceFunc) autoconnect_callback, NULL);
1246 autoconnect_backoff = 0;
1251 * Shows an error message.
1252 * When block enabled, it will run in it's own mainloop.
1254 static void error_message_destroy(void)
1256 gtk_widget_destroy(error_dialog);
1257 error_dialog = NULL;
1258 gtk_list_store_clear(error_list_store);
1261 void show_error_message(const gchar * string, const int block)
1263 GtkTreeIter iter;
1264 GtkWidget *label = NULL;
1265 if (!error_dialog) {
1266 GtkWidget *hbox = NULL, *image;
1267 GtkWidget *vbox = NULL, *sw = NULL, *tree = NULL;
1268 GtkWidget *pl3_win = NULL;
1269 GtkCellRenderer *renderer;
1270 /* create dialog */
1271 error_dialog =
1272 gtk_dialog_new_with_buttons(_
1273 ("Error occurred during operation"),
1274 NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CLOSE, GTK_RESPONSE_OK, NULL);
1275 if (pl3_xml) {
1276 pl3_win = playlist3_get_window();
1277 gtk_window_set_transient_for(GTK_WINDOW(error_dialog), GTK_WINDOW(pl3_win));
1279 /** create list store */
1280 if (!error_list_store) {
1281 error_list_store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
1282 /* don't want this destroyed */
1283 g_object_ref(error_list_store);
1285 hbox = gtk_hbox_new(FALSE, 6);
1286 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(error_dialog)->vbox), hbox);
1287 gtk_container_set_border_width(GTK_CONTAINER(hbox), 9);
1289 /* Error image */
1290 image = gtk_image_new_from_stock(GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_DIALOG);
1291 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, TRUE, 0);
1293 vbox = gtk_vbox_new(FALSE, 6);
1294 gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
1296 /* Create label */
1297 label = gtk_label_new(_("The following error(s) occurred:"));
1298 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1299 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
1300 /** Create tree view */
1301 /* sw */
1302 sw = gtk_scrolled_window_new(NULL, NULL);
1303 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
1304 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1305 /* tree */
1306 tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(error_list_store));
1307 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), FALSE);
1308 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree), TRUE);
1309 /* add tree to sw */
1310 gtk_container_add(GTK_CONTAINER(sw), tree);
1311 /** add cell renderers */
1312 renderer = gtk_cell_renderer_text_new();
1313 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree),
1314 -1, _("Error Message"), renderer, "text", 0, NULL);
1316 /** add sw to vbox */
1317 gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
1319 gtk_list_store_append(error_list_store, &iter);
1320 gtk_list_store_set(error_list_store, &iter, 0, string, -1);
1321 g_signal_connect(G_OBJECT(error_dialog), "response", G_CALLBACK(error_message_destroy), NULL);
1322 gtk_widget_show_all(error_dialog);
1323 if (block) {
1324 gtk_dialog_run(GTK_DIALOG(error_dialog));
1325 } else {
1326 gtk_widget_show(error_dialog);
1330 static void create_gmpc_paths(void)
1333 * Create needed directories for mpd.
1336 /** create path */
1337 gchar *url = gmpc_get_user_path(NULL);
1340 * Check if ~/.gmpc/ exists
1341 * If not try to create it.
1343 if (!g_file_test(url, G_FILE_TEST_EXISTS)) {
1344 if (g_mkdir_with_parents(url, 0700) < 0) {
1345 g_log(LOG_DOMAIN, G_LOG_LEVEL_ERROR, "Failed to create: %s\n", url);
1346 show_error_message("Failed to create config directory.", TRUE);
1347 abort();
1351 * if it exists, check if it's a directory
1353 if (!g_file_test(url, G_FILE_TEST_IS_DIR)) {
1354 show_error_message("The config directory is not a directory.", TRUE);
1355 abort();
1356 } else {
1357 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "%s exist and is directory", url);
1359 /* Free the path */
1360 q_free(url);
1363 void print_version(void)
1365 printf(BOLD "%s\n", ("Gnome Music Player Client"));
1367 printf(GMPC_COPYRIGHT "\n\n" RESET);
1368 printf("%-25s: %s\n", ("Tagline"), GMPC_TAGLINE);
1369 printf("%-25s: %i.%i.%i\n", ("Version"), GMPC_MAJOR_VERSION, GMPC_MINOR_VERSION, GMPC_MICRO_VERSION);
1370 if (revision && revision[0] != '\0') {
1371 printf("%-25s: %s\n", ("Revision"), revision);
1374 /* vim: set noexpandtab ts=4 sw=4 sts=4 tw=120: */