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.
25 #include <libxml/parser.h>
27 /** Gtk/glib glade stuff */
29 #include <glib/gstdio.h>
33 #include "playlist3.h"
35 /** session support */
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"
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"
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
;
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
;
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
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;
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);
155 static void create_gmpc_paths(void);
157 void print_version(void);
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
)
171 g_log(LOG_DOMAIN_IPC
, G_LOG_LEVEL_DEBUG
, "got message: '%s'\n", message
);
175 if (strcmp(message
, "QUIT") == 0) {
176 printf("I've been told to quit, doing this now\n");
180 * Gives play,pause command
182 else if (strcmp(message
, "PLAY") == 0 || strcmp(message
, "PAUSE") == 0) {
188 else if (strcmp(message
, "NEXT") == 0) {
192 * Give previous command
194 else if (strcmp(message
, "PREV") == 0) {
200 else if (strcmp(message
, "STOP") == 0) {
202 } else if (strcmp(message
, "TOGGLE_VIEW") == 0) {
204 } else if (strcmp(message
, "HIDE_VIEW") == 0) {
206 } else if (strcmp(message
, "SHOW_VIEW") == 0) {
209 else if (strcmp(message
, "CONNECT") == 0) {
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]);
222 * Bring gmpc to front, as default action.
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");
243 g_log_set_handler(value
, G_LOG_LEVEL_MASK
|G_LOG_FLAG_FATAL
|G_LOG_FLAG_RECURSION
, g_log_default_handler
, NULL
);
246 static gboolean
hide_on_start(void)
251 int main(int argc
, char **argv
)
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>"},
308 * A string used severall times to create a path
315 g_log_set_default_handler(gmpc_log_func
, NULL
);
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
);
324 /* Ok, release version... no debug */
329 egg_sm_client_set_mode(EGG_SM_CLIENT_MODE_NO_RESTART
);
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
);
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
);
350 g_log(NULL
, G_LOG_LEVEL_ERROR
, "Failed to parse commandline options: %s", error
->message
);
354 /* Show the version, if requested */
361 * Set debug level, options are
364 * 2 = Error + Warning 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
);
381 TEC("Parsing command line options");
384 * initialize threading
386 g_log(LOG_DOMAIN
, G_LOG_LEVEL_DEBUG
, "Initializing threading");
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.
395 * This fixes the plugin crasher bug on windows.
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())
406 TEC("Initializing threading");
410 g_log(LOG_DOMAIN
, G_LOG_LEVEL_DEBUG
, "Initializing gtk ");
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
);
421 gtk_rc_add_default_file(url
);
426 gtk_init(&argc
, &argv
);
428 /* connect signal to Session manager to quit */
429 g_signal_connect( egg_sm_client_get(),
431 G_CALLBACK(main_quit
),
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
457 TEC("Check version and create paths");
460 * COMMANDLINE_OPTION:
461 * Cleanup the metadata database and quit.
464 /* start the metadata system */
466 printf("Cleaning up cover file..\n");
467 /* Call the cleanup */
468 metadata_cache_cleanup();
470 /* Destroy the meta data system and exit. */
472 TEC("Database cleanup");
477 * Open the config file
480 * Check if the user has forced a different config file location.
481 * else set to ~/.gmpc/gmpc.cfg
484 url
= gmpc_get_user_path("gmpc.cfg");
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 */
504 TEC("Opening config file: %s", 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");
516 FILE *fp
= g_fopen(url
, "a");
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
);
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");
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
);
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);
564 cfg_set_single_value_as_string(config
, "Default", "version", VERSION
);
570 TEC("New version check");
576 if (cfg_get_single_value_as_int_with_default(config
, "Default", "allow-multiple", FALSE
) == FALSE
) {
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
);
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
);
596 TEC("IPC setup and quitting");
600 bacon_message_connection_set_callback(bacon_connection
, bacon_on_message_received
, NULL
);
602 /* If user requested a quit, quit */
606 if (bacon_connection
)
607 bacon_message_connection_free(bacon_connection
);
619 paned_size_group
= (GObject
*)gmpc_paned_size_group_new();
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
630 TEC("Initializing metadata system");
635 g_log(LOG_DOMAIN
, G_LOG_LEVEL_DEBUG
, "Loading 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
);
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
);
686 plugin_add(&file_browser_plug
, 0, NULL
);
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
);
698 plugin_add(&mmkeys_plug
, 0, NULL
);
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
) {
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
);
728 plugin_load_dir(url
);
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
);
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
);
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
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
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");
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)) {
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
;
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
,
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
);
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");
832 * Setup Multimedia Keys
835 * Create mmkeys object
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");
871 if (bacon_connection
) {
872 bacon_message_connection_free(bacon_connection
);
873 bacon_connection
= NULL
;
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
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();
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");
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
);
926 gmpc_mpddata_treeview_cleanup();
928 g_log(LOG_DOMAIN
, G_LOG_LEVEL_DEBUG
, "Quit....\n");
933 * Function to quiet the program
937 g_log(LOG_DOMAIN
, G_LOG_LEVEL_DEBUG
, "Quiting gmpc....");
939 * close playlist and store size
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
);
971 * Callback that get's called every 5 seconds,
972 * and tries to autoconnect
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
984 && cfg_get_single_value_as_int_with_default(config
, "connection", "autoconnect", DEFAULT_AUTOCONNECT
)) {
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
);
996 static void init_stock_icons(void)
1000 path
= gmpc_get_full_image_path();
1001 gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), path
);
1004 gtk_window_set_default_icon_name("gmpc");
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");
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
)
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 /*******************************
1041 * TODO: Needs to be redone/rethought
1044 static void password_dialog_response(GtkWidget
* dialog
, gint response
, gpointer data
)
1053 case GTK_RESPONSE_OK
:
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
);
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
);
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();
1082 if (xml_password_window
)
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
));
1090 if (!xml_password_window
)
1093 path
= g_strdup_printf(_("Failed to set password on: '%s'\nPlease try again"), mpd_get_hostname(connection
));
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
);
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",
1115 DEFAULT_AUTOCONNECT
);
1116 /* if we are not connected we show a reconnect */
1117 if (!mpd_check_connected(mi
)) {
1120 /* no response? then we just ignore it when autoconnecting. */
1121 if (error_id
== 15 && autoconnect
)
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
);
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
);
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
);
1144 gchar
*str
= g_markup_printf_escaped("<b>%s %i: %s</b>",
1145 _("error code"), error_id
,
1147 playlist3_show_error_message(str
, ERROR_CRITICAL
);
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
);
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
;
1174 playlist3_show_error_message(value
, ERROR_CRITICAL
);
1177 /* Remove timeout */
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
1189 if (connection_use_auth()) {
1190 mpd_send_password(connection);
1192 advanced_search_update_taglist();
1196 advanced_search_update_taglist();
1199 * force an update of status, to check password
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 */
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
)
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
1234 mpd_status_update(mi
);
1237 playlist3_show_error_message(_("Connected to mpd"), ERROR_INFO
);
1239 playlist3_show_error_message(_("Disconnected from mpd"), ERROR_INFO
);
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
)
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
;
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
);
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);
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);
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 */
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
);
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
);
1324 gtk_dialog_run(GTK_DIALOG(error_dialog
));
1326 gtk_widget_show(error_dialog
);
1330 static void create_gmpc_paths(void)
1333 * Create needed directories for mpd.
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
);
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
);
1357 g_log(LOG_DOMAIN
, G_LOG_LEVEL_DEBUG
, "%s exist and is directory", 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: */