hythmbox-2.98/shell/rb-shell.c

Location Tool Test ID Function Issue
rb-shell.c:478:3 clang-analyzer Access to field 'g_type' results in a dereference of a null pointer (loaded from variable 'value')
rb-shell.c:528:3 clang-analyzer Access to field 'g_type' results in a dereference of a null pointer (loaded from variable 'value')
   1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
   2  *
   3  *  Copyright (C) 2002, 2003 Jorn Baayen
   4  *  Copyright (C) 2003, 2004 Colin Walters <walters@gnome.org>
   5  *
   6  *  This program 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 2, or (at your option)
   9  *  any later version.
  10  *
  11  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
  12  *  GStreamer plugins to be used and distributed together with GStreamer
  13  *  and Rhythmbox. This permission is above and beyond the permissions granted
  14  *  by the GPL license by which Rhythmbox is covered. If you modify this code
  15  *  you may extend this exception to your version of the code, but you are not
  16  *  obligated to do so. If you do not wish to do so, delete this exception
  17  *  statement from your version.
  18  *
  19  *  This program is distributed in the hope that it will be useful,
  20  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  21  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  22  *  GNU General Public License for more details.
  23  *
  24  *  You should have received a copy of the GNU General Public License
  25  *  along with this program; if not, write to the Free Software
  26  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  27  *
  28  */
  29 
  30 /**
  31  * SECTION:rb-shell
  32  * @short_description: holds the Rhythmbox main window and everything else
  33  *
  34  * RBShell is the main application class in Rhythmbox.  It creates and holds
  35  * references to the other main objects (#RBShellPlayer, #RhythmDB, #RBDisplayPageTree),
  36  * constructs the main window UI, and provides the basic DBus interface.
  37  */
  38 
  39 #include <config.h>
  40 
  41 #include <string.h>
  42 #include <ctype.h>
  43 #include <stdio.h>
  44 #include <sys/stat.h>
  45 
  46 #include <glib/gi18n.h>
  47 #include <gdk/gdk.h>
  48 #include <gdk/gdkx.h>
  49 #include <gtk/gtk.h>
  50 #include <girepository.h>
  51 
  52 #include <libpeas/peas.h>
  53 #include <libpeas-gtk/peas-gtk.h>
  54 
  55 #include <gst/gst.h>
  56 
  57 #ifdef HAVE_MMKEYS
  58 #include <X11/XF86keysym.h>
  59 #endif /* HAVE_MMKEYS */
  60 
  61 #include "rb-shell.h"
  62 #include "rb-debug.h"
  63 #include "rb-dialog.h"
  64 #ifdef WITH_RHYTHMDB_TREE
  65 #include "rhythmdb-tree.h"
  66 #else
  67 #error "no database specified. configure broken?"
  68 #endif
  69 #include "rb-stock-icons.h"
  70 #include "rb-display-page-tree.h"
  71 #include "rb-display-page-group.h"
  72 #include "rb-file-helpers.h"
  73 #include "rb-source.h"
  74 #include "rb-playlist-manager.h"
  75 #include "rb-removable-media-manager.h"
  76 #include "rb-track-transfer-queue.h"
  77 #include "rb-shell-clipboard.h"
  78 #include "rb-shell-player.h"
  79 #include "rb-statusbar.h"
  80 #include "rb-shell-preferences.h"
  81 #include "rb-library-source.h"
  82 #include "rb-podcast-source.h"
  83 #include "totem-pl-parser.h"
  84 #include "rb-shell-preferences.h"
  85 #include "rb-playlist-source.h"
  86 #include "rb-static-playlist-source.h"
  87 #include "rb-play-queue-source.h"
  88 #include "rb-missing-files-source.h"
  89 #include "rb-import-errors-source.h"
  90 #include "rb-util.h"
  91 #include "rb-display-page-model.h"
  92 #include "rb-song-info.h"
  93 #include "rb-marshal.h"
  94 #include "rb-missing-plugins.h"
  95 #include "rb-header.h"
  96 #include "rb-podcast-manager.h"
  97 #include "rb-podcast-main-source.h"
  98 #include "rb-podcast-entry-types.h"
  99 #include "rb-ext-db.h"
 100 #include "rb-auto-playlist-source.h"
 101 
 102 #include "eggsmclient.h"
 103 
 104 #define UNINSTALLED_PLUGINS_LOCATION "plugins"
 105 
 106 #define PLAYING_ENTRY_NOTIFY_TIME 4
 107 
 108 static void rb_shell_class_init (RBShellClass *klass);
 109 static void rb_shell_init (RBShell *shell);
 110 static void rb_shell_constructed (GObject *object);
 111 static void rb_shell_finalize (GObject *object);
 112 static void rb_shell_set_property (GObject *object,
 113 				   guint prop_id,
 114 				   const GValue *value,
 115 				   GParamSpec *pspec);
 116 static void rb_shell_get_property (GObject *object,
 117 				   guint prop_id,
 118 				   GValue *value,
 119 				   GParamSpec *pspec);
 120 static void rb_shell_activate (GApplication *app);
 121 static void rb_shell_open (GApplication *app, GFile **files, int n_files, const char *hint);
 122 static gboolean rb_shell_local_command_line (GApplication *app, gchar ***args, int *exit_status);
 123 static gboolean rb_shell_get_visibility (RBShell *shell);
 124 static gboolean rb_shell_window_state_cb (GtkWidget *widget,
 125 					  GdkEventWindowState *event,
 126 					  RBShell *shell);
 127 static gboolean rb_shell_window_configure_cb (GtkWidget *win,
 128 					      GdkEventConfigure*event,
 129 					      RBShell *shell);
 130 static gboolean rb_shell_window_delete_cb (GtkWidget *win,
 131 			                   GdkEventAny *event,
 132 			                   RBShell *shell);
 133 static gboolean rb_shell_key_press_event_cb (GtkWidget *win,
 134 					     GdkEventKey *event,
 135 					     RBShell *shell);
 136 static void rb_shell_sync_window_state (RBShell *shell, gboolean dont_maximise);
 137 static void rb_shell_sync_paned (RBShell *shell);
 138 static void rb_shell_sync_party_mode (RBShell *shell);
 139 static void rb_shell_select_page (RBShell *shell, RBDisplayPage *display_page);
 140 static void display_page_selected_cb (RBDisplayPageTree *display_page_tree,
 141 				      RBDisplayPage *page,
 142 				      RBShell *shell);
 143 static void rb_shell_playing_source_changed_cb (RBShellPlayer *player,
 144 						RBSource *source,
 145 						RBShell *shell);
 146 static void rb_shell_playing_from_queue_cb (RBShellPlayer *player,
 147 					    GParamSpec *arg,
 148 					    RBShell *shell);
 149 static void rb_shell_db_save_error_cb (RhythmDB *db,
 150 				       const char *uri, const GError *error,
 151 				       RBShell *shell);
 152 
 153 static void rb_shell_playlist_added_cb (RBPlaylistManager *mgr, RBSource *source, RBShell *shell);
 154 static void rb_shell_playlist_created_cb (RBPlaylistManager *mgr, RBSource *source, RBShell *shell);
 155 static void rb_shell_medium_added_cb (RBRemovableMediaManager *mgr, RBSource *source, RBShell *shell);
 156 static void rb_shell_display_page_deleted_cb (RBDisplayPage *page, RBShell *shell);
 157 static void rb_shell_set_window_title (RBShell *shell, const char *window_title);
 158 static void rb_shell_player_window_title_changed_cb (RBShellPlayer *player,
 159 					             const char *window_title,
 160 					             RBShell *shell);
 161 static void rb_shell_cmd_about (GtkAction *action,
 162 		                RBShell *shell);
 163 static void rb_shell_cmd_contents (GtkAction *action,
 164 				   RBShell *shell);
 165 static void rb_shell_cmd_quit (GtkAction *action,
 166 			       RBShell *shell);
 167 static void rb_shell_cmd_preferences (GtkAction *action,
 168 		                      RBShell *shell);
 169 static void rb_shell_cmd_plugins (GtkAction *action,
 170 		                  RBShell *shell);
 171 static void rb_shell_cmd_add_music (GtkAction *action, RBShell *shell);
 172 
 173 static void rb_shell_cmd_current_song (GtkAction *action,
 174 				       RBShell *shell);
 175 static void rb_shell_jump_to_current (RBShell *shell);
 176 static void rb_shell_jump_to_entry_with_source (RBShell *shell, RBSource *source,
 177 						RhythmDBEntry *entry);
 178 static void rb_shell_play_entry (RBShell *shell, RhythmDBEntry *entry);
 179 static void rb_shell_cmd_view_all (GtkAction *action,
 180 				   RBShell *shell);
 181 static void rb_shell_view_party_mode_changed_cb (GtkAction *action,
 182 						 RBShell *shell);
 183 static void rb_shell_view_statusbar_changed_cb (GtkAction *action,
 184 						RBShell *shell);
 185 static void rb_shell_view_queue_as_sidebar_changed_cb (GtkAction *action,
 186 						       RBShell *shell);
 187 static void rb_shell_load_complete_cb (RhythmDB *db, RBShell *shell);
 188 static void rb_shell_sync_pane_visibility (RBShell *shell);
 189 static void rb_shell_sync_statusbar_visibility (RBShell *shell);
 190 static void rb_shell_set_visibility (RBShell *shell,
 191 				     gboolean initial,
 192 				     gboolean visible);
 193 static void display_page_tree_drag_received_cb (RBDisplayPageTree *display_page_tree,
 194 						RBDisplayPage *page,
 195 						GtkSelectionData *data,
 196 						RBShell *shell);
 197 
 198 static void paned_size_allocate_cb (GtkWidget *widget,
 199 				    GtkAllocation *allocation,
 200 				    RBShell *shell);
 201 static void rb_shell_volume_widget_changed_cb (GtkScaleButton *vol,
 202 					       gdouble volume,
 203 					       RBShell *shell);
 204 static void rb_shell_player_volume_changed_cb (RBShellPlayer *player,
 205 					       GParamSpec *arg,
 206 					       RBShell *shell);
 207 
 208 static void rb_shell_session_init (RBShell *shell);
 209 
 210 static gboolean rb_shell_visibility_changing (RBShell *shell, gboolean initial, gboolean visible);
 211 
 212 enum
 213 {
 214 	PROP_NONE,
 215 	PROP_NO_REGISTRATION,
 216 	PROP_NO_UPDATE,
 217 	PROP_DRY_RUN,
 218 	PROP_RHYTHMDB_FILE,
 219 	PROP_PLAYLISTS_FILE,
 220 	PROP_SELECTED_PAGE,
 221 	PROP_DB,
 222 	PROP_UI_MANAGER,
 223 	PROP_CLIPBOARD,
 224 	PROP_PLAYLIST_MANAGER,
 225 	PROP_REMOVABLE_MEDIA_MANAGER,
 226 	PROP_SHELL_PLAYER,
 227 	PROP_WINDOW,
 228 	PROP_PREFS,
 229 	PROP_QUEUE_SOURCE,
 230 	PROP_PROXY_CONFIG,
 231 	PROP_LIBRARY_SOURCE,
 232 	PROP_DISPLAY_PAGE_MODEL,
 233 	PROP_DISPLAY_PAGE_TREE,
 234 	PROP_VISIBILITY,
 235 	PROP_TRACK_TRANSFER_QUEUE,
 236 	PROP_AUTOSTARTED,
 237 	PROP_DISABLE_PLUGINS
 238 };
 239 
 240 enum
 241 {
 242 	VISIBILITY_CHANGED,
 243 	VISIBILITY_CHANGING,
 244 	CREATE_SONG_INFO,
 245 	NOTIFY_PLAYING_ENTRY,
 246 	NOTIFY_CUSTOM,
 247 	LAST_SIGNAL
 248 };
 249 
 250 static guint rb_shell_signals[LAST_SIGNAL] = { 0 };
 251 
 252 G_DEFINE_TYPE (RBShell, rb_shell, GTK_TYPE_APPLICATION)
 253 
 254 struct _RBShellPrivate
 255 {
 256 	GtkWidget *window;
 257 	gboolean iconified;
 258 
 259 	GtkUIManager *ui_manager;
 260 	GtkActionGroup *actiongroup;
 261 	guint source_ui_merge_id;
 262 
 263 	GtkWidget *main_vbox;
 264 	GtkWidget *paned;
 265 	GtkWidget *right_paned;
 266 	RBDisplayPageTree *display_page_tree;
 267 	GtkWidget *notebook;
 268 	GtkWidget *queue_paned;
 269 	GtkWidget *queue_sidebar;
 270 
 271 	GtkBox *sidebar_container;
 272 	GtkBox *right_sidebar_container;
 273 	GtkBox *top_container;
 274 	GtkBox *bottom_container;
 275 	guint right_sidebar_widget_count;
 276 
 277 	RBDisplayPageModel *display_page_model;
 278 	GList *sources;				/* kill? */
 279 	GHashTable *sources_hash;		/* kill? */
 280 
 281 	guint async_state_save_id;
 282 	guint save_playlist_id;
 283 
 284 	gboolean shutting_down;
 285 	gboolean load_complete;
 286 
 287 	gboolean no_registration;
 288 	gboolean no_update;
 289 	gboolean dry_run;
 290 	gboolean autostarted;
 291 	gboolean disable_plugins;
 292 	char *rhythmdb_file;
 293 	char *playlists_file;
 294 
 295 	RhythmDB *db;
 296 	RBExtDB *art_store;
 297 
 298 	RBShellPlayer *player_shell;
 299 	RBShellClipboard *clipboard_shell;
 300 	RBHeader *header;
 301 	RBStatusbar *statusbar;
 302 	RBPlaylistManager *playlist_manager;
 303 	RBRemovableMediaManager *removable_media_manager;
 304 	RBTrackTransferQueue *track_transfer_queue;
 305 	RBPodcastManager *podcast_manager;
 306 
 307 	RBLibrarySource *library_source;
 308 	RBPodcastSource *podcast_source;
 309 	RBPlaylistSource *queue_source;
 310 	RBSource *missing_files_source;
 311 	RBSource *import_errors_source;
 312 
 313 	RBDisplayPage *selected_page;
 314 
 315 	GtkWidget *prefs;
 316 	GtkWidget *plugins;
 317 
 318 	GtkWidget *volume_button;
 319 	gboolean syncing_volume;
 320 
 321 	char *cached_title;
 322 	gboolean cached_playing;
 323 
 324 	gboolean party_mode;
 325 
 326 	GSettings *settings;
 327 
 328 	GSettings *plugin_settings;
 329 	PeasEngine *plugin_engine;
 330 	PeasExtensionSet *activatable;
 331 };
 332 
 333 
 334 static GtkActionEntry rb_shell_actions [] =
 335 {
 336 	{ "Music", NULL, N_("_Music") },
 337 	{ "Edit", NULL, N_("_Edit") },
 338 	{ "View", NULL, N_("_View") },
 339 	{ "Control", NULL, N_("_Control") },
 340 	{ "Tools", NULL, N_("_Tools") },
 341 	{ "Help", NULL, N_("_Help") },
 342 
 343 	{ "MusicAdd", GTK_STOCK_OPEN, N_("Add Music..."), "<control>O",
 344 	  N_("Add music to the library"),
 345 	  G_CALLBACK (rb_shell_cmd_add_music) },
 346 	{ "HelpAbout", GTK_STOCK_ABOUT, N_("_About"), NULL,
 347 	  N_("Show information about Rhythmbox"),
 348 	  G_CALLBACK (rb_shell_cmd_about) },
 349 	{ "HelpContents", GTK_STOCK_HELP, N_("_Contents"), "F1",
 350 	  N_("Display Rhythmbox help"),
 351 	  G_CALLBACK (rb_shell_cmd_contents) },
 352 	{ "MusicQuit", GTK_STOCK_QUIT, N_("_Quit"), "<control>Q",
 353 	  N_("Quit Rhythmbox"),
 354 	  G_CALLBACK (rb_shell_cmd_quit) },
 355 	{ "EditPreferences", GTK_STOCK_PREFERENCES, N_("Prefere_nces"), NULL,
 356 	  N_("Edit Rhythmbox preferences"),
 357 	  G_CALLBACK (rb_shell_cmd_preferences) },
 358 	{ "EditPlugins", NULL, N_("Plu_gins"), NULL,
 359 	  N_("Change and configure plugins"),
 360 	  G_CALLBACK (rb_shell_cmd_plugins) },
 361 	{ "ViewAll", NULL, N_("Show _All Tracks"), "<control>Y",
 362 	  N_("Show all tracks in this music source"),
 363 	  G_CALLBACK (rb_shell_cmd_view_all) },
 364 	{ "ViewJumpToPlaying", GTK_STOCK_JUMP_TO, N_("_Jump to Playing Song"), "<control>J",
 365 	  N_("Scroll the view to the currently playing song"),
 366 	  G_CALLBACK (rb_shell_cmd_current_song) },
 367 };
 368 static guint rb_shell_n_actions = G_N_ELEMENTS (rb_shell_actions);
 369 
 370 static GtkToggleActionEntry rb_shell_toggle_entries [] =
 371 {
 372 	{ "ViewSidePane", NULL, N_("Side _Pane"), "F9",
 373 	  N_("Change the visibility of the side pane"),
 374 	  NULL, TRUE },
 375 	{ "ViewPartyMode", NULL, N_("Party _Mode"), "F11",
 376 	  N_("Change the status of the party mode"),
 377 	  G_CALLBACK (rb_shell_view_party_mode_changed_cb), FALSE },
 378 	{ "ViewQueueAsSidebar", NULL, N_("Play _Queue as Side Pane"), "<control>K",
 379 	  N_("Change whether the queue is visible as a source or a sidebar"),
 380 	  G_CALLBACK (rb_shell_view_queue_as_sidebar_changed_cb) },
 381         { "ViewStatusbar", NULL, N_("S_tatusbar"), NULL,
 382 	  N_("Change the visibility of the statusbar"),
 383 	  G_CALLBACK (rb_shell_view_statusbar_changed_cb), TRUE },
 384 	{ "ViewSongPositionSlider", NULL, N_("_Song Position Slider"), NULL,
 385 	  N_("Change the visibility of the song position slider"),
 386 	  NULL, TRUE },
 387 	{ "ViewAlbumArt", NULL, N_("_Album Art"), NULL,
 388 	  N_("Change the visibility of the album art display"),
 389 	  NULL, TRUE },
 390         { "ViewBrowser", NULL, N_("_Browse"), "<control>B",
 391 	  N_("Change the visibility of the browser"),
 392 	  NULL, TRUE }
 393 };
 394 static guint rb_shell_n_toggle_entries = G_N_ELEMENTS (rb_shell_toggle_entries);
 395 
 396 static void
 397 rb_shell_activate (GApplication *app)
 398 {
 399 	rb_shell_present (RB_SHELL (app), gtk_get_current_event_time (), NULL);
 400 }
 401 
 402 static void
 403 rb_shell_open (GApplication *app, GFile **files, int n_files, const char *hint)
 404 {
 405 	int i;
 406 
 407 	for (i = 0; i < n_files; i++) {
 408 		char *uri;
 409 
 410 		uri = g_file_get_uri (files[i]);
 411 
 412 		/*
 413 		 * rb_uri_exists won't work if the location isn't mounted.
 414 		 * however, things that are interesting to mount are generally
 415 		 * non-local, so we'll process them anyway.
 416 		 */
 417 		if (rb_uri_is_local (uri) == FALSE || rb_uri_exists (uri)) {
 418 			rb_shell_load_uri (RB_SHELL (app), uri, TRUE, NULL);
 419 		}
 420 		g_free (uri);
 421 	}
 422 }
 423 
 424 static void
 425 load_state_changed_cb (GActionGroup *action_group, const char *action_name, GVariant *state, GPtrArray *files)
 426 {
 427 	gboolean loaded;
 428 	gboolean scanned;
 429 
 430 	if (g_strcmp0 (action_name, "LoadURI") != 0) {
 431 		return;
 432 	}
 433 
 434 	g_variant_get (state, "(bb)", &loaded, &scanned);
 435 	if (loaded) {
 436 		rb_debug ("opening files now");
 437 		g_signal_handlers_disconnect_by_func (action_group, load_state_changed_cb, files);
 438 
 439 		g_application_open (G_APPLICATION (action_group), (GFile **)files->pdata, files->len, "");
 440 		g_ptr_array_free (files, TRUE);
 441 	}
 442 }
 443 
 444 
 445 
 446 static GMountOperation *
 447 rb_shell_create_mount_op_cb (RhythmDB *db, RBShell *shell)
 448 {
 449 	/* we don't want the operation to be modal, so we don't associate it with the window. */
 450 	GMountOperation *op = gtk_mount_operation_new (NULL);
 451 	gtk_mount_operation_set_screen (GTK_MOUNT_OPERATION (op),
 452 					gtk_window_get_screen (GTK_WINDOW (shell->priv->window)));
 453 	return op;
 454 }
 455 
 456 static GValue *
 457 load_external_art_cb (RBExtDB *store, GValue *value, RBShell *shell)
 458 {
 459 	const char *data;
 460 	gsize data_size;
 461 	GdkPixbufLoader *loader;
 462 	GdkPixbuf *pixbuf;
 463 	GValue *v;
 464 	GError *error = NULL;
 465 
 466 	if (G_VALUE_HOLDS_STRING (value)) {
 467 		data = g_value_get_string (value);
 468 		data_size = strlen (data);
 469 	} else if (G_VALUE_HOLDS (value, G_TYPE_GSTRING)) {
 470 		GString *str = g_value_get_boxed (value);
 471 		data = (const char *)str->str;
 472 		data_size = str->len;
 473 	} else if (G_VALUE_HOLDS (value, G_TYPE_BYTE_ARRAY)) {
 474 		GByteArray *bytes = g_value_get_boxed (value);
 475 		data = (const char *)bytes->data;
 476 		data_size = bytes->len;
 477 	} else {
 478 		rb_debug ("unable to load pixbufs from values of type %s", G_VALUE_TYPE_NAME (value));
Access to field 'g_type' results in a dereference of a null pointer (loaded from variable 'value')
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

479 return NULL; 480 } 481 482 loader = gdk_pixbuf_loader_new (); 483 gdk_pixbuf_loader_write (loader, (const guchar *)data, data_size, &error); 484 if (error != NULL) { 485 rb_debug ("unable to load pixbuf: %s", error->message); 486 g_clear_error (&error); 487 g_object_unref (loader); 488 return NULL; 489 } 490 491 gdk_pixbuf_loader_close (loader, &error); 492 if (error != NULL) { 493 rb_debug ("unable to load pixbuf: %s", error->message); 494 g_clear_error (&error); 495 g_object_unref (loader); 496 return NULL; 497 } 498 499 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); 500 v = g_new0 (GValue, 1); 501 g_value_init (v, GDK_TYPE_PIXBUF); 502 g_value_set_object (v, pixbuf); 503 g_object_unref (loader); 504 505 return v; 506 } 507 508 static GValue * 509 store_external_art_cb (RBExtDB *store, GValue *value, RBShell *shell) 510 { 511 const char *jpeg_format = "jpeg"; 512 char *jpeg_format_options[] = { "quality", NULL }; 513 char *jpeg_format_values[] = { "100", NULL }; 514 const char *png_format = "png"; 515 char *png_format_options[] = { "compression", NULL }; 516 char *png_format_values[] = { "9", NULL }; 517 const char *format; 518 char **format_options; 519 char **format_values; 520 GdkPixbuf *pixbuf; 521 char *data; 522 gsize data_size; 523 GError *error = NULL; 524 GString *s; 525 GValue *v; 526 527 if (G_VALUE_HOLDS (value, GDK_TYPE_PIXBUF) == FALSE) { 528 rb_debug ("can't store values of type %s", G_VALUE_TYPE_NAME (value));
Access to field 'g_type' results in a dereference of a null pointer (loaded from variable 'value')
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

529 return NULL; 530 } 531 532 pixbuf = GDK_PIXBUF (g_value_get_object (value)); 533 534 /* switch to png if the image has an alpha channel */ 535 if (gdk_pixbuf_get_has_alpha (pixbuf)) { 536 format = png_format; 537 format_options = png_format_options; 538 format_values = png_format_values; 539 } else { 540 format = jpeg_format; 541 format_options = jpeg_format_options; 542 format_values = jpeg_format_values; 543 } 544 545 if (gdk_pixbuf_save_to_bufferv (pixbuf, &data, &data_size, format, format_options, format_values, &error) == FALSE) { 546 rb_debug ("unable to save pixbuf: %s", error->message); 547 g_clear_error (&error); 548 return NULL; 549 } 550 551 s = g_slice_new0 (GString); 552 s->str = data; 553 s->len = data_size; 554 s->allocated_len = data_size; 555 v = g_new0 (GValue, 1); 556 g_value_init (v, G_TYPE_GSTRING); 557 g_value_take_boxed (v, s); 558 return v; 559 } 560 561 static void 562 construct_db (RBShell *shell) 563 { 564 char *pathname; 565 566 /* Initialize the database */ 567 rb_debug ("creating database object"); 568 rb_profile_start ("creating database object"); 569 570 if (shell->priv->rhythmdb_file) { 571 pathname = g_strdup (shell->priv->rhythmdb_file); 572 } else { 573 pathname = rb_find_user_data_file ("rhythmdb.xml"); 574 } 575 576 #ifdef WITH_RHYTHMDB_TREE 577 shell->priv->db = rhythmdb_tree_new (pathname); 578 #elif defined(WITH_RHYTHMDB_GDA) 579 shell->priv->db = rhythmdb_gda_new (pathname); 580 #endif 581 g_free (pathname); 582 583 if (shell->priv->dry_run) 584 g_object_set (shell->priv->db, "dry-run", TRUE, NULL); 585 if (shell->priv->no_update) 586 g_object_set (shell->priv->db, "no-update", TRUE, NULL); 587 588 g_signal_connect_object (G_OBJECT (shell->priv->db), "load-complete", 589 G_CALLBACK (rb_shell_load_complete_cb), shell, 590 0); 591 g_signal_connect_object (G_OBJECT (shell->priv->db), "create-mount-op", 592 G_CALLBACK (rb_shell_create_mount_op_cb), shell, 593 0); 594 595 shell->priv->art_store = rb_ext_db_new ("album-art"); 596 g_signal_connect (shell->priv->art_store, "load", G_CALLBACK (load_external_art_cb), shell); 597 g_signal_connect (shell->priv->art_store, "store", G_CALLBACK (store_external_art_cb), shell); 598 599 rb_profile_end ("creating database object"); 600 } 601 602 static void 603 construct_widgets (RBShell *shell) 604 { 605 GtkWindow *win; 606 607 rb_profile_start ("constructing widgets"); 608 609 /* initialize UI */ 610 win = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL)); 611 gtk_window_set_title (win, _("Rhythmbox")); 612 613 shell->priv->window = GTK_WIDGET (win); 614 shell->priv->iconified = FALSE; 615 g_signal_connect_object (G_OBJECT (win), "window-state-event", 616 G_CALLBACK (rb_shell_window_state_cb), 617 shell, 0); 618 619 g_signal_connect_object (G_OBJECT (win), "configure-event", 620 G_CALLBACK (rb_shell_window_configure_cb), 621 shell, 0); 622 623 /* connect after, so that things can affect behaviour */ 624 g_signal_connect_object (G_OBJECT (win), "delete_event", 625 G_CALLBACK (rb_shell_window_delete_cb), 626 shell, G_CONNECT_AFTER); 627 628 gtk_widget_add_events (GTK_WIDGET (win), GDK_KEY_PRESS_MASK); 629 g_signal_connect_object (G_OBJECT(win), "key_press_event", 630 G_CALLBACK (rb_shell_key_press_event_cb), shell, 0); 631 632 rb_debug ("shell: initializing shell services"); 633 634 shell->priv->podcast_manager = rb_podcast_manager_new (shell->priv->db); 635 shell->priv->track_transfer_queue = rb_track_transfer_queue_new (shell); 636 shell->priv->ui_manager = gtk_ui_manager_new (); 637 shell->priv->source_ui_merge_id = gtk_ui_manager_new_merge_id (shell->priv->ui_manager); 638 639 shell->priv->player_shell = rb_shell_player_new (shell->priv->db, 640 shell->priv->ui_manager, 641 shell->priv->actiongroup); 642 g_signal_connect_object (G_OBJECT (shell->priv->player_shell), 643 "playing-source-changed", 644 G_CALLBACK (rb_shell_playing_source_changed_cb), 645 shell, 0); 646 g_signal_connect_object (G_OBJECT (shell->priv->player_shell), 647 "notify::playing-from-queue", 648 G_CALLBACK (rb_shell_playing_from_queue_cb), 649 shell, 0); 650 g_signal_connect_object (G_OBJECT (shell->priv->player_shell), 651 "window_title_changed", 652 G_CALLBACK (rb_shell_player_window_title_changed_cb), 653 shell, 0); 654 shell->priv->clipboard_shell = rb_shell_clipboard_new (shell->priv->actiongroup, 655 shell->priv->ui_manager, 656 shell->priv->db); 657 658 shell->priv->display_page_tree = rb_display_page_tree_new (shell); 659 gtk_widget_show_all (GTK_WIDGET (shell->priv->display_page_tree)); 660 g_signal_connect_object (shell->priv->display_page_tree, "drop-received", 661 G_CALLBACK (display_page_tree_drag_received_cb), shell, 0); 662 g_object_get (shell->priv->display_page_tree, "model", &shell->priv->display_page_model, NULL); 663 rb_display_page_group_add_core_groups (G_OBJECT (shell), shell->priv->display_page_model); 664 665 shell->priv->header = rb_header_new (shell->priv->player_shell, shell->priv->db); 666 g_object_set (shell->priv->player_shell, "header", shell->priv->header, NULL); 667 gtk_widget_show (GTK_WIDGET (shell->priv->header)); 668 g_settings_bind (shell->priv->settings, "time-display", shell->priv->header, "show-remaining", G_SETTINGS_BIND_DEFAULT); 669 670 shell->priv->statusbar = rb_statusbar_new (shell->priv->db, 671 shell->priv->ui_manager, 672 shell->priv->track_transfer_queue); 673 gtk_widget_show (GTK_WIDGET (shell->priv->statusbar)); 674 675 g_signal_connect_object (shell->priv->display_page_tree, "selected", 676 G_CALLBACK (display_page_selected_cb), shell, 0); 677 678 shell->priv->notebook = gtk_notebook_new (); 679 gtk_widget_show (shell->priv->notebook); 680 gtk_notebook_set_show_tabs (GTK_NOTEBOOK (shell->priv->notebook), FALSE); 681 gtk_notebook_set_show_border (GTK_NOTEBOOK (shell->priv->notebook), FALSE); 682 g_signal_connect_object (shell->priv->display_page_tree, 683 "size-allocate", 684 G_CALLBACK (paned_size_allocate_cb), 685 shell, 0); 686 687 shell->priv->queue_source = RB_PLAYLIST_SOURCE (rb_play_queue_source_new (shell)); 688 g_object_set (shell->priv->player_shell, "queue-source", shell->priv->queue_source, NULL); 689 g_object_set (shell->priv->clipboard_shell, "queue-source", shell->priv->queue_source, NULL); 690 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->queue_source), RB_DISPLAY_PAGE_GROUP_LIBRARY); 691 g_object_get (shell->priv->queue_source, "sidebar", &shell->priv->queue_sidebar, NULL); 692 gtk_widget_show_all (shell->priv->queue_sidebar); 693 gtk_widget_set_no_show_all (shell->priv->queue_sidebar, TRUE); 694 695 /* places for plugins to put UI */ 696 shell->priv->top_container = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0)); 697 shell->priv->bottom_container = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0)); 698 shell->priv->sidebar_container = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0)); 699 shell->priv->right_sidebar_container = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0)); 700 701 /* set up sidebars */ 702 shell->priv->paned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL); 703 shell->priv->right_paned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL); 704 gtk_widget_show_all (shell->priv->right_paned); 705 g_signal_connect_object (G_OBJECT (shell->priv->right_paned), 706 "size-allocate", 707 G_CALLBACK (paned_size_allocate_cb), 708 shell, 0); 709 gtk_widget_set_no_show_all (shell->priv->right_paned, TRUE); 710 { 711 GtkWidget *vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); 712 713 shell->priv->queue_paned = gtk_paned_new (GTK_ORIENTATION_VERTICAL); 714 gtk_paned_pack1 (GTK_PANED (shell->priv->queue_paned), 715 GTK_WIDGET (shell->priv->display_page_tree), 716 FALSE, TRUE); 717 gtk_paned_pack2 (GTK_PANED (shell->priv->queue_paned), 718 shell->priv->queue_sidebar, 719 TRUE, TRUE); 720 gtk_container_child_set (GTK_CONTAINER (shell->priv->queue_paned), 721 GTK_WIDGET (shell->priv->display_page_tree), 722 "resize", FALSE, 723 NULL); 724 725 gtk_box_pack_start (GTK_BOX (vbox2), 726 shell->priv->notebook, 727 TRUE, TRUE, 0); 728 gtk_box_pack_start (GTK_BOX (vbox2), 729 GTK_WIDGET (shell->priv->bottom_container), 730 FALSE, FALSE, 0); 731 732 gtk_paned_pack1 (GTK_PANED (shell->priv->right_paned), 733 vbox2, TRUE, TRUE); 734 gtk_paned_pack2 (GTK_PANED (shell->priv->right_paned), 735 GTK_WIDGET (shell->priv->right_sidebar_container), 736 FALSE, FALSE); 737 gtk_widget_hide (GTK_WIDGET(shell->priv->right_sidebar_container)); 738 739 gtk_box_pack_start (shell->priv->sidebar_container, 740 shell->priv->queue_paned, 741 TRUE, TRUE, 0); 742 gtk_paned_pack1 (GTK_PANED (shell->priv->paned), 743 GTK_WIDGET (shell->priv->sidebar_container), 744 FALSE, TRUE); 745 gtk_paned_pack2 (GTK_PANED (shell->priv->paned), 746 shell->priv->right_paned, 747 TRUE, TRUE); 748 gtk_widget_show (vbox2); 749 } 750 751 g_signal_connect_object (G_OBJECT (shell->priv->queue_paned), 752 "size-allocate", 753 G_CALLBACK (paned_size_allocate_cb), 754 shell, 0); 755 gtk_widget_show (shell->priv->paned); 756 757 shell->priv->main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); 758 gtk_container_set_border_width (GTK_CONTAINER (shell->priv->main_vbox), 0); 759 760 gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), GTK_WIDGET (shell->priv->top_container), FALSE, TRUE, 0); 761 gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), shell->priv->paned, TRUE, TRUE, 0); 762 gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), GTK_WIDGET (shell->priv->statusbar), FALSE, TRUE, 0); 763 gtk_widget_show_all (shell->priv->main_vbox); 764 765 gtk_container_add (GTK_CONTAINER (win), shell->priv->main_vbox); 766 767 rb_profile_end ("constructing widgets"); 768 } 769 770 static void 771 construct_sources (RBShell *shell) 772 { 773 RBDisplayPage *page_group; 774 char *pathname; 775 776 rb_profile_start ("constructing sources"); 777 778 page_group = RB_DISPLAY_PAGE_GROUP_LIBRARY; 779 shell->priv->library_source = RB_LIBRARY_SOURCE (rb_library_source_new (shell)); 780 shell->priv->podcast_source = RB_PODCAST_SOURCE (rb_podcast_main_source_new (shell, shell->priv->podcast_manager)); 781 shell->priv->missing_files_source = rb_missing_files_source_new (shell, shell->priv->library_source); 782 783 shell->priv->import_errors_source = rb_import_errors_source_new (shell, 784 RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR, 785 RHYTHMDB_ENTRY_TYPE_SONG, 786 RHYTHMDB_ENTRY_TYPE_IGNORE); 787 788 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source), page_group); 789 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->podcast_source), page_group); 790 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->missing_files_source), page_group); 791 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->import_errors_source), page_group); 792 793 rb_auto_playlist_source_create_actions (shell); 794 rb_static_playlist_source_create_actions (shell); 795 796 rb_podcast_main_source_add_subsources (RB_PODCAST_MAIN_SOURCE (shell->priv->podcast_source)); 797 798 /* Find the playlist name if none supplied */ 799 if (shell->priv->playlists_file) { 800 pathname = g_strdup (shell->priv->playlists_file); 801 } else { 802 pathname = rb_find_user_data_file ("playlists.xml"); 803 } 804 805 /* Initialize playlist manager */ 806 rb_debug ("shell: creating playlist manager"); 807 shell->priv->playlist_manager = rb_playlist_manager_new (shell, 808 shell->priv->display_page_model, 809 shell->priv->display_page_tree, 810 pathname); 811 812 g_object_set (shell->priv->clipboard_shell, 813 "playlist-manager", shell->priv->playlist_manager, 814 NULL); 815 816 g_signal_connect_object (G_OBJECT (shell->priv->playlist_manager), "playlist_added", 817 G_CALLBACK (rb_shell_playlist_added_cb), shell, 0); 818 g_signal_connect_object (G_OBJECT (shell->priv->playlist_manager), "playlist_created", 819 G_CALLBACK (rb_shell_playlist_created_cb), shell, 0); 820 821 /* Initialize removable media manager */ 822 rb_debug ("shell: creating removable media manager"); 823 shell->priv->removable_media_manager = rb_removable_media_manager_new (shell); 824 825 g_signal_connect_object (G_OBJECT (shell->priv->removable_media_manager), "medium_added", 826 G_CALLBACK (rb_shell_medium_added_cb), shell, 0); 827 828 829 g_free (pathname); 830 831 rb_profile_end ("constructing sources"); 832 } 833 834 static void 835 construct_load_ui (RBShell *shell) 836 { 837 GtkWidget *menubar; 838 GtkWidget *toolbar; 839 GtkToolItem *tool_item; 840 GError *error = NULL; 841 842 rb_debug ("shell: loading ui"); 843 rb_profile_start ("loading ui"); 844 845 gtk_ui_manager_insert_action_group (shell->priv->ui_manager, 846 shell->priv->actiongroup, 0); 847 gtk_ui_manager_add_ui_from_file (shell->priv->ui_manager, 848 rb_file ("rhythmbox-ui.xml"), &error); 849 850 gtk_ui_manager_ensure_update (shell->priv->ui_manager); 851 gtk_window_add_accel_group (GTK_WINDOW (shell->priv->window), 852 gtk_ui_manager_get_accel_group (shell->priv->ui_manager)); 853 menubar = gtk_ui_manager_get_widget (shell->priv->ui_manager, "/MenuBar"); 854 855 gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), menubar, FALSE, FALSE, 0); 856 gtk_box_reorder_child (GTK_BOX (shell->priv->main_vbox), menubar, 0); 857 858 toolbar = gtk_ui_manager_get_widget (shell->priv->ui_manager, "/ToolBar"); 859 gtk_style_context_add_class (gtk_widget_get_style_context (toolbar), 860 GTK_STYLE_CLASS_PRIMARY_TOOLBAR); 861 gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_BOTH); 862 gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), toolbar, FALSE, FALSE, 0); 863 gtk_box_reorder_child (GTK_BOX (shell->priv->main_vbox), toolbar, 1); 864 865 shell->priv->volume_button = gtk_volume_button_new (); 866 g_signal_connect (shell->priv->volume_button, "value-changed", 867 G_CALLBACK (rb_shell_volume_widget_changed_cb), 868 shell); 869 g_signal_connect (shell->priv->player_shell, "notify::volume", 870 G_CALLBACK (rb_shell_player_volume_changed_cb), 871 shell); 872 rb_shell_player_volume_changed_cb (shell->priv->player_shell, NULL, shell); 873 874 tool_item = gtk_tool_item_new (); 875 gtk_tool_item_set_expand (tool_item, TRUE); 876 gtk_container_add (GTK_CONTAINER (tool_item), GTK_WIDGET (shell->priv->header)); 877 gtk_widget_show_all (GTK_WIDGET (tool_item)); 878 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_item, -1); 879 880 tool_item = gtk_tool_item_new (); 881 gtk_container_add (GTK_CONTAINER (tool_item), shell->priv->volume_button); 882 gtk_widget_show_all (GTK_WIDGET (tool_item)); 883 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_item, -1); 884 885 gtk_widget_set_tooltip_text (shell->priv->volume_button, 886 _("Change the music volume")); 887 888 if (error != NULL) { 889 g_warning ("Couldn't merge %s: %s", 890 rb_file ("rhythmbox-ui.xml"), error->message); 891 g_clear_error (&error); 892 } 893 894 rb_profile_end ("loading ui"); 895 } 896 897 static void 898 extension_added_cb (PeasExtensionSet *set, PeasPluginInfo *info, PeasExtension *extension, RBShell *shell) 899 { 900 rb_debug ("activating extension %s", peas_plugin_info_get_name (info)); 901 peas_extension_call (extension, "activate"); 902 } 903 904 static void 905 extension_removed_cb (PeasExtensionSet *set, PeasPluginInfo *info, PeasExtension *extension, RBShell *shell) 906 { 907 rb_debug ("deactivating extension %s", peas_plugin_info_get_name (info)); 908 peas_extension_call (extension, "deactivate"); 909 } 910 911 static void 912 construct_plugins (RBShell *shell) 913 { 914 char *typelib_dir; 915 char *plugindir; 916 char *plugindatadir; 917 char **seen_plugins; 918 GPtrArray *new_plugins = NULL; 919 const GList *plugins; 920 const GList *l; 921 GError *error = NULL; 922 923 if (shell->priv->disable_plugins) { 924 return; 925 } 926 927 rb_profile_start ("loading plugins"); 928 shell->priv->plugin_settings = g_settings_new ("org.gnome.rhythmbox.plugins"); 929 930 shell->priv->plugin_engine = peas_engine_new (); 931 /* need an #ifdef for this? */ 932 peas_engine_enable_loader (shell->priv->plugin_engine, "python"); 933 934 typelib_dir = g_build_filename (LIBDIR, 935 "girepository-1.0", 936 NULL); 937 if (g_irepository_require_private (g_irepository_get_default (), 938 typelib_dir, "MPID", "3.0", 0, &error) == FALSE) { 939 g_clear_error (&error); 940 if (g_irepository_require (g_irepository_get_default (), "MPID", "3.0", 0, &error) == FALSE) { 941 g_warning ("Could not load MPID typelib: %s", error->message); 942 g_clear_error (&error); 943 } 944 } 945 946 if (g_irepository_require_private (g_irepository_get_default (), 947 typelib_dir, "RB", "3.0", 0, &error) == FALSE) { 948 g_clear_error (&error); 949 if (g_irepository_require (g_irepository_get_default (), "RB", "3.0", 0, &error) == FALSE) { 950 g_warning ("Could not load RB typelib: %s", error->message); 951 g_clear_error (&error); 952 } 953 } 954 g_free (typelib_dir); 955 956 if (g_irepository_require (g_irepository_get_default (), "Peas", "1.0", 0, &error) == FALSE) { 957 g_warning ("Could not load Peas typelib: %s", error->message); 958 g_clear_error (&error); 959 } 960 961 if (g_irepository_require (g_irepository_get_default (), "PeasGtk", "1.0", 0, &error) == FALSE) { 962 g_warning ("Could not load PeasGtk typelib: %s", error->message); 963 g_clear_error (&error); 964 } 965 966 plugindir = g_build_filename (rb_user_data_dir (), "plugins", NULL); 967 rb_debug ("plugin search path: %s", plugindir); 968 peas_engine_add_search_path (shell->priv->plugin_engine, 969 plugindir, 970 plugindir); 971 g_free (plugindir); 972 973 plugindir = g_build_filename (LIBDIR, "rhythmbox", "plugins", NULL); 974 plugindatadir = g_build_filename (DATADIR, "rhythmbox", "plugins", NULL); 975 rb_debug ("plugin search path: %s / %s", plugindir, plugindatadir); 976 peas_engine_add_search_path (shell->priv->plugin_engine, 977 plugindir, 978 plugindatadir); 979 g_free (plugindir); 980 g_free (plugindatadir); 981 982 #ifdef USE_UNINSTALLED_DIRS 983 plugindir = g_build_filename (SHARE_UNINSTALLED_BUILDDIR, "..", UNINSTALLED_PLUGINS_LOCATION, NULL); 984 rb_debug ("plugin search path: %s", plugindir); 985 peas_engine_add_search_path (shell->priv->plugin_engine, 986 plugindir, 987 plugindir); 988 g_free (plugindir); 989 #endif 990 991 shell->priv->activatable = peas_extension_set_new (shell->priv->plugin_engine, 992 PEAS_TYPE_ACTIVATABLE, 993 "object", shell, 994 NULL); 995 g_signal_connect (shell->priv->activatable, "extension-added", G_CALLBACK (extension_added_cb), shell); 996 g_signal_connect (shell->priv->activatable, "extension-removed", G_CALLBACK (extension_removed_cb), shell); 997 998 g_settings_bind (shell->priv->plugin_settings, 999 "active-plugins", 1000 shell->priv->plugin_engine, 1001 "loaded-plugins", 1002 G_SETTINGS_BIND_DEFAULT); 1003 1004 seen_plugins = g_settings_get_strv (shell->priv->plugin_settings, "seen-plugins"); 1005 plugins = peas_engine_get_plugin_list (shell->priv->plugin_engine); 1006 for (l = plugins; l != NULL; l = l->next) { 1007 PeasPluginInfo *info = PEAS_PLUGIN_INFO (l->data); 1008 char *kf_name; 1009 char *kf_path; 1010 GKeyFile *keyfile; 1011 1012 /* load builtin plugins, except for the 'rb' utility module, which only 1013 * gets loaded if another plugin needs it. 1014 */ 1015 if (peas_plugin_info_is_builtin (info) && 1016 g_strcmp0 (peas_plugin_info_get_module_name (info), "rb") != 0) { 1017 peas_engine_load_plugin (shell->priv->plugin_engine, info); 1018 continue; 1019 } 1020 1021 /* have we seen this plugin before? */ 1022 if (rb_str_in_strv (peas_plugin_info_get_module_name (info), (const char **)seen_plugins)) { 1023 continue; 1024 } 1025 if (new_plugins == NULL) { 1026 new_plugins = g_ptr_array_new_with_free_func (g_free); 1027 } 1028 g_ptr_array_add (new_plugins, g_strdup (peas_plugin_info_get_module_name (info))); 1029 1030 /* it's a new plugin, see if it wants to be enabled */ 1031 kf_name = g_strdup_printf ("%s.plugin", peas_plugin_info_get_module_name (info)); 1032 kf_path = g_build_filename (peas_plugin_info_get_module_dir (info), kf_name, NULL); 1033 g_free (kf_name); 1034 1035 keyfile = g_key_file_new (); 1036 if (g_key_file_load_from_file (keyfile, kf_path, G_KEY_FILE_NONE, NULL)) { 1037 if (g_key_file_get_boolean (keyfile, "RB", "InitiallyEnabled", NULL)) { 1038 rb_debug ("loading new plugin %s", peas_plugin_info_get_module_name (info)); 1039 peas_engine_load_plugin (shell->priv->plugin_engine, info); 1040 } else { 1041 rb_debug ("new plugin %s not enabled", peas_plugin_info_get_module_name (info)); 1042 } 1043 } else { 1044 rb_debug ("couldn't load plugin file %s", kf_path); 1045 } 1046 g_free (kf_path); 1047 g_key_file_free (keyfile); 1048 } 1049 1050 if (new_plugins != NULL) { 1051 GPtrArray *update; 1052 int i; 1053 1054 update = g_ptr_array_new_with_free_func (g_free); 1055 for (i = 0; i < g_strv_length (seen_plugins); i++) { 1056 g_ptr_array_add (update, g_strdup (seen_plugins[i])); 1057 } 1058 for (i = 0; i < new_plugins->len; i++) { 1059 g_ptr_array_add (update, g_strdup (g_ptr_array_index (new_plugins, i))); 1060 } 1061 1062 g_ptr_array_add (update, NULL); 1063 g_settings_set_strv (shell->priv->plugin_settings, "seen-plugins", (const char * const *)update->pdata); 1064 1065 g_ptr_array_free (new_plugins, TRUE); 1066 g_ptr_array_free (update, TRUE); 1067 } 1068 1069 g_strfreev (seen_plugins); 1070 1071 rb_profile_end ("loading plugins"); 1072 } 1073 1074 static gboolean 1075 _scan_idle (RBShell *shell) 1076 { 1077 gboolean loaded, scanned; 1078 1079 GDK_THREADS_ENTER (); 1080 rb_removable_media_manager_scan (shell->priv->removable_media_manager); 1081 GDK_THREADS_LEAVE (); 1082 1083 if (shell->priv->no_registration == FALSE) { 1084 g_variant_get (g_action_group_get_action_state (G_ACTION_GROUP (shell), "LoadURI"), "(bb)", &loaded, &scanned); 1085 g_action_group_change_action_state (G_ACTION_GROUP (shell), "LoadURI", g_variant_new ("(bb)", loaded, TRUE)); 1086 } 1087 1088 return FALSE; 1089 } 1090 1091 static void 1092 rb_shell_startup (GApplication *app) 1093 { 1094 RBShell *shell = RB_SHELL (app); 1095 GtkAction *gtkaction; 1096 RBEntryView *view; 1097 1098 rb_debug ("Constructing shell"); 1099 rb_profile_start ("constructing shell"); 1100 1101 gtkaction = gtk_action_group_get_action (shell->priv->actiongroup, "ViewSidePane"); 1102 g_settings_bind (shell->priv->settings, "display-page-tree-visible", 1103 gtkaction, "active", 1104 G_SETTINGS_BIND_DEFAULT); 1105 g_settings_bind (shell->priv->settings, "display-page-tree-visible", 1106 shell->priv->sidebar_container, "visible", 1107 G_SETTINGS_BIND_DEFAULT); 1108 1109 gtkaction = gtk_action_group_get_action (shell->priv->actiongroup, "ViewSongPositionSlider"); 1110 g_settings_bind (shell->priv->settings, "show-song-position-slider", 1111 gtkaction, "active", 1112 G_SETTINGS_BIND_DEFAULT); 1113 g_settings_bind (shell->priv->settings, "show-song-position-slider", 1114 shell->priv->header, "show-position-slider", 1115 G_SETTINGS_BIND_DEFAULT); 1116 1117 gtkaction = gtk_action_group_get_action (shell->priv->actiongroup, "ViewAlbumArt"); 1118 g_settings_bind (shell->priv->settings, "show-album-art", 1119 gtkaction, "active", 1120 G_SETTINGS_BIND_DEFAULT); 1121 g_settings_bind (shell->priv->settings, "show-album-art", 1122 shell->priv->header, "show-album-art", 1123 G_SETTINGS_BIND_DEFAULT); 1124 1125 rb_debug ("shell: syncing with settings"); 1126 rb_shell_sync_pane_visibility (shell); 1127 1128 g_signal_connect_object (G_OBJECT (shell->priv->db), "save-error", 1129 G_CALLBACK (rb_shell_db_save_error_cb), shell, 0); 1130 1131 construct_sources (shell); 1132 1133 construct_load_ui (shell); 1134 1135 construct_plugins (shell); 1136 1137 rb_shell_sync_window_state (shell, FALSE); 1138 rb_shell_sync_party_mode (shell); 1139 1140 rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source)); 1141 1142 /* by now we've added the built in sources and any sources from plugins, 1143 * so we can consider the fixed page groups loaded 1144 */ 1145 rb_display_page_group_loaded (RB_DISPLAY_PAGE_GROUP (RB_DISPLAY_PAGE_GROUP_LIBRARY)); 1146 rb_display_page_group_loaded (RB_DISPLAY_PAGE_GROUP (RB_DISPLAY_PAGE_GROUP_STORES)); 1147 1148 rb_missing_plugins_init (GTK_WINDOW (shell->priv->window)); 1149 1150 g_idle_add ((GSourceFunc)_scan_idle, shell); 1151 1152 /* GO GO GO! */ 1153 rb_debug ("loading database"); 1154 rhythmdb_load (shell->priv->db); 1155 1156 rb_debug ("shell: syncing window state"); 1157 rb_shell_sync_paned (shell); 1158 1159 /* set initial visibility */ 1160 rb_shell_set_visibility (shell, TRUE, TRUE); 1161 1162 gdk_notify_startup_complete (); 1163 1164 view = rb_source_get_entry_view (RB_SOURCE (shell->priv->library_source)); 1165 if (view != NULL) { 1166 gtk_widget_grab_focus (GTK_WIDGET (view)); 1167 } 1168 1169 rb_profile_end ("constructing shell"); 1170 1171 /* window-based usage counting doesn't work for us, just hold the app until 1172 * we're asked to quit. 1173 */ 1174 g_application_hold (app); 1175 1176 (* G_APPLICATION_CLASS (rb_shell_parent_class)->startup) (app); 1177 } 1178 1179 static gboolean 1180 rb_shell_local_command_line (GApplication *app, gchar ***args, int *exit_status) 1181 { 1182 RBShell *shell; 1183 GError *error = NULL; 1184 gboolean scanned; 1185 gboolean loaded; 1186 GPtrArray *files; 1187 int n_files; 1188 int i; 1189 1190 n_files = g_strv_length (*args) - 1; 1191 1192 shell = RB_SHELL (app); 1193 if (shell->priv->no_registration) { 1194 if (n_files > 0) { 1195 g_warning ("Unable to open files on the commandline with --no-registration"); 1196 } 1197 rb_shell_startup (app); 1198 return TRUE; 1199 } 1200 1201 if (!g_application_register (app, NULL, &error)) { 1202 g_critical ("%s", error->message); 1203 g_error_free (error); 1204 *exit_status = 1; 1205 return TRUE; 1206 } 1207 1208 if (n_files <= 0) { 1209 g_application_activate (app); 1210 *exit_status = 0; 1211 return TRUE; 1212 } 1213 1214 files = g_ptr_array_new_with_free_func (g_object_unref); 1215 for (i = 0; i < n_files; i++) { 1216 g_ptr_array_add (files, g_file_new_for_commandline_arg ((*args)[i + 1])); 1217 } 1218 1219 g_variant_get (g_action_group_get_action_state (G_ACTION_GROUP (app), "LoadURI"), "(bb)", &loaded, &scanned); 1220 if (loaded) { 1221 rb_debug ("opening files immediately"); 1222 g_application_open (app, (GFile **)files->pdata, files->len, ""); 1223 g_ptr_array_free (files, TRUE); 1224 } else { 1225 rb_debug ("opening files once db is loaded"); 1226 g_signal_connect (app, "action-state-changed::LoadURI", G_CALLBACK (load_state_changed_cb), files); 1227 } 1228 1229 return TRUE; 1230 } 1231 static void 1232 rb_shell_class_init (RBShellClass *klass) 1233 { 1234 GObjectClass *object_class = G_OBJECT_CLASS (klass); 1235 GApplicationClass *app_class = G_APPLICATION_CLASS (klass); 1236 1237 object_class->set_property = rb_shell_set_property; 1238 object_class->get_property = rb_shell_get_property; 1239 object_class->finalize = rb_shell_finalize; 1240 object_class->constructed = rb_shell_constructed; 1241 1242 app_class->activate = rb_shell_activate; 1243 app_class->open = rb_shell_open; 1244 app_class->local_command_line = rb_shell_local_command_line; 1245 app_class->startup = rb_shell_startup; 1246 1247 klass->visibility_changing = rb_shell_visibility_changing; 1248 1249 /** 1250 * RBShell:no-registration: 1251 * 1252 * If %TRUE, disable single-instance features. 1253 */ 1254 g_object_class_install_property (object_class, 1255 PROP_NO_REGISTRATION, 1256 g_param_spec_boolean ("no-registration", 1257 "no-registration", 1258 "Whether or not to register", 1259 FALSE, 1260 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); 1261 /** 1262 * RBShell:no-update: 1263 * 1264 * If %TRUE, don't update the database. 1265 */ 1266 g_object_class_install_property (object_class, 1267 PROP_NO_UPDATE, 1268 g_param_spec_boolean ("no-update", 1269 "no-update", 1270 "Whether or not to update the library", 1271 FALSE, 1272 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); 1273 /** 1274 * RBShell:dry-run: 1275 * 1276 * If TRUE, don't write back file metadata changes. 1277 */ 1278 g_object_class_install_property (object_class, 1279 PROP_DRY_RUN, 1280 g_param_spec_boolean ("dry-run", 1281 "dry-run", 1282 "Whether or not this is a dry run", 1283 FALSE, 1284 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); 1285 /** 1286 * RBShell:rhythmdb-file: 1287 * 1288 * The path to the rhythmdb file 1289 */ 1290 g_object_class_install_property (object_class, 1291 PROP_RHYTHMDB_FILE, 1292 g_param_spec_string ("rhythmdb-file", 1293 "rhythmdb-file", 1294 "The RhythmDB file to use", 1295 "rhythmdb.xml", 1296 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); 1297 1298 /** 1299 * RBShell:playlists-file: 1300 * 1301 * The path to the playlist file 1302 */ 1303 g_object_class_install_property (object_class, 1304 PROP_PLAYLISTS_FILE, 1305 g_param_spec_string ("playlists-file", 1306 "playlists-file", 1307 "The playlists file to use", 1308 "playlists.xml", 1309 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); 1310 /** 1311 * RBShell:selected-page: 1312 * 1313 * The currently selected display page 1314 */ 1315 g_object_class_install_property (object_class, 1316 PROP_SELECTED_PAGE, 1317 g_param_spec_object ("selected-page", 1318 "selected-page", 1319 "Display page which is currently selected", 1320 RB_TYPE_DISPLAY_PAGE, 1321 G_PARAM_READABLE)); 1322 /** 1323 * RBShell:db: 1324 * 1325 * The #RhythmDB instance 1326 */ 1327 g_object_class_install_property (object_class, 1328 PROP_DB, 1329 g_param_spec_object ("db", 1330 "RhythmDB", 1331 "RhythmDB object", 1332 RHYTHMDB_TYPE, 1333 G_PARAM_READABLE)); 1334 /** 1335 * RBShell:ui-manager: 1336 * 1337 * The #GtkUIManager instance 1338 */ 1339 g_object_class_install_property (object_class, 1340 PROP_UI_MANAGER, 1341 g_param_spec_object ("ui-manager", 1342 "GtkUIManager", 1343 "GtkUIManager object", 1344 GTK_TYPE_UI_MANAGER, 1345 G_PARAM_READABLE)); 1346 /** 1347 * RBShell:clipboard: 1348 * 1349 * The #RBShellClipboard instance 1350 */ 1351 g_object_class_install_property (object_class, 1352 PROP_CLIPBOARD, 1353 g_param_spec_object ("clipboard", 1354 "RBShellClipboard", 1355 "RBShellClipboard object", 1356 RB_TYPE_SHELL_CLIPBOARD, 1357 G_PARAM_READABLE)); 1358 /** 1359 * RBShell:playlist-manager: 1360 * 1361 * The #RBPlaylistManager instance 1362 */ 1363 g_object_class_install_property (object_class, 1364 PROP_PLAYLIST_MANAGER, 1365 g_param_spec_object ("playlist-manager", 1366 "RBPlaylistManager", 1367 "RBPlaylistManager object", 1368 RB_TYPE_PLAYLIST_MANAGER, 1369 G_PARAM_READABLE)); 1370 /** 1371 * RBShell:shell-player: 1372 * 1373 * The #RBShellPlayer instance 1374 */ 1375 g_object_class_install_property (object_class, 1376 PROP_SHELL_PLAYER, 1377 g_param_spec_object ("shell-player", 1378 "RBShellPlayer", 1379 "RBShellPlayer object", 1380 RB_TYPE_SHELL_PLAYER, 1381 G_PARAM_READABLE)); 1382 /** 1383 * RBShell:removable-media-manager: 1384 * 1385 * The #RBRemovableMediaManager instance 1386 */ 1387 g_object_class_install_property (object_class, 1388 PROP_REMOVABLE_MEDIA_MANAGER, 1389 g_param_spec_object ("removable-media-manager", 1390 "RBRemovableMediaManager", 1391 "RBRemovableMediaManager object", 1392 RB_TYPE_REMOVABLE_MEDIA_MANAGER, 1393 G_PARAM_READABLE)); 1394 /** 1395 * RBShell:window: 1396 * 1397 * The main Rhythmbox window. 1398 */ 1399 g_object_class_install_property (object_class, 1400 PROP_WINDOW, 1401 g_param_spec_object ("window", 1402 "GtkWindow", 1403 "GtkWindow object", 1404 GTK_TYPE_WINDOW, 1405 G_PARAM_READABLE)); 1406 /** 1407 * RBShell:prefs: 1408 * 1409 * The #RBShellPreferences instance 1410 */ 1411 g_object_class_install_property (object_class, 1412 PROP_PREFS, 1413 g_param_spec_object ("prefs", 1414 "RBShellPreferences", 1415 "RBShellPreferences object", 1416 RB_TYPE_SHELL_PREFERENCES, 1417 G_PARAM_READABLE)); 1418 /** 1419 * RBShell:queue-source: 1420 * 1421 * The play queue source 1422 */ 1423 g_object_class_install_property (object_class, 1424 PROP_QUEUE_SOURCE, 1425 g_param_spec_object ("queue-source", 1426 "queue-source", 1427 "Queue source", 1428 RB_TYPE_PLAY_QUEUE_SOURCE, 1429 G_PARAM_READABLE)); 1430 /** 1431 * RBShell:library-source: 1432 * 1433 * The library source 1434 */ 1435 g_object_class_install_property (object_class, 1436 PROP_LIBRARY_SOURCE, 1437 g_param_spec_object ("library-source", 1438 "library-source", 1439 "Library source", 1440 RB_TYPE_LIBRARY_SOURCE, 1441 G_PARAM_READABLE)); 1442 /** 1443 * RBShell:display-page-model: 1444 * 1445 * The model underlying the display page tree 1446 */ 1447 g_object_class_install_property (object_class, 1448 PROP_DISPLAY_PAGE_MODEL, 1449 g_param_spec_object ("display-page-model", 1450 "display-page-model", 1451 "RBDisplayPageModel", 1452 RB_TYPE_DISPLAY_PAGE_MODEL, 1453 G_PARAM_READABLE)); 1454 1455 /** 1456 * RBShell:display-page-tree: 1457 * 1458 * The #RBDisplayPageTree instance 1459 */ 1460 g_object_class_install_property (object_class, 1461 PROP_DISPLAY_PAGE_TREE, 1462 g_param_spec_object ("display-page-tree", 1463 "display-page-tree", 1464 "RBDisplayPageTree", 1465 RB_TYPE_DISPLAY_PAGE_TREE, 1466 G_PARAM_READABLE)); 1467 1468 /** 1469 * RBShell:visibility: 1470 * 1471 * Whether the main window is currently visible. 1472 */ 1473 g_object_class_install_property (object_class, 1474 PROP_VISIBILITY, 1475 g_param_spec_boolean ("visibility", 1476 "visibility", 1477 "Current window visibility", 1478 TRUE, 1479 G_PARAM_READWRITE)); 1480 /** 1481 * RBShell:track-transfer-queue: 1482 * 1483 * The #RBTrackTransferQueue instance 1484 */ 1485 g_object_class_install_property (object_class, 1486 PROP_TRACK_TRANSFER_QUEUE, 1487 g_param_spec_object ("track-transfer-queue", 1488 "RBTrackTransferQueue", 1489 "RBTrackTransferQueue object", 1490 RB_TYPE_TRACK_TRANSFER_QUEUE, 1491 G_PARAM_READABLE)); 1492 /** 1493 * RBShell:autostarted: 1494 * 1495 * Whether Rhythmbox was automatically started by the session manager 1496 */ 1497 g_object_class_install_property (object_class, 1498 PROP_AUTOSTARTED, 1499 g_param_spec_boolean ("autostarted", 1500 "autostarted", 1501 "TRUE if autostarted", 1502 FALSE, 1503 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); 1504 /** 1505 * RBShell:disable-plugins: 1506 * 1507 * If %TRUE, disable plugins 1508 */ 1509 g_object_class_install_property (object_class, 1510 PROP_DISABLE_PLUGINS, 1511 g_param_spec_boolean ("disable-plugins", 1512 "disable-plugins", 1513 "Whether or not to disable plugins", 1514 FALSE, 1515 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); 1516 1517 /** 1518 * RBShell::visibility-changed: 1519 * @shell: the #RBShell 1520 * @visibile: new visibility 1521 * 1522 * Emitted after the visibility of the main Rhythmbox window has 1523 * changed. 1524 */ 1525 rb_shell_signals[VISIBILITY_CHANGED] = 1526 g_signal_new ("visibility_changed", 1527 G_OBJECT_CLASS_TYPE (object_class), 1528 G_SIGNAL_RUN_LAST, 1529 G_STRUCT_OFFSET (RBShellClass, visibility_changed), 1530 NULL, NULL, 1531 g_cclosure_marshal_VOID__BOOLEAN, 1532 G_TYPE_NONE, 1533 1, 1534 G_TYPE_BOOLEAN); 1535 /** 1536 * RBShell::visibility-changing: 1537 * @shell: the #RBShell 1538 * @initial: if %TRUE, this is the initial visibility for the window 1539 * @visible: new shell visibility 1540 * 1541 * Emitted before the visibility of the main window changes. The return 1542 * value overrides the visibility setting. If multiple signal handlers 1543 * are attached, the last one wins. 1544 */ 1545 rb_shell_signals[VISIBILITY_CHANGING] = 1546 g_signal_new ("visibility_changing", 1547 G_OBJECT_CLASS_TYPE (object_class), 1548 G_SIGNAL_RUN_LAST, 1549 G_STRUCT_OFFSET (RBShellClass, visibility_changing), 1550 NULL, NULL, 1551 rb_marshal_BOOLEAN__BOOLEAN_BOOLEAN, 1552 G_TYPE_BOOLEAN, 1553 2, 1554 G_TYPE_BOOLEAN, 1555 G_TYPE_BOOLEAN); 1556 1557 /** 1558 * RBShell::create-song-info: 1559 * @shell: the #RBShell 1560 * @song_info: the new #RBSongInfo window 1561 * @multi: if %TRUE, the song info window is for multiple entries 1562 * 1563 * Emitted when creating a new #RBSongInfo window. Signal handlers can 1564 * add pages to the song info window notebook to display additional 1565 * information. 1566 */ 1567 rb_shell_signals[CREATE_SONG_INFO] = 1568 g_signal_new ("create_song_info", 1569 G_OBJECT_CLASS_TYPE (object_class), 1570 G_SIGNAL_RUN_LAST, 1571 G_STRUCT_OFFSET (RBShellClass, create_song_info), 1572 NULL, NULL, 1573 rb_marshal_VOID__OBJECT_BOOLEAN, 1574 G_TYPE_NONE, 1575 2, 1576 RB_TYPE_SONG_INFO, G_TYPE_BOOLEAN); 1577 /** 1578 * RBShell::notify-playing-entry: 1579 * @shell: the #RBShell 1580 * 1581 * Emitted when a notification should be displayed showing the current 1582 * playing entry. 1583 */ 1584 rb_shell_signals[NOTIFY_PLAYING_ENTRY] = 1585 g_signal_new ("notify-playing-entry", 1586 G_OBJECT_CLASS_TYPE (object_class), 1587 G_SIGNAL_RUN_LAST, 1588 0, 1589 NULL, NULL, 1590 g_cclosure_marshal_VOID__BOOLEAN, 1591 G_TYPE_NONE, 1592 1, 1593 G_TYPE_BOOLEAN); 1594 /** 1595 * RBShell::notify-custom: 1596 * @shell: the #RBShell 1597 * @timeout: length of time (in seconds) to display the notification 1598 * @primary: main notification text 1599 * @secondary: secondary notification text 1600 * @image_uri: URI for an image to include in the notification (optional) 1601 * @requested: if %TRUE, the notification was triggered by an explicit user action 1602 * 1603 * Emitted when a custom notification should be displayed to the user. 1604 */ 1605 rb_shell_signals[NOTIFY_CUSTOM] = 1606 g_signal_new ("notify-custom", 1607 G_OBJECT_CLASS_TYPE (object_class), 1608 G_SIGNAL_RUN_LAST, 1609 0, 1610 NULL, NULL, 1611 rb_marshal_VOID__UINT_STRING_STRING_STRING_BOOLEAN, 1612 G_TYPE_NONE, 1613 5, 1614 G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN); 1615 g_type_class_add_private (klass, sizeof (RBShellPrivate)); 1616 } 1617 1618 static void 1619 rb_shell_init (RBShell *shell) 1620 { 1621 shell->priv = G_TYPE_INSTANCE_GET_PRIVATE (shell, RB_TYPE_SHELL, RBShellPrivate); 1622 1623 rb_user_data_dir (); 1624 rb_refstring_system_init (); 1625 1626 #ifdef USE_UNINSTALLED_DIRS 1627 rb_file_helpers_init (TRUE); 1628 #else 1629 rb_file_helpers_init (FALSE); 1630 #endif 1631 rb_stock_icons_init (); 1632 1633 rb_shell_session_init (shell); 1634 1635 g_setenv ("PULSE_PROP_media.role", "music", TRUE); 1636 } 1637 1638 static void 1639 rb_shell_set_property (GObject *object, 1640 guint prop_id, 1641 const GValue *value, 1642 GParamSpec *pspec) 1643 { 1644 RBShell *shell = RB_SHELL (object); 1645 1646 switch (prop_id) 1647 { 1648 case PROP_NO_REGISTRATION: 1649 shell->priv->no_registration = g_value_get_boolean (value); 1650 break; 1651 case PROP_NO_UPDATE: 1652 shell->priv->no_update = g_value_get_boolean (value); 1653 break; 1654 case PROP_DRY_RUN: 1655 shell->priv->dry_run = g_value_get_boolean (value); 1656 if (shell->priv->dry_run) 1657 shell->priv->no_registration = TRUE; 1658 break; 1659 case PROP_RHYTHMDB_FILE: 1660 g_free (shell->priv->rhythmdb_file); 1661 shell->priv->rhythmdb_file = g_value_dup_string (value); 1662 break; 1663 case PROP_PLAYLISTS_FILE: 1664 g_free (shell->priv->playlists_file); 1665 shell->priv->playlists_file = g_value_dup_string (value); 1666 break; 1667 case PROP_VISIBILITY: 1668 rb_shell_set_visibility (shell, FALSE, g_value_get_boolean (value)); 1669 break; 1670 case PROP_AUTOSTARTED: 1671 shell->priv->autostarted = g_value_get_boolean (value); 1672 break; 1673 case PROP_DISABLE_PLUGINS: 1674 shell->priv->disable_plugins = g_value_get_boolean (value); 1675 break; 1676 default: 1677 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 1678 break; 1679 } 1680 } 1681 1682 static void 1683 rb_shell_get_property (GObject *object, 1684 guint prop_id, 1685 GValue *value, 1686 GParamSpec *pspec) 1687 { 1688 RBShell *shell = RB_SHELL (object); 1689 1690 switch (prop_id) 1691 { 1692 case PROP_NO_REGISTRATION: 1693 g_value_set_boolean (value, shell->priv->no_registration); 1694 break; 1695 case PROP_NO_UPDATE: 1696 g_value_set_boolean (value, shell->priv->no_update); 1697 break; 1698 case PROP_DRY_RUN: 1699 g_value_set_boolean (value, shell->priv->dry_run); 1700 break; 1701 case PROP_RHYTHMDB_FILE: 1702 g_value_set_string (value, shell->priv->rhythmdb_file); 1703 break; 1704 case PROP_PLAYLISTS_FILE: 1705 g_value_set_string (value, shell->priv->playlists_file); 1706 break; 1707 case PROP_DB: 1708 g_value_set_object (value, shell->priv->db); 1709 break; 1710 case PROP_UI_MANAGER: 1711 g_value_set_object (value, shell->priv->ui_manager); 1712 break; 1713 case PROP_CLIPBOARD: 1714 g_value_set_object (value, shell->priv->clipboard_shell); 1715 break; 1716 case PROP_PLAYLIST_MANAGER: 1717 g_value_set_object (value, shell->priv->playlist_manager); 1718 break; 1719 case PROP_SHELL_PLAYER: 1720 g_value_set_object (value, shell->priv->player_shell); 1721 break; 1722 case PROP_REMOVABLE_MEDIA_MANAGER: 1723 g_value_set_object (value, shell->priv->removable_media_manager); 1724 break; 1725 case PROP_SELECTED_PAGE: 1726 g_value_set_object (value, shell->priv->selected_page); 1727 break; 1728 case PROP_WINDOW: 1729 g_value_set_object (value, shell->priv->window); 1730 break; 1731 case PROP_PREFS: 1732 /* create the preferences window the first time we need it */ 1733 if (shell->priv->prefs == NULL) { 1734 GtkWidget *content; 1735 1736 shell->priv->prefs = rb_shell_preferences_new (shell->priv->sources); 1737 1738 gtk_window_set_transient_for (GTK_WINDOW (shell->priv->prefs), 1739 GTK_WINDOW (shell->priv->window)); 1740 content = gtk_dialog_get_content_area (GTK_DIALOG (shell->priv->prefs)); 1741 gtk_widget_show_all (content); 1742 } 1743 g_value_set_object (value, shell->priv->prefs); 1744 break; 1745 case PROP_QUEUE_SOURCE: 1746 g_value_set_object (value, shell->priv->queue_source); 1747 break; 1748 case PROP_LIBRARY_SOURCE: 1749 g_value_set_object (value, shell->priv->library_source); 1750 break; 1751 case PROP_DISPLAY_PAGE_MODEL: 1752 g_value_set_object (value, shell->priv->display_page_model); 1753 break; 1754 case PROP_DISPLAY_PAGE_TREE: 1755 g_value_set_object (value, shell->priv->display_page_tree); 1756 break; 1757 case PROP_VISIBILITY: 1758 g_value_set_boolean (value, rb_shell_get_visibility (shell)); 1759 break; 1760 case PROP_TRACK_TRANSFER_QUEUE: 1761 g_value_set_object (value, shell->priv->track_transfer_queue); 1762 break; 1763 case PROP_AUTOSTARTED: 1764 g_value_set_boolean (value, shell->priv->autostarted); 1765 break; 1766 case PROP_DISABLE_PLUGINS: 1767 g_value_set_boolean (value, shell->priv->disable_plugins); 1768 break; 1769 default: 1770 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 1771 break; 1772 } 1773 } 1774 1775 static gboolean 1776 rb_shell_sync_state (RBShell *shell) 1777 { 1778 if (shell->priv->dry_run) { 1779 rb_debug ("in dry-run mode, not syncing state"); 1780 return FALSE; 1781 } 1782 1783 if (!shell->priv->load_complete) { 1784 rb_debug ("load incomplete, not syncing state"); 1785 return FALSE; 1786 } 1787 1788 rb_debug ("saving playlists"); 1789 rb_playlist_manager_save_playlists (shell->priv->playlist_manager, 1790 TRUE); 1791 1792 rb_debug ("saving db"); 1793 rhythmdb_save (shell->priv->db); 1794 return FALSE; 1795 } 1796 1797 static gboolean 1798 idle_save_playlist_manager (RBShell *shell) 1799 { 1800 GDK_THREADS_ENTER (); 1801 rb_playlist_manager_save_playlists (shell->priv->playlist_manager, 1802 FALSE); 1803 GDK_THREADS_LEAVE (); 1804 1805 return TRUE; 1806 } 1807 1808 static void 1809 rb_shell_shutdown (RBShell *shell) 1810 { 1811 GdkDisplay *display; 1812 1813 if (shell->priv->shutting_down) 1814 return; 1815 shell->priv->shutting_down = TRUE; 1816 1817 /* Hide the main window and tray icon as soon as possible */ 1818 display = gtk_widget_get_display (shell->priv->window); 1819 gtk_widget_hide (shell->priv->window); 1820 gdk_display_sync (display); 1821 1822 if (shell->priv->plugin_engine != NULL) { 1823 g_object_unref (shell->priv->plugin_engine); 1824 shell->priv->plugin_engine = NULL; 1825 } 1826 if (shell->priv->activatable != NULL) { 1827 g_object_unref (shell->priv->activatable); 1828 shell->priv->activatable = NULL; 1829 } 1830 if (shell->priv->plugin_settings != NULL) { 1831 g_object_unref (shell->priv->plugin_settings); 1832 shell->priv->plugin_settings = NULL; 1833 } 1834 } 1835 1836 static void 1837 rb_shell_finalize (GObject *object) 1838 { 1839 RBShell *shell = RB_SHELL (object); 1840 1841 rb_debug ("Finalizing shell"); 1842 1843 rb_shell_player_stop (shell->priv->player_shell); 1844 1845 if (shell->priv->settings != NULL) { 1846 rb_settings_delayed_sync (shell->priv->settings, NULL, NULL, NULL); 1847 g_object_unref (shell->priv->settings); 1848 } 1849 1850 g_free (shell->priv->cached_title); 1851 1852 if (shell->priv->save_playlist_id > 0) { 1853 g_source_remove (shell->priv->save_playlist_id); 1854 shell->priv->save_playlist_id = 0; 1855 } 1856 1857 if (shell->priv->queue_sidebar != NULL) { 1858 g_object_unref (shell->priv->queue_sidebar); 1859 } 1860 1861 if (shell->priv->playlist_manager != NULL) { 1862 rb_debug ("shutting down playlist manager"); 1863 rb_playlist_manager_shutdown (shell->priv->playlist_manager); 1864 1865 rb_debug ("unreffing playlist manager"); 1866 g_object_unref (shell->priv->playlist_manager); 1867 } 1868 1869 if (shell->priv->removable_media_manager != NULL) { 1870 rb_debug ("unreffing removable media manager"); 1871 g_object_unref (shell->priv->removable_media_manager); 1872 g_object_unref (shell->priv->track_transfer_queue); 1873 } 1874 1875 if (shell->priv->podcast_manager != NULL) { 1876 rb_debug ("unreffing podcast manager"); 1877 g_object_unref (shell->priv->podcast_manager); 1878 } 1879 1880 if (shell->priv->clipboard_shell != NULL) { 1881 rb_debug ("unreffing clipboard shell"); 1882 g_object_unref (shell->priv->clipboard_shell); 1883 } 1884 1885 if (shell->priv->prefs != NULL) { 1886 rb_debug ("destroying prefs"); 1887 gtk_widget_destroy (shell->priv->prefs); 1888 } 1889 1890 g_free (shell->priv->rhythmdb_file); 1891 1892 g_free (shell->priv->playlists_file); 1893 1894 rb_debug ("destroying window"); 1895 gtk_widget_destroy (shell->priv->window); 1896 1897 g_list_free (shell->priv->sources); 1898 shell->priv->sources = NULL; 1899 1900 if (shell->priv->sources_hash != NULL) { 1901 g_hash_table_destroy (shell->priv->sources_hash); 1902 } 1903 1904 if (shell->priv->db != NULL) { 1905 rb_debug ("shutting down DB"); 1906 rhythmdb_shutdown (shell->priv->db); 1907 1908 rb_debug ("unreffing DB"); 1909 g_object_unref (shell->priv->db); 1910 } 1911 if (shell->priv->art_store != NULL) { 1912 g_object_unref (shell->priv->art_store); 1913 shell->priv->art_store = NULL; 1914 } 1915 1916 rb_file_helpers_shutdown (); 1917 rb_stock_icons_shutdown (); 1918 rb_refstring_system_shutdown (); 1919 1920 G_OBJECT_CLASS (rb_shell_parent_class)->finalize (object); 1921 1922 rb_debug ("shell shutdown complete"); 1923 } 1924 1925 /** 1926 * rb_shell_new: 1927 * @autostarted: %TRUE if autostarted by the session manager 1928 * @argc: a pointer to the number of command line arguments 1929 * @argv: a pointer to the array of command line arguments 1930 * 1931 * Creates the Rhythmbox shell. This is effectively a singleton, so it doesn't 1932 * make sense to call this from anywhere other than main.c. 1933 * 1934 * Return value: the #RBShell instance 1935 */ 1936 RBShell * 1937 rb_shell_new (gboolean autostarted, int *argc, char ***argv) 1938 { 1939 GOptionContext *context; 1940 gboolean debug = FALSE; 1941 char *debug_match = NULL; 1942 gboolean no_update = FALSE; 1943 gboolean no_registration = FALSE; 1944 gboolean dry_run = FALSE; 1945 gboolean disable_plugins = FALSE; 1946 char *rhythmdb_file = NULL; 1947 char *playlists_file = NULL; 1948 GError *error = NULL; 1949 1950 const GOptionEntry options [] = { 1951 { "debug", 'd', 0, G_OPTION_ARG_NONE, &debug, N_("Enable debug output"), NULL }, 1952 { "debug-match", 'D', 0, G_OPTION_ARG_STRING, &debug_match, N_("Enable debug output matching a specified string"), NULL }, 1953 { "no-update", 0, 0, G_OPTION_ARG_NONE, &no_update, N_("Do not update the library with file changes"), NULL }, 1954 { "no-registration", 'n', 0, G_OPTION_ARG_NONE, &no_registration, N_("Do not register the shell"), NULL }, 1955 { "dry-run", 0, 0, G_OPTION_ARG_NONE, &dry_run, N_("Don't save any data permanently (implies --no-registration)"), NULL }, 1956 { "disable-plugins", 0, 0, G_OPTION_ARG_NONE, &disable_plugins, N_("Disable loading of plugins"), NULL }, 1957 { "rhythmdb-file", 0, 0, G_OPTION_ARG_STRING, &rhythmdb_file, N_("Path for database file to use"), NULL }, 1958 { "playlists-file", 0, 0, G_OPTION_ARG_STRING, &playlists_file, N_("Path for playlists file to use"), NULL }, 1959 { NULL } 1960 }; 1961 1962 context = g_option_context_new (NULL); 1963 g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE); 1964 g_option_context_add_group (context, gst_init_get_option_group ()); 1965 g_option_context_add_group (context, egg_sm_client_get_option_group ()); 1966 g_option_context_add_group (context, gtk_get_option_group (TRUE)); 1967 1968 if (g_option_context_parse (context, argc, argv, &error) == FALSE) { 1969 g_print (_("%s\nRun '%s --help' to see a full list of available command line options.\n"), 1970 error->message, (*argv)[0]); 1971 g_error_free (error); 1972 g_option_context_free (context); 1973 exit (1); 1974 } 1975 g_option_context_free (context); 1976 1977 if (!debug && debug_match) 1978 rb_debug_init_match (debug_match); 1979 else 1980 rb_debug_init (debug); 1981 1982 return g_object_new (RB_TYPE_SHELL, 1983 "application-id", "org.gnome.Rhythmbox3", 1984 "flags", G_APPLICATION_HANDLES_OPEN, 1985 "autostarted", autostarted, 1986 "no-registration", no_registration, 1987 "no-update", no_update, 1988 "dry-run", dry_run, 1989 "rhythmdb-file", rhythmdb_file, 1990 "playlists-file", playlists_file, 1991 "disable-plugins", disable_plugins, 1992 NULL); 1993 } 1994 1995 static void 1996 load_uri_action_cb (GSimpleAction *action, GVariant *parameters, RBShell *shell) 1997 { 1998 const char *uri; 1999 gboolean play; 2000 2001 g_variant_get (parameters, "(&sb)", &uri, &play); 2002 2003 rb_shell_load_uri (shell, uri, play, NULL); 2004 } 2005 2006 static void 2007 activate_source_action_cb (GSimpleAction *action, GVariant *parameters, RBShell *shell) 2008 { 2009 const char *source; 2010 guint play; 2011 2012 g_variant_get (parameters, "(&su)", &source, &play); 2013 rb_shell_activate_source_by_uri (shell, source, play, NULL); 2014 } 2015 2016 static void 2017 quit_action_cb (GSimpleAction *action, GVariant *parameters, RBShell *shell) 2018 { 2019 rb_shell_quit (shell, NULL); 2020 } 2021 2022 static void 2023 rb_shell_constructed (GObject *object) 2024 { 2025 RBShell *shell; 2026 GSimpleAction *action; 2027 2028 gtk_init (NULL, NULL); 2029 2030 RB_CHAIN_GOBJECT_METHOD (rb_shell_parent_class, constructed, object); 2031 2032 shell = RB_SHELL (object); 2033 2034 /* create application actions */ 2035 action = g_simple_action_new_stateful ("LoadURI", G_VARIANT_TYPE ("(sb)"), g_variant_new ("(bb)", FALSE, FALSE)); 2036 g_signal_connect_object (action, "activate", G_CALLBACK (load_uri_action_cb), shell, 0); 2037 g_action_map_add_action (G_ACTION_MAP (shell), G_ACTION (action)); 2038 g_object_unref (action); 2039 2040 action = g_simple_action_new ("ActivateSource", G_VARIANT_TYPE ("(su)")); 2041 g_signal_connect_object (action, "activate", G_CALLBACK (activate_source_action_cb), shell, 0); 2042 g_action_map_add_action (G_ACTION_MAP (shell), G_ACTION (action)); 2043 g_object_unref (action); 2044 2045 action = g_simple_action_new ("Quit", NULL); 2046 g_signal_connect_object (action, "activate", G_CALLBACK (quit_action_cb), shell, 0); 2047 g_action_map_add_action (G_ACTION_MAP (shell), G_ACTION (action)); 2048 g_object_unref (action); 2049 2050 /* construct enough of the rest of it to display the window if required */ 2051 2052 shell->priv->settings = g_settings_new ("org.gnome.rhythmbox"); 2053 2054 shell->priv->actiongroup = gtk_action_group_new ("MainActions"); 2055 gtk_action_group_set_translation_domain (shell->priv->actiongroup, 2056 GETTEXT_PACKAGE); 2057 gtk_action_group_add_actions (shell->priv->actiongroup, 2058 rb_shell_actions, 2059 rb_shell_n_actions, shell); 2060 gtk_action_group_add_toggle_actions (shell->priv->actiongroup, 2061 rb_shell_toggle_entries, 2062 rb_shell_n_toggle_entries, 2063 shell); 2064 2065 /* Translators: this is the short label for the 'add music' action */ 2066 gtk_action_set_short_label (gtk_action_group_get_action (shell->priv->actiongroup, "MusicAdd"), C_("Library", "Import")); 2067 /* Translators: this is the short label for the 'view all tracks' action */ 2068 gtk_action_set_short_label (gtk_action_group_get_action (shell->priv->actiongroup, "ViewAll"), _("Show All")); 2069 2070 construct_db (shell); 2071 2072 construct_widgets (shell); 2073 } 2074 2075 static gboolean 2076 rb_shell_window_state_cb (GtkWidget *widget, 2077 GdkEventWindowState *event, 2078 RBShell *shell) 2079 { 2080 shell->priv->iconified = ((event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) != 0); 2081 2082 if (event->changed_mask & (GDK_WINDOW_STATE_WITHDRAWN | GDK_WINDOW_STATE_ICONIFIED)) { 2083 g_signal_emit (shell, rb_shell_signals[VISIBILITY_CHANGED], 0, 2084 rb_shell_get_visibility (shell)); 2085 } 2086 2087 /* don't save maximized state when is hidden */ 2088 if (!gtk_widget_get_visible (shell->priv->window)) 2089 return FALSE; 2090 2091 if (event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) { 2092 gboolean maximised = ((event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0); 2093 2094 if (maximised != g_settings_get_boolean (shell->priv->settings, "maximized")) { 2095 g_settings_set_boolean (shell->priv->settings, 2096 "maximized", 2097 maximised); 2098 } 2099 rb_shell_sync_window_state (shell, TRUE); 2100 rb_shell_sync_paned (shell); 2101 } 2102 2103 return FALSE; 2104 } 2105 2106 static gboolean 2107 rb_shell_visibility_changing (RBShell *shell, gboolean initial, gboolean visible) 2108 { 2109 return visible; 2110 } 2111 2112 static gboolean 2113 rb_shell_get_visibility (RBShell *shell) 2114 { 2115 GdkWindowState state; 2116 2117 if (!gtk_widget_get_realized (shell->priv->window)) 2118 return FALSE; 2119 if (shell->priv->iconified) 2120 return FALSE; 2121 2122 state = gdk_window_get_state (gtk_widget_get_window (GTK_WIDGET (shell->priv->window))); 2123 if (state & (GDK_WINDOW_STATE_WITHDRAWN | GDK_WINDOW_STATE_ICONIFIED)) 2124 return FALSE; 2125 2126 return TRUE; 2127 } 2128 2129 static void 2130 rb_shell_set_visibility (RBShell *shell, 2131 gboolean initial, 2132 gboolean visible) 2133 { 2134 gboolean really_visible; 2135 2136 rb_profile_start ("changing shell visibility"); 2137 2138 if (visible == rb_shell_get_visibility (shell)) { 2139 rb_profile_end ("changing shell visibility"); 2140 return; 2141 } 2142 2143 really_visible = visible; 2144 g_signal_emit (shell, rb_shell_signals[VISIBILITY_CHANGING], 0, initial, visible, &really_visible); 2145 2146 if (really_visible) { 2147 rb_debug ("showing main window"); 2148 rb_shell_sync_window_state (shell, FALSE); 2149 2150 gtk_widget_show (GTK_WIDGET (shell->priv->window)); 2151 gtk_window_deiconify (GTK_WINDOW (shell->priv->window)); 2152 2153 if (gtk_widget_get_realized (GTK_WIDGET (shell->priv->window))) 2154 rb_shell_present (shell, gtk_get_current_event_time (), NULL); 2155 else 2156 gtk_widget_show_all (GTK_WIDGET (shell->priv->window)); 2157 2158 g_signal_emit (shell, rb_shell_signals[VISIBILITY_CHANGED], 0, visible); 2159 } else { 2160 rb_debug ("hiding main window"); 2161 shell->priv->iconified = TRUE; 2162 gtk_window_iconify (GTK_WINDOW (shell->priv->window)); 2163 2164 g_signal_emit (shell, rb_shell_signals[VISIBILITY_CHANGED], 0, FALSE); 2165 } 2166 2167 rb_profile_end ("changing shell visibility"); 2168 } 2169 2170 static void 2171 sync_window_settings (GSettings *settings, RBShell *shell) 2172 { 2173 int width, height; 2174 int oldwidth, oldheight; 2175 int oldx, oldy; 2176 int x, y; 2177 int pos; 2178 2179 gtk_window_get_size (GTK_WINDOW (shell->priv->window), &width, &height); 2180 2181 g_settings_get (shell->priv->settings, "size", "(ii)", &oldwidth, &oldheight); 2182 if ((width != oldwidth) || (height != oldheight)) { 2183 rb_debug ("storing window size of %d:%d", width, height); 2184 g_settings_set (shell->priv->settings, "size", "(ii)", width, height); 2185 } 2186 2187 gtk_window_get_position (GTK_WINDOW(shell->priv->window), &x, &y); 2188 g_settings_get (shell->priv->settings, "position", "(ii)", &oldx, &oldy); 2189 if ((x != oldx) || (y != oldy)) { 2190 rb_debug ("storing window position of %d:%d", x, y); 2191 g_settings_set (shell->priv->settings, "position", "(ii)", x, y); 2192 } 2193 2194 pos = gtk_paned_get_position (GTK_PANED (shell->priv->paned)); 2195 rb_debug ("paned position %d", pos); 2196 2197 if (pos != g_settings_get_int (shell->priv->settings, "paned-position")) { 2198 g_settings_set_int (shell->priv->settings, "paned-position", pos); 2199 } 2200 2201 pos = gtk_paned_get_position (GTK_PANED (shell->priv->right_paned)); 2202 rb_debug ("right_paned position %d", pos); 2203 2204 if (pos != g_settings_get_int (shell->priv->settings, "right-paned-position")) { 2205 g_settings_set_int (shell->priv->settings, "right-paned-position", pos); 2206 } 2207 2208 pos = gtk_paned_get_position (GTK_PANED (shell->priv->queue_paned)); 2209 rb_debug ("sidebar paned position %d", pos); 2210 2211 if (pos != g_settings_get_int (shell->priv->settings, "display-page-tree-height")) { 2212 g_settings_set_int (shell->priv->settings, "display-page-tree-height", pos); 2213 } 2214 } 2215 2216 static gboolean 2217 rb_shell_window_configure_cb (GtkWidget *win, 2218 GdkEventConfigure *event, 2219 RBShell *shell) 2220 { 2221 if (g_settings_get_boolean (shell->priv->settings, "maximized") || shell->priv->iconified) 2222 return FALSE; 2223 2224 rb_settings_delayed_sync (shell->priv->settings, 2225 (RBDelayedSyncFunc) sync_window_settings, 2226 g_object_ref (shell), 2227 g_object_unref); 2228 return FALSE; 2229 } 2230 2231 static gboolean 2232 rb_shell_window_delete_cb (GtkWidget *win, 2233 GdkEventAny *event, 2234 RBShell *shell) 2235 { 2236 if (shell->priv->party_mode) { 2237 return TRUE; 2238 } 2239 2240 rb_shell_quit (shell, NULL); 2241 2242 return TRUE; 2243 } 2244 2245 static gboolean 2246 rb_shell_key_press_event_cb (GtkWidget *win, 2247 GdkEventKey *event, 2248 RBShell *shell) 2249 { 2250 #ifndef HAVE_MMKEYS 2251 return FALSE; 2252 #else 2253 2254 gboolean retval = TRUE; 2255 2256 switch (event->keyval) { 2257 case XF86XK_Back: 2258 rb_shell_player_do_previous (shell->priv->player_shell, NULL); 2259 break; 2260 case XF86XK_Forward: 2261 rb_shell_player_do_next (shell->priv->player_shell, NULL); 2262 break; 2263 default: 2264 retval = FALSE; 2265 } 2266 2267 return retval; 2268 #endif /* !HAVE_MMKEYS */ 2269 } 2270 2271 static void 2272 rb_shell_sync_window_state (RBShell *shell, 2273 gboolean dont_maximise) 2274 { 2275 GdkGeometry hints; 2276 int width, height; 2277 int x, y; 2278 2279 rb_profile_start ("syncing window state"); 2280 2281 if (!dont_maximise) { 2282 if (g_settings_get_boolean (shell->priv->settings, "maximized")) 2283 gtk_window_maximize (GTK_WINDOW (shell->priv->window)); 2284 else 2285 gtk_window_unmaximize (GTK_WINDOW (shell->priv->window)); 2286 } 2287 2288 g_settings_get (shell->priv->settings, "size", "(ii)", &width, &height); 2289 2290 gtk_window_set_default_size (GTK_WINDOW (shell->priv->window), width, height); 2291 gtk_window_resize (GTK_WINDOW (shell->priv->window), width, height); 2292 gtk_window_set_geometry_hints (GTK_WINDOW (shell->priv->window), 2293 NULL, 2294 &hints, 2295 0); 2296 2297 g_settings_get (shell->priv->settings, "position", "(ii)", &x, &y); 2298 gtk_window_move (GTK_WINDOW (shell->priv->window), x, y); 2299 rb_profile_end ("syncing window state"); 2300 } 2301 2302 static void 2303 display_page_selected_cb (RBDisplayPageTree *display_page_tree, 2304 RBDisplayPage *page, 2305 RBShell *shell) 2306 { 2307 rb_debug ("page selected"); 2308 rb_shell_select_page (shell, page); 2309 } 2310 2311 gboolean 2312 rb_shell_activate_source (RBShell *shell, RBSource *source, guint play, GError **error) 2313 { 2314 RhythmDBEntry *entry; 2315 /* FIXME 2316 * 2317 * this doesn't work correctly yet, but it's still an improvement on the 2318 * previous behaviour. 2319 * 2320 * with crossfading enabled, this fades out the current song, but 2321 * doesn't start the new one. 2322 */ 2323 2324 /* Select the new one, and optionally start it playing */ 2325 rb_shell_select_page (shell, RB_DISPLAY_PAGE (source)); 2326 2327 switch (play) { 2328 case RB_SHELL_ACTIVATION_SELECT: 2329 return TRUE; 2330 2331 case RB_SHELL_ACTIVATION_PLAY: 2332 entry = rb_shell_player_get_playing_entry (shell->priv->player_shell); 2333 if (entry != NULL) { 2334 rhythmdb_entry_unref (entry); 2335 return TRUE; 2336 } 2337 /* fall through */ 2338 case RB_SHELL_ACTIVATION_ALWAYS_PLAY: 2339 rb_shell_player_set_playing_source (shell->priv->player_shell, source); 2340 return rb_shell_player_playpause (shell->priv->player_shell, FALSE, error); 2341 2342 default: 2343 return FALSE; 2344 } 2345 } 2346 2347 static void 2348 rb_shell_db_save_error_cb (RhythmDB *db, 2349 const char *uri, const GError *error, 2350 RBShell *shell) 2351 { 2352 rb_error_dialog (GTK_WINDOW (shell->priv->window), 2353 _("Error while saving song information"), 2354 "%s", error->message); 2355 } 2356 2357 /** 2358 * rb_shell_get_source_by_entry_type: 2359 * @shell: the #RBShell 2360 * @type: entry type for which to find a source 2361 * 2362 * Looks up and returns the source that owns entries of the specified 2363 * type. 2364 * 2365 * Return value: (transfer none): source instance, if any 2366 */ 2367 RBSource * 2368 rb_shell_get_source_by_entry_type (RBShell *shell, 2369 RhythmDBEntryType *type) 2370 { 2371 return g_hash_table_lookup (shell->priv->sources_hash, type); 2372 } 2373 2374 /** 2375 * rb_shell_register_entry_type_for_source: 2376 * @shell: the #RBShell 2377 * @source: the #RBSource to register 2378 * @type: the #RhythmDBEntryType to register for 2379 * 2380 * Registers a source as the owner of entries of the specified type. 2381 * The main effect of this is that calling #rb_shell_get_source_by_entry_type 2382 * with the same entry type will return the source. A source should only 2383 * be registered as the owner of a single entry type. 2384 */ 2385 void 2386 rb_shell_register_entry_type_for_source (RBShell *shell, 2387 RBSource *source, 2388 RhythmDBEntryType *type) 2389 { 2390 if (shell->priv->sources_hash == NULL) { 2391 shell->priv->sources_hash = g_hash_table_new (g_direct_hash, 2392 g_direct_equal); 2393 } 2394 g_assert (g_hash_table_lookup (shell->priv->sources_hash, type) == NULL); 2395 g_hash_table_insert (shell->priv->sources_hash, type, source); 2396 } 2397 2398 /** 2399 * rb_shell_append_display_page: 2400 * @shell: the #RBShell 2401 * @page: the new #RBDisplayPage 2402 * @parent: (allow-none): the parent page for the new page 2403 * 2404 * Adds a new display page to the shell. 2405 */ 2406 void 2407 rb_shell_append_display_page (RBShell *shell, RBDisplayPage *page, RBDisplayPage *parent) 2408 { 2409 if (RB_IS_SOURCE (page)) { 2410 shell->priv->sources = g_list_append (shell->priv->sources, RB_SOURCE (page)); 2411 } 2412 2413 g_signal_connect_object (G_OBJECT (page), "deleted", 2414 G_CALLBACK (rb_shell_display_page_deleted_cb), shell, 0); 2415 2416 gtk_notebook_append_page (GTK_NOTEBOOK (shell->priv->notebook), 2417 GTK_WIDGET (page), 2418 gtk_label_new ("")); 2419 gtk_widget_show (GTK_WIDGET (page)); 2420 2421 rb_display_page_model_add_page (shell->priv->display_page_model, page, parent); 2422 } 2423 2424 static void 2425 rb_shell_playlist_added_cb (RBPlaylistManager *mgr, 2426 RBSource *source, 2427 RBShell *shell) 2428 { 2429 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (source), RB_DISPLAY_PAGE_GROUP_PLAYLISTS); 2430 } 2431 2432 static void 2433 rb_shell_playlist_created_cb (RBPlaylistManager *mgr, 2434 RBSource *source, 2435 RBShell *shell) 2436 { 2437 g_settings_set_boolean (shell->priv->settings, "display-page-tree-visible", TRUE); 2438 2439 rb_shell_sync_window_state (shell, FALSE); 2440 } 2441 2442 static void 2443 rb_shell_medium_added_cb (RBRemovableMediaManager *mgr, 2444 RBSource *source, 2445 RBShell *shell) 2446 { 2447 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (source), RB_DISPLAY_PAGE_GROUP_DEVICES); 2448 } 2449 2450 static void 2451 rb_shell_display_page_deleted_cb (RBDisplayPage *page, RBShell *shell) 2452 { 2453 2454 rb_debug ("display page deleted"); 2455 2456 if (RB_IS_SOURCE (page)) { 2457 RhythmDBEntryType *entry_type; 2458 RBSource *source = RB_SOURCE (page); 2459 2460 /* remove from the map if the source owns the type */ 2461 g_object_get (source, "entry-type", &entry_type, NULL); 2462 if (rb_shell_get_source_by_entry_type (shell, entry_type) == source) { 2463 g_hash_table_remove (shell->priv->sources_hash, entry_type); 2464 } 2465 g_object_unref (entry_type); 2466 2467 if (source == rb_shell_player_get_playing_source (shell->priv->player_shell) || 2468 source == rb_shell_player_get_active_source (shell->priv->player_shell)) { 2469 rb_shell_player_stop (shell->priv->player_shell); 2470 } 2471 2472 shell->priv->sources = g_list_remove (shell->priv->sources, source); 2473 } 2474 2475 if (page == shell->priv->selected_page) { 2476 if (page != RB_DISPLAY_PAGE (shell->priv->library_source)) { 2477 rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source)); 2478 } else { 2479 rb_shell_select_page (shell, NULL); 2480 } 2481 } 2482 2483 rb_display_page_model_remove_page (shell->priv->display_page_model, page); 2484 gtk_notebook_remove_page (GTK_NOTEBOOK (shell->priv->notebook), 2485 gtk_notebook_page_num (GTK_NOTEBOOK (shell->priv->notebook), 2486 GTK_WIDGET (page))); 2487 } 2488 2489 static void 2490 rb_shell_playing_source_changed_cb (RBShellPlayer *player, 2491 RBSource *source, 2492 RBShell *shell) 2493 { 2494 rb_debug ("playing source changed"); 2495 if (source != RB_SOURCE (shell->priv->queue_source)) { 2496 rb_display_page_model_set_playing_source (shell->priv->display_page_model, RB_DISPLAY_PAGE (source)); 2497 } 2498 } 2499 2500 static void 2501 rb_shell_playing_from_queue_cb (RBShellPlayer *player, 2502 GParamSpec *param, 2503 RBShell *shell) 2504 { 2505 gboolean from_queue; 2506 2507 g_object_get (player, "playing-from-queue", &from_queue, NULL); 2508 if (!g_settings_get_boolean (shell->priv->settings, "queue-as-sidebar")) { 2509 RBSource *source; 2510 source = rb_shell_player_get_playing_source (shell->priv->player_shell); 2511 rb_display_page_model_set_playing_source (shell->priv->display_page_model, RB_DISPLAY_PAGE (source)); 2512 } else { 2513 RBSource *source; 2514 RhythmDBEntry *entry; 2515 RhythmDBEntryType *entry_type; 2516 2517 /* if playing from the queue, show the playing entry as playing in the 2518 * registered source for its type, so it makes sense when 'jump to current' 2519 * jumps to it there. 2520 */ 2521 entry = rb_shell_player_get_playing_entry (shell->priv->player_shell); 2522 if (entry == NULL) 2523 return; 2524 2525 entry_type = rhythmdb_entry_get_entry_type (entry); 2526 source = rb_shell_get_source_by_entry_type (shell, entry_type); 2527 if (source != NULL) { 2528 RBEntryViewState state; 2529 RBEntryView *songs; 2530 2531 songs = rb_source_get_entry_view (source); 2532 if (songs != NULL) { 2533 state = from_queue ? RB_ENTRY_VIEW_PLAYING : RB_ENTRY_VIEW_NOT_PLAYING; 2534 rb_entry_view_set_state (songs, state); 2535 } 2536 } 2537 rhythmdb_entry_unref (entry); 2538 2539 source = rb_shell_player_get_active_source (shell->priv->player_shell); 2540 rb_display_page_model_set_playing_source (shell->priv->display_page_model, RB_DISPLAY_PAGE (source)); 2541 } 2542 } 2543 2544 static void 2545 rb_shell_select_page (RBShell *shell, RBDisplayPage *page) 2546 { 2547 int pagenum; 2548 2549 if (shell->priv->selected_page == page) 2550 return; 2551 2552 rb_debug ("selecting page %p", page); 2553 2554 if (shell->priv->selected_page) { 2555 rb_display_page_deselected (shell->priv->selected_page); 2556 gtk_ui_manager_remove_ui (shell->priv->ui_manager, shell->priv->source_ui_merge_id); 2557 } 2558 2559 shell->priv->selected_page = page; 2560 rb_display_page_selected (shell->priv->selected_page); 2561 2562 /* show page */ 2563 pagenum = gtk_notebook_page_num (GTK_NOTEBOOK (shell->priv->notebook), 2564 GTK_WIDGET (page)); 2565 gtk_notebook_set_current_page (GTK_NOTEBOOK (shell->priv->notebook), pagenum); 2566 2567 g_signal_handlers_block_by_func (shell->priv->display_page_tree, 2568 G_CALLBACK (display_page_selected_cb), 2569 shell); 2570 rb_display_page_tree_select (shell->priv->display_page_tree, page); 2571 g_signal_handlers_unblock_by_func (shell->priv->display_page_tree, 2572 G_CALLBACK (display_page_selected_cb), 2573 shell); 2574 2575 /* update services */ 2576 if (RB_IS_SOURCE (page)) { 2577 RBSource *source = RB_SOURCE (page); 2578 rb_shell_clipboard_set_source (shell->priv->clipboard_shell, source); 2579 rb_shell_player_set_selected_source (shell->priv->player_shell, source); 2580 g_object_set (shell->priv->playlist_manager, "source", source, NULL); 2581 g_object_set (shell->priv->removable_media_manager, "source", source, NULL); 2582 } else { 2583 rb_shell_clipboard_set_source (shell->priv->clipboard_shell, NULL); 2584 rb_shell_player_set_selected_source (shell->priv->player_shell, NULL); /* ? */ 2585 2586 /* clear playlist-manager:source? */ 2587 /* clear removable-media-manager:source? */ 2588 } 2589 rb_statusbar_set_page (shell->priv->statusbar, page); 2590 2591 g_object_notify (G_OBJECT (shell), "selected-page"); 2592 } 2593 2594 static void 2595 rb_shell_player_window_title_changed_cb (RBShellPlayer *player, 2596 const char *window_title, 2597 RBShell *shell) 2598 { 2599 rb_shell_set_window_title (shell, window_title); 2600 } 2601 2602 static void 2603 rb_shell_set_window_title (RBShell *shell, 2604 const char *window_title) 2605 { 2606 if (window_title == NULL) { 2607 rb_debug ("clearing title"); 2608 2609 g_free (shell->priv->cached_title); 2610 shell->priv->cached_title = NULL; 2611 2612 gtk_window_set_title (GTK_WINDOW (shell->priv->window), 2613 _("Rhythmbox")); 2614 } 2615 else { 2616 gboolean playing; 2617 char *title; 2618 2619 rb_shell_player_get_playing (shell->priv->player_shell, &playing, NULL); 2620 2621 if (shell->priv->cached_title && 2622 !strcmp (shell->priv->cached_title, window_title) && 2623 playing == shell->priv->cached_playing) { 2624 return; 2625 } 2626 g_free (shell->priv->cached_title); 2627 shell->priv->cached_title = g_strdup (window_title); 2628 shell->priv->cached_playing = playing; 2629 2630 rb_debug ("setting title to \"%s\"", window_title); 2631 if (!playing) { 2632 /* Translators: %s is the song name */ 2633 title = g_strdup_printf (_("%s (Paused)"), window_title); 2634 gtk_window_set_title (GTK_WINDOW (shell->priv->window), 2635 title); 2636 g_free (title); 2637 } else { 2638 gtk_window_set_title (GTK_WINDOW (shell->priv->window), 2639 window_title); 2640 } 2641 } 2642 } 2643 2644 static void 2645 rb_shell_view_statusbar_changed_cb (GtkAction *action, 2646 RBShell *shell) 2647 { 2648 g_settings_set_boolean (shell->priv->settings, 2649 "statusbar-hidden", 2650 !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))); 2651 2652 rb_shell_sync_statusbar_visibility (shell); 2653 } 2654 2655 static void 2656 rb_shell_view_queue_as_sidebar_changed_cb (GtkAction *action, 2657 RBShell *shell) 2658 { 2659 gboolean queue_as_sidebar = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); 2660 /* maybe use a settings binding? */ 2661 g_settings_set_boolean (shell->priv->settings, 2662 "queue-as-sidebar", 2663 queue_as_sidebar); 2664 2665 if (queue_as_sidebar && 2666 shell->priv->selected_page == RB_DISPLAY_PAGE (shell->priv->queue_source)) { 2667 /* queue no longer exists as a source, so change to the library */ 2668 rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source)); 2669 } 2670 2671 rb_shell_playing_from_queue_cb (shell->priv->player_shell, NULL, shell); 2672 2673 rb_shell_sync_pane_visibility (shell); 2674 } 2675 2676 static void 2677 rb_shell_view_party_mode_changed_cb (GtkAction *action, 2678 RBShell *shell) 2679 { 2680 shell->priv->party_mode = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); 2681 rb_shell_sync_party_mode (shell); 2682 } 2683 2684 static void 2685 rb_shell_cmd_about (GtkAction *action, 2686 RBShell *shell) 2687 { 2688 const char **tem; 2689 GString *comment; 2690 2691 const char *authors[] = { 2692 "", 2693 #include "MAINTAINERS.tab" 2694 "", 2695 NULL, 2696 #include "MAINTAINERS.old.tab" 2697 "", 2698 NULL, 2699 #include "AUTHORS.tab" 2700 NULL 2701 }; 2702 2703 const char *documenters[] = { 2704 #include "DOCUMENTERS.tab" 2705 NULL 2706 }; 2707 2708 const char *translator_credits = _("translator-credits"); 2709 2710 const char *license[] = { 2711 N_("Rhythmbox is free software; you can redistribute it and/or modify\n" 2712 "it under the terms of the GNU General Public License as published by\n" 2713 "the Free Software Foundation; either version 2 of the License, or\n" 2714 "(at your option) any later version.\n"), 2715 N_("Rhythmbox is distributed in the hope that it will be useful,\n" 2716 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" 2717 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" 2718 "GNU General Public License for more details.\n"), 2719 N_("You should have received a copy of the GNU General Public License\n" 2720 "along with Rhythmbox; if not, write to the Free Software Foundation, Inc.,\n" 2721 "51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA\n") 2722 }; 2723 2724 char *license_trans; 2725 2726 authors[0] = _("Maintainers:"); 2727 for (tem = authors; *tem != NULL; tem++) 2728 ; 2729 *tem = _("Former Maintainers:"); 2730 for (; *tem != NULL; tem++) 2731 ; 2732 *tem = _("Contributors:"); 2733 2734 comment = g_string_new (_("Music management and playback software for GNOME.")); 2735 2736 license_trans = g_strconcat (_(license[0]), "\n", _(license[1]), "\n", 2737 _(license[2]), "\n", NULL); 2738 2739 gtk_show_about_dialog (GTK_WINDOW (shell->priv->window), 2740 "version", VERSION, 2741 "copyright", "Copyright \xc2\xa9 2005 - 2009 The Rhythmbox authors\nCopyright \xc2\xa9 2003 - 2005 Colin Walters\nCopyright \xc2\xa9 2002, 2003 Jorn Baayen", 2742 "license", license_trans, 2743 "website-label", _("Rhythmbox Website"), 2744 "website", "http://www.gnome.org/projects/rhythmbox", 2745 "comments", comment->str, 2746 "authors", (const char **) authors, 2747 "documenters", (const char **) documenters, 2748 "translator-credits", strcmp (translator_credits, "translator-credits") != 0 ? translator_credits : NULL, 2749 "logo-icon-name", "rhythmbox", 2750 NULL); 2751 g_string_free (comment, TRUE); 2752 g_free (license_trans); 2753 } 2754 2755 /** 2756 * rb_shell_toggle_visibility: 2757 * @shell: the #RBShell 2758 * 2759 * Toggles the visibility of the main Rhythmbox window. 2760 */ 2761 void 2762 rb_shell_toggle_visibility (RBShell *shell) 2763 { 2764 gboolean visible; 2765 2766 visible = rb_shell_get_visibility (shell); 2767 2768 rb_shell_set_visibility (shell, FALSE, !visible); 2769 } 2770 2771 static void 2772 rb_shell_cmd_quit (GtkAction *action, 2773 RBShell *shell) 2774 { 2775 rb_shell_quit (shell, NULL); 2776 } 2777 2778 static void 2779 rb_shell_cmd_contents (GtkAction *action, 2780 RBShell *shell) 2781 { 2782 GError *error = NULL; 2783 2784 gtk_show_uri (gtk_widget_get_screen (shell->priv->window), 2785 "ghelp:rhythmbox", 2786 gtk_get_current_event_time (), 2787 &error); 2788 2789 if (error != NULL) { 2790 rb_error_dialog (NULL, _("Couldn't display help"), 2791 "%s", error->message); 2792 g_error_free (error); 2793 } 2794 } 2795 2796 static void 2797 rb_shell_cmd_preferences (GtkAction *action, 2798 RBShell *shell) 2799 { 2800 RBShellPreferences *prefs; 2801 2802 g_object_get (shell, "prefs", &prefs, NULL); 2803 2804 gtk_window_present (GTK_WINDOW (prefs)); 2805 g_object_unref (prefs); 2806 } 2807 2808 static gboolean 2809 rb_shell_plugins_window_delete_cb (GtkWidget *window, 2810 GdkEventAny *event, 2811 gpointer data) 2812 { 2813 gtk_widget_hide (window); 2814 2815 return TRUE; 2816 } 2817 2818 static void 2819 rb_shell_plugins_response_cb (GtkDialog *dialog, 2820 int response_id, 2821 gpointer data) 2822 { 2823 if (response_id == GTK_RESPONSE_CLOSE) 2824 gtk_widget_hide (GTK_WIDGET (dialog)); 2825 } 2826 2827 static void 2828 rb_shell_cmd_plugins (GtkAction *action, 2829 RBShell *shell) 2830 { 2831 if (shell->priv->plugins == NULL) { 2832 GtkWidget *content_area; 2833 GtkWidget *manager; 2834 2835 shell->priv->plugins = gtk_dialog_new_with_buttons (_("Configure Plugins"), 2836 GTK_WINDOW (shell->priv->window), 2837 GTK_DIALOG_DESTROY_WITH_PARENT, 2838 GTK_STOCK_CLOSE, 2839 GTK_RESPONSE_CLOSE, 2840 NULL); 2841 content_area = gtk_dialog_get_content_area (GTK_DIALOG (shell->priv->plugins)); 2842 gtk_container_set_border_width (GTK_CONTAINER (shell->priv->plugins), 5); 2843 gtk_box_set_spacing (GTK_BOX (content_area), 2); 2844 2845 g_signal_connect_object (G_OBJECT (shell->priv->plugins), 2846 "delete_event", 2847 G_CALLBACK (rb_shell_plugins_window_delete_cb), 2848 NULL, 0); 2849 g_signal_connect_object (G_OBJECT (shell->priv->plugins), 2850 "response", 2851 G_CALLBACK (rb_shell_plugins_response_cb), 2852 NULL, 0); 2853 2854 manager = peas_gtk_plugin_manager_new (NULL); 2855 gtk_widget_show_all (GTK_WIDGET (manager)); 2856 gtk_box_pack_start (GTK_BOX (content_area), manager, TRUE, TRUE, 0); 2857 gtk_window_set_default_size (GTK_WINDOW (shell->priv->plugins), 600, 400); 2858 } 2859 2860 gtk_window_present (GTK_WINDOW (shell->priv->plugins)); 2861 } 2862 2863 static void 2864 rb_shell_cmd_add_music (GtkAction *action, RBShell *shell) 2865 { 2866 rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source)); 2867 rb_library_source_show_import_dialog (shell->priv->library_source); 2868 } 2869 2870 static gboolean 2871 quit_timeout (gpointer dummy) 2872 { 2873 GDK_THREADS_ENTER (); 2874 rb_debug ("quit damn you"); 2875 gtk_main_quit (); 2876 GDK_THREADS_LEAVE (); 2877 return FALSE; 2878 } 2879 2880 /** 2881 * rb_shell_quit: 2882 * @shell: the #RBShell 2883 * @error: not used 2884 * 2885 * Begins the process of shutting down Rhythmbox. This function will 2886 * return. The error parameter and return value only exist because this 2887 * function is part of the DBus interface. 2888 * 2889 * Return value: not important 2890 */ 2891 gboolean 2892 rb_shell_quit (RBShell *shell, 2893 GError **error) 2894 { 2895 rb_debug ("Quitting"); 2896 2897 /* Stop the playing source, if any */ 2898 rb_shell_player_stop (shell->priv->player_shell); 2899 2900 rb_podcast_manager_shutdown (shell->priv->podcast_manager); 2901 2902 rb_shell_shutdown (shell); 2903 rb_shell_sync_state (shell); 2904 2905 g_application_release (G_APPLICATION (shell)); 2906 2907 g_timeout_add_seconds (10, quit_timeout, NULL); 2908 return TRUE; 2909 } 2910 2911 static gboolean 2912 idle_handle_load_complete (RBShell *shell) 2913 { 2914 gboolean loaded, scanned; 2915 GDK_THREADS_ENTER (); 2916 2917 rb_debug ("load complete"); 2918 2919 rb_playlist_manager_load_playlists (shell->priv->playlist_manager); 2920 rb_display_page_group_loaded (RB_DISPLAY_PAGE_GROUP (RB_DISPLAY_PAGE_GROUP_PLAYLISTS)); 2921 shell->priv->load_complete = TRUE; 2922 shell->priv->save_playlist_id = g_timeout_add_seconds (10, (GSourceFunc) idle_save_playlist_manager, shell); 2923 2924 if (shell->priv->no_registration == FALSE) { 2925 g_variant_get (g_action_group_get_action_state (G_ACTION_GROUP (shell), "LoadURI"), "(bb)", &loaded, &scanned); 2926 g_action_group_change_action_state (G_ACTION_GROUP (shell), "LoadURI", g_variant_new ("(bb)", TRUE, scanned)); 2927 } 2928 2929 rhythmdb_start_action_thread (shell->priv->db); 2930 2931 GDK_THREADS_LEAVE (); 2932 2933 return FALSE; 2934 } 2935 2936 static void 2937 rb_shell_load_complete_cb (RhythmDB *db, 2938 RBShell *shell) 2939 { 2940 g_idle_add ((GSourceFunc) idle_handle_load_complete, shell); 2941 } 2942 2943 static void 2944 rb_shell_sync_pane_visibility (RBShell *shell) 2945 { 2946 GtkAction *action; 2947 gboolean queue_as_sidebar = g_settings_get_boolean (shell->priv->settings, "queue-as-sidebar"); 2948 2949 if (shell->priv->queue_source != NULL) { 2950 g_object_set (shell->priv->queue_source, "visibility", !queue_as_sidebar, NULL); 2951 } 2952 2953 if (queue_as_sidebar) { 2954 gtk_widget_show (shell->priv->queue_sidebar); 2955 } else { 2956 gtk_widget_hide (shell->priv->queue_sidebar); 2957 } 2958 2959 action = gtk_action_group_get_action (shell->priv->actiongroup, 2960 "ViewQueueAsSidebar"); 2961 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), queue_as_sidebar); 2962 } 2963 2964 static gboolean 2965 window_state_event_cb (GtkWidget *widget, 2966 GdkEventWindowState *event, 2967 RBShell *shell) 2968 { 2969 if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) { 2970 rb_shell_present (shell, gtk_get_current_event_time (), NULL); 2971 } 2972 2973 return TRUE; 2974 } 2975 2976 static void 2977 rb_shell_sync_party_mode (RBShell *shell) 2978 { 2979 GtkAction *action; 2980 2981 /* party mode does not use gsettings as a model since it 2982 should not be persistent */ 2983 2984 /* disable/enable quit action */ 2985 action = gtk_action_group_get_action (shell->priv->actiongroup, "MusicQuit"); 2986 g_object_set (action, "sensitive", !shell->priv->party_mode, NULL); 2987 2988 /* show/hide queue as sidebar ? */ 2989 2990 g_object_set (shell->priv->player_shell, "queue-only", shell->priv->party_mode, NULL); 2991 2992 /* Set playlist manager source to the current source to update properties */ 2993 if (shell->priv->selected_page && RB_IS_SOURCE (shell->priv->selected_page)) { 2994 RBSource *source = RB_SOURCE (shell->priv->selected_page); 2995 g_object_set (shell->priv->playlist_manager, "source", source, NULL); 2996 rb_shell_clipboard_set_source (shell->priv->clipboard_shell, source); 2997 } 2998 2999 gtk_window_set_keep_above (GTK_WINDOW (shell->priv->window), shell->priv->party_mode); 3000 if (shell->priv->party_mode) { 3001 gtk_window_fullscreen (GTK_WINDOW (shell->priv->window)); 3002 gtk_window_stick (GTK_WINDOW (shell->priv->window)); 3003 g_signal_connect (shell->priv->window, "window-state-event", G_CALLBACK (window_state_event_cb), shell); 3004 } else { 3005 gtk_window_unstick (GTK_WINDOW (shell->priv->window)); 3006 gtk_window_unfullscreen (GTK_WINDOW (shell->priv->window)); 3007 g_signal_handlers_disconnect_by_func (shell->priv->window, window_state_event_cb, shell); 3008 } 3009 } 3010 3011 static void 3012 rb_shell_sync_statusbar_visibility (RBShell *shell) 3013 { 3014 gboolean visible; 3015 GtkAction *action; 3016 3017 visible = !g_settings_get_boolean (shell->priv->settings, "statusbar-hidden"); 3018 3019 action = gtk_action_group_get_action (shell->priv->actiongroup, "ViewStatusbar"); 3020 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), visible); 3021 3022 gtk_widget_set_visible (GTK_WIDGET (shell->priv->statusbar), visible); 3023 } 3024 3025 static void 3026 rb_shell_sync_paned (RBShell *shell) 3027 { 3028 gtk_paned_set_position (GTK_PANED (shell->priv->right_paned), 3029 g_settings_get_int (shell->priv->settings, "right-paned-position")); 3030 gtk_paned_set_position (GTK_PANED (shell->priv->paned), 3031 g_settings_get_int (shell->priv->settings, "paned-position")); 3032 gtk_paned_set_position (GTK_PANED (shell->priv->queue_paned), 3033 g_settings_get_int (shell->priv->settings, "display-page-tree-height")); 3034 } 3035 3036 static void 3037 paned_size_allocate_cb (GtkWidget *widget, 3038 GtkAllocation *allocation, 3039 RBShell *shell) 3040 { 3041 rb_settings_delayed_sync (shell->priv->settings, 3042 (RBDelayedSyncFunc) sync_window_settings, 3043 g_object_ref (shell), 3044 g_object_unref); 3045 } 3046 3047 static void 3048 display_page_tree_drag_received_cb (RBDisplayPageTree *display_page_tree, 3049 RBDisplayPage *page, 3050 GtkSelectionData *data, 3051 RBShell *shell) 3052 { 3053 if (page == NULL) { 3054 RBSource *source; 3055 source = rb_playlist_manager_new_playlist_from_selection_data (shell->priv->playlist_manager, 3056 data); 3057 page = RB_DISPLAY_PAGE (source); 3058 } 3059 3060 if (page != NULL) { 3061 rb_display_page_receive_drag (page, data); 3062 } 3063 3064 } 3065 3066 static void 3067 rb_shell_cmd_current_song (GtkAction *action, 3068 RBShell *shell) 3069 { 3070 rb_debug ("current song"); 3071 3072 rb_shell_jump_to_current (shell); 3073 } 3074 3075 static void 3076 rb_shell_cmd_view_all (GtkAction *action, 3077 RBShell *shell) 3078 { 3079 if (RB_IS_SOURCE (shell->priv->selected_page)) { 3080 RBSource *source = RB_SOURCE (shell->priv->selected_page); 3081 rb_debug ("view all"); 3082 3083 rb_source_reset_filters (source); 3084 } 3085 } 3086 3087 static void 3088 rb_shell_jump_to_entry_with_source (RBShell *shell, 3089 RBSource *source, 3090 RhythmDBEntry *entry) 3091 { 3092 RBEntryView *songs; 3093 3094 g_return_if_fail (entry != NULL); 3095 3096 if ((source == RB_SOURCE (shell->priv->queue_source) && 3097 g_settings_get_boolean (shell->priv->settings, "queue-as-sidebar")) || 3098 source == NULL) { 3099 RhythmDBEntryType *entry_type; 3100 entry_type = rhythmdb_entry_get_entry_type (entry); 3101 source = rb_shell_get_source_by_entry_type (shell, entry_type); 3102 } 3103 if (source == NULL) 3104 return; 3105 3106 songs = rb_source_get_entry_view (source); 3107 rb_shell_select_page (shell, RB_DISPLAY_PAGE (source)); 3108 3109 if (songs != NULL) { 3110 rb_entry_view_scroll_to_entry (songs, entry); 3111 rb_entry_view_select_entry (songs, entry); 3112 } 3113 } 3114 3115 static void 3116 rb_shell_play_entry (RBShell *shell, 3117 RhythmDBEntry *entry) 3118 { 3119 rb_shell_player_stop (shell->priv->player_shell); 3120 rb_shell_jump_to_entry_with_source (shell, NULL, entry); 3121 rb_shell_player_play_entry (shell->priv->player_shell, entry, NULL); 3122 } 3123 3124 static void 3125 rb_shell_jump_to_current (RBShell *shell) 3126 { 3127 RBSource *source; 3128 RhythmDBEntry *playing; 3129 3130 source = rb_shell_player_get_playing_source (shell->priv->player_shell); 3131 3132 g_return_if_fail (source != NULL); 3133 3134 playing = rb_shell_player_get_playing_entry (shell->priv->player_shell); 3135 3136 rb_shell_jump_to_entry_with_source (shell, source, playing); 3137 rhythmdb_entry_unref (playing); 3138 } 3139 3140 void 3141 rb_shell_notify_custom (RBShell *shell, 3142 guint timeout, 3143 const char *primary, 3144 const char *secondary, 3145 const char *image_uri, 3146 gboolean requested) 3147 { 3148 g_signal_emit (shell, rb_shell_signals[NOTIFY_CUSTOM], 0, timeout, primary, secondary, image_uri, requested); 3149 } 3150 3151 /** 3152 * rb_shell_do_notify: 3153 * @shell: the #RBShell 3154 * @requested: if %TRUE, the notification was requested by some explicit user action 3155 * @error: not used 3156 * 3157 * Displays a notification of the current playing track. 3158 * 3159 * Return value: not important 3160 */ 3161 gboolean 3162 rb_shell_do_notify (RBShell *shell, gboolean requested, GError **error) 3163 { 3164 g_signal_emit (shell, rb_shell_signals[NOTIFY_PLAYING_ENTRY], 0, requested); 3165 return TRUE; 3166 } 3167 3168 /** 3169 * rb_shell_error_quark: 3170 * 3171 * Returns the #GQuark used for #RBShell errors 3172 * 3173 * Return value: shell error #GQuark 3174 */ 3175 GQuark 3176 rb_shell_error_quark (void) 3177 { 3178 static GQuark quark = 0; 3179 if (!quark) 3180 quark = g_quark_from_static_string ("rb_shell_error"); 3181 3182 return quark; 3183 } 3184 3185 static void 3186 session_save_state_cb (EggSMClient *client, 3187 GKeyFile *key_file, 3188 RBShell *shell) 3189 { 3190 rb_debug ("session save-state"); 3191 rb_shell_sync_state (shell); 3192 } 3193 3194 static void 3195 session_quit_cb (EggSMClient *client, 3196 RBShell *shell) 3197 { 3198 rb_debug ("session quit"); 3199 rb_shell_quit (shell, NULL); 3200 } 3201 3202 static void 3203 rb_shell_session_init (RBShell *shell) 3204 { 3205 EggSMClient *sm_client; 3206 3207 sm_client = egg_sm_client_get (); 3208 g_signal_connect (sm_client, "save-state", G_CALLBACK (session_save_state_cb), shell); 3209 g_signal_connect (sm_client, "quit", G_CALLBACK (session_quit_cb), shell); 3210 } 3211 3212 /** 3213 * rb_shell_guess_source_for_uri: 3214 * @shell: the #RBSource 3215 * @uri: the URI to guess a source for 3216 * 3217 * Attempts to locate the source that should handle the specified URI. 3218 * This iterates through all sources, calling #rb_source_want_uri, 3219 * returning the source that returns the highest value. 3220 * 3221 * Return value: (transfer none): the most appropriate #RBSource for the uri 3222 */ 3223 RBSource * 3224 rb_shell_guess_source_for_uri (RBShell *shell, 3225 const char *uri) 3226 { 3227 GList *t; 3228 RBSource *best = NULL; 3229 guint strength = 0; 3230 3231 for (t = shell->priv->sources; t != NULL; t = t->next) { 3232 guint s; 3233 RBSource *source; 3234 3235 source = (RBSource *)t->data; 3236 if (rb_source_uri_is_source (source, uri)) 3237 return source; 3238 3239 s = rb_source_want_uri (source, uri); 3240 if (s > strength) { 3241 gchar *name; 3242 3243 g_object_get (source, "name", &name, NULL); 3244 rb_debug ("source %s returned strength %u for uri %s", 3245 name, s, uri); 3246 g_free (name); 3247 3248 strength = s; 3249 best = source; 3250 } 3251 } 3252 3253 return best; 3254 } 3255 3256 /* Load a URI representing an element of the given type, with 3257 * optional metadata 3258 */ 3259 /** 3260 * rb_shell_add_uri: 3261 * @shell: the #RBShell 3262 * @uri: the URI to add 3263 * @title: optional title value for the URI 3264 * @genre: optional genre value for the URI 3265 * @error: returns error information 3266 * 3267 * Adds the specified URI to the Rhythmbox database. Whether the 3268 * title and genre specified are actually used is up to the source 3269 * that handles the URI 3270 * 3271 * Return value: TRUE if the URI was added successfully 3272 */ 3273 gboolean 3274 rb_shell_add_uri (RBShell *shell, 3275 const char *uri, 3276 const char *title, 3277 const char *genre, 3278 GError **error) 3279 { 3280 RBSource *source; 3281 3282 source = rb_shell_guess_source_for_uri (shell, uri); 3283 if (source == NULL) { 3284 g_set_error (error, 3285 RB_SHELL_ERROR, 3286 RB_SHELL_ERROR_NO_SOURCE_FOR_URI, 3287 _("No registered source can handle URI %s"), 3288 uri); 3289 return FALSE; 3290 } 3291 3292 rb_source_add_uri (source, uri, title, genre, NULL, NULL, NULL); 3293 return TRUE; 3294 } 3295 3296 typedef struct { 3297 RBShell *shell; 3298 char *uri; 3299 gboolean play; 3300 RBSource *playlist_source; 3301 gboolean can_use_playlist; 3302 gboolean source_is_entry; 3303 } PlaylistParseData; 3304 3305 static void 3306 handle_playlist_entry_cb (TotemPlParser *playlist, 3307 const char *uri, 3308 GHashTable *metadata, 3309 PlaylistParseData *data) 3310 { 3311 RBSource *source; 3312 3313 /* 3314 * Track whether the same playlist-handling source 3315 * wants all the URIs from the playlist; if it does, 3316 * then we'll just give the playlist URI to the source. 3317 */ 3318 if (data->can_use_playlist == FALSE) 3319 return; 3320 3321 source = rb_shell_guess_source_for_uri (data->shell, uri); 3322 if (data->playlist_source == NULL) { 3323 if (source != NULL && rb_source_try_playlist (source)) { 3324 data->playlist_source = RB_SOURCE (g_object_ref (source)); 3325 data->source_is_entry = rb_source_uri_is_source (source, uri); 3326 } else { 3327 data->can_use_playlist = FALSE; 3328 } 3329 } else if (data->playlist_source != source) { 3330 g_object_unref (data->playlist_source); 3331 data->playlist_source = NULL; 3332 data->can_use_playlist = FALSE; 3333 data->source_is_entry = FALSE; 3334 } 3335 } 3336 3337 static void 3338 shell_load_uri_done (RBSource *source, const char *uri, RBShell *shell) 3339 { 3340 RhythmDBEntry *entry; 3341 3342 entry = rhythmdb_entry_lookup_by_location (shell->priv->db, uri); 3343 if (entry) { 3344 rb_shell_play_entry (shell, entry); 3345 } else { 3346 rb_debug ("unable to find entry for uri %s", uri); 3347 } 3348 } 3349 3350 static void 3351 load_uri_finish (RBShell *shell, RBSource *entry_source, RhythmDBEntry *entry, gboolean play) 3352 { 3353 if (play == FALSE) { 3354 rb_debug ("didn't want to do anything anyway"); 3355 } else if (entry != NULL) { 3356 rb_debug ("found an entry to play"); 3357 rb_shell_play_entry (shell, entry); 3358 } else if (entry_source != NULL) { 3359 char *name; 3360 GError *error = NULL; 3361 3362 g_object_get (entry_source, "name", &name, NULL); 3363 if (rb_shell_activate_source (shell, entry_source, RB_SHELL_ACTIVATION_ALWAYS_PLAY, &error) == FALSE) { 3364 rb_debug ("couldn't activate source %s: %s", name, error->message); 3365 g_clear_error (&error); 3366 } else { 3367 rb_debug ("activated source '%s'", name); 3368 } 3369 g_free (name); 3370 } else { 3371 rb_debug ("couldn't do anything"); 3372 } 3373 } 3374 3375 static void 3376 load_uri_parser_finished_cb (GObject *parser, GAsyncResult *res, PlaylistParseData *data) 3377 { 3378 TotemPlParserResult result; 3379 RBSource *entry_source = NULL; 3380 GError *error = NULL; 3381 3382 result = totem_pl_parser_parse_finish (TOTEM_PL_PARSER (parser), res, &error); 3383 g_object_unref (parser); 3384 3385 if (error != NULL) { 3386 rb_debug ("parsing %s as a playlist failed: %s", data->uri, error->message); 3387 g_clear_error (&error); 3388 } else if (result == TOTEM_PL_PARSER_RESULT_UNHANDLED) { 3389 rb_debug ("%s unhandled", data->uri); 3390 } else if (result == TOTEM_PL_PARSER_RESULT_IGNORED) { 3391 rb_debug ("%s ignored", data->uri); 3392 } 3393 3394 if (result == TOTEM_PL_PARSER_RESULT_SUCCESS) { 3395 3396 if (data->can_use_playlist && data->playlist_source) { 3397 rb_debug ("adding playlist %s to source", data->uri); 3398 rb_source_add_uri (data->playlist_source, data->uri, NULL, NULL, NULL, NULL, NULL); 3399 3400 /* FIXME: We need some way to determine whether the URI as 3401 * given will appear in the db, or whether something else will. 3402 * This hack assumes we'll never add local playlists to the db 3403 * directly. 3404 */ 3405 if (rb_uri_is_local (data->uri) && (data->source_is_entry == FALSE)) { 3406 data->play = FALSE; 3407 } 3408 3409 if (data->source_is_entry != FALSE) { 3410 entry_source = data->playlist_source; 3411 } 3412 } else { 3413 rb_debug ("adding %s as a static playlist", data->uri); 3414 if (!rb_playlist_manager_parse_file (data->shell->priv->playlist_manager, 3415 data->uri, 3416 &error)) { 3417 rb_debug ("unable to parse %s as a static playlist: %s", data->uri, error->message); 3418 g_clear_error (&error); 3419 } 3420 data->play = FALSE; /* maybe we should play the new playlist? */ 3421 } 3422 } else { 3423 RBSource *source; 3424 3425 source = rb_shell_guess_source_for_uri (data->shell, data->uri); 3426 if (source != NULL) { 3427 char *name; 3428 g_object_get (source, "name", &name, NULL); 3429 if (rb_source_uri_is_source (source, data->uri)) { 3430 rb_debug ("%s identifies source %s", data->uri, name); 3431 entry_source = source; 3432 } else if (data->play) { 3433 rb_debug ("adding %s to source %s, will play it when it shows up", data->uri, name); 3434 rb_source_add_uri (source, data->uri, NULL, NULL, (RBSourceAddCallback) shell_load_uri_done, g_object_ref (data->shell), g_object_unref); 3435 data->play = FALSE; 3436 } else { 3437 rb_debug ("just adding %s to source %s", data->uri, name); 3438 rb_source_add_uri (source, data->uri, NULL, NULL, NULL, NULL, NULL); 3439 } 3440 g_free (name); 3441 } else { 3442 rb_debug ("couldn't find a source for %s, trying to add it anyway", data->uri); 3443 if (!rb_shell_add_uri (data->shell, data->uri, NULL, NULL, &error)) { 3444 rb_debug ("couldn't do it: %s", error->message); 3445 g_clear_error (&error); 3446 } 3447 } 3448 } 3449 3450 load_uri_finish (data->shell, entry_source, NULL, data->play); 3451 3452 if (data->playlist_source != NULL) { 3453 g_object_unref (data->playlist_source); 3454 } 3455 g_object_unref (data->shell); 3456 g_free (data->uri); 3457 g_free (data); 3458 } 3459 3460 /** 3461 * rb_shell_load_uri: 3462 * @shell: the #RBShell 3463 * @uri: the URI to load 3464 * @play: if TRUE, start playing the URI (if possible) 3465 * @error: returns error information 3466 * 3467 * Loads a URI representing a single song, a directory, a playlist, or 3468 * an internet radio station, and optionally starts playing it. 3469 * 3470 * For playlists containing only stream URLs, we either add the playlist 3471 * itself (if it's remote) or each URL from it (if it's local). The main 3472 * reason for this is so clicking on stream playlist links in web browsers 3473 * works properly - the playlist file will be downloaded to /tmp/, and 3474 * we can't add that to the database, so we need to add the stream URLs 3475 * instead. 3476 * 3477 * Return value: TRUE if the URI was added successfully 3478 */ 3479 gboolean 3480 rb_shell_load_uri (RBShell *shell, 3481 const char *uri, 3482 gboolean play, 3483 GError **error) 3484 { 3485 RhythmDBEntry *entry; 3486 3487 /* If the URI points to a Podcast, pass it on to the Podcast source */ 3488 if (rb_uri_could_be_podcast (uri, NULL)) { 3489 rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->podcast_source)); 3490 rb_podcast_source_add_feed (shell->priv->podcast_source, uri); 3491 return TRUE; 3492 } 3493 3494 entry = rhythmdb_entry_lookup_by_location (shell->priv->db, uri); 3495 3496 if (entry == NULL) { 3497 TotemPlParser *parser; 3498 PlaylistParseData *data; 3499 3500 data = g_new0 (PlaylistParseData, 1); 3501 data->shell = g_object_ref (shell); 3502 data->uri = g_strdup (uri); 3503 data->play = play; 3504 data->can_use_playlist = TRUE; 3505 data->source_is_entry = FALSE; 3506 data->playlist_source = NULL; 3507 3508 rb_debug ("adding uri %s, play %d", uri, play); 3509 parser = totem_pl_parser_new (); 3510 3511 g_signal_connect_data (parser, "entry-parsed", 3512 G_CALLBACK (handle_playlist_entry_cb), 3513 data, NULL, 0); 3514 3515 totem_pl_parser_add_ignored_mimetype (parser, "x-directory/normal"); 3516 totem_pl_parser_add_ignored_mimetype (parser, "inode/directory"); 3517 totem_pl_parser_add_ignored_scheme (parser, "cdda"); 3518 g_object_set (parser, "recurse", FALSE, NULL); 3519 if (rb_debug_matches ("totem_pl_parser_parse_async", "totem-pl-parser.c")) { 3520 g_object_set (parser, "debug", TRUE, NULL); 3521 } 3522 3523 totem_pl_parser_parse_async (parser, uri, FALSE, NULL, (GAsyncReadyCallback)load_uri_parser_finished_cb, data); 3524 } else { 3525 load_uri_finish (shell, NULL, entry, play); 3526 } 3527 3528 return TRUE; 3529 } 3530 3531 /** 3532 * rb_shell_get_party_mode: 3533 * @shell: the #RBShell 3534 * 3535 * Returns %TRUE if the shell is in party mode 3536 * 3537 * Return value: %TRUE if the shell is in party mode 3538 */ 3539 gboolean 3540 rb_shell_get_party_mode (RBShell *shell) 3541 { 3542 return shell->priv->party_mode; 3543 } 3544 3545 /** 3546 * rb_shell_present: 3547 * @shell: the #RBShell 3548 * @timestamp: GTK timestamp to use (for focus-stealing prevention) 3549 * @error: not used 3550 * 3551 * Attempts to display the main window to the user. See #gtk_window_present for details. 3552 * 3553 * Return value: not used. 3554 */ 3555 gboolean 3556 rb_shell_present (RBShell *shell, 3557 guint32 timestamp, 3558 GError **error) 3559 { 3560 rb_profile_start ("presenting shell"); 3561 3562 rb_debug ("presenting with timestamp %u", timestamp); 3563 gtk_widget_show (GTK_WIDGET (shell->priv->window)); 3564 gtk_window_present_with_time (GTK_WINDOW (shell->priv->window), timestamp); 3565 gtk_window_set_skip_taskbar_hint (GTK_WINDOW (shell->priv->window), FALSE); 3566 3567 rb_profile_end ("presenting shell"); 3568 3569 return TRUE; 3570 } 3571 3572 /** 3573 * rb_shell_activate_source_by_uri: 3574 * @shell: the #RBShell 3575 * @source_uri: URI for the source to activate 3576 * @play: 0: select source, 1: play source if not playing, 2: play source 3577 * @error: returns error information 3578 * 3579 * Searches for a source matching @source_uri and if found, selects it, 3580 * and depending on the value of @play, may start playing from it. 3581 * Device-based sources will match the device node or mount point URI. 3582 * Other types of sources may have their own URI scheme or format. 3583 * This is part of the DBus interface. 3584 * 3585 * Return value: %TRUE if successful 3586 */ 3587 gboolean 3588 rb_shell_activate_source_by_uri (RBShell *shell, 3589 const char *source_uri, 3590 guint play, 3591 GError **error) 3592 { 3593 GList *t; 3594 GFile *f; 3595 char *uri; 3596 3597 /* ensure the argument is actually a URI */ 3598 f = g_file_new_for_commandline_arg (source_uri); 3599 uri = g_file_get_uri (f); 3600 g_object_unref (f); 3601 3602 for (t = shell->priv->sources; t != NULL; t = t->next) { 3603 RBSource *source; 3604 3605 source = (RBSource *)t->data; 3606 if (rb_source_uri_is_source (source, uri)) { 3607 rb_debug ("found source for uri %s", uri); 3608 g_free (uri); 3609 return rb_shell_activate_source (shell, source, play, error); 3610 } 3611 } 3612 3613 g_set_error (error, 3614 RB_SHELL_ERROR, 3615 RB_SHELL_ERROR_NO_SOURCE_FOR_URI, 3616 _("No registered source matches URI %s"), 3617 uri); 3618 g_free (uri); 3619 return FALSE; 3620 } 3621 3622 /** 3623 * rb_shell_get_song_properties: 3624 * @shell: the #RBShell 3625 * @uri: the URI to query 3626 * @properties: (out callee-allocates) (element-type utf8 GObject.Value) returns the properties of the specified URI 3627 * @error: returns error information 3628 * 3629 * Gathers and returns all metadata (including extra metadata such as album 3630 * art URIs and lyrics) for the specified URI. 3631 * 3632 * Return value: %TRUE if the URI is found in the database 3633 */ 3634 gboolean 3635 rb_shell_get_song_properties (RBShell *shell, 3636 const char *uri, 3637 GHashTable **properties, 3638 GError **error) 3639 { 3640 RhythmDBEntry *entry; 3641 RBStringValueMap *map; 3642 3643 entry = rhythmdb_entry_lookup_by_location (shell->priv->db, uri); 3644 3645 if (entry == NULL) { 3646 g_set_error (error, 3647 RB_SHELL_ERROR, 3648 RB_SHELL_ERROR_NO_SUCH_URI, 3649 _("Unknown song URI: %s"), 3650 uri); 3651 return FALSE; 3652 } 3653 3654 map = rhythmdb_entry_gather_metadata (shell->priv->db, entry); 3655 *properties = rb_string_value_map_steal_hashtable (map); 3656 g_object_unref (map); 3657 3658 return (*properties != NULL); 3659 } 3660 3661 /** 3662 * rb_shell_set_song_property: 3663 * @shell: the #RBShell 3664 * @uri: the URI to modify 3665 * @propname: the name of the property to modify 3666 * @value: the new value to set 3667 * @error: returns error information 3668 * 3669 * Attempts to set a property of a database entry identified by its URI. 3670 * If the URI identifies a file and the property is one associated with a 3671 * file metadata tag, the new value will be written to the file. 3672 * 3673 * Return value: %TRUE if the property was set successfully. 3674 */ 3675 gboolean 3676 rb_shell_set_song_property (RBShell *shell, 3677 const char *uri, 3678 const char *propname, 3679 const GValue *value, 3680 GError **error) 3681 { 3682 RhythmDBEntry *entry; 3683 GType proptype; 3684 int propid; 3685 3686 entry = rhythmdb_entry_lookup_by_location (shell->priv->db, uri); 3687 3688 if (entry == NULL) { 3689 g_set_error (error, 3690 RB_SHELL_ERROR, 3691 RB_SHELL_ERROR_NO_SUCH_URI, 3692 _("Unknown song URI: %s"), 3693 uri); 3694 return FALSE; 3695 } 3696 3697 if ((propid = rhythmdb_propid_from_nice_elt_name (shell->priv->db, (guchar *) propname)) < 0) { 3698 g_set_error (error, 3699 RB_SHELL_ERROR, 3700 RB_SHELL_ERROR_NO_SUCH_PROPERTY, 3701 _("Unknown property %s"), 3702 propname); 3703 return FALSE; 3704 } 3705 3706 proptype = rhythmdb_get_property_type (shell->priv->db, propid); 3707 if (G_VALUE_TYPE (value) != proptype) { 3708 GValue convert = {0,}; 3709 g_value_init (&convert, proptype); 3710 if (g_value_transform (value, &convert) == FALSE) { 3711 g_value_unset (&convert); 3712 g_set_error (error, 3713 RB_SHELL_ERROR, 3714 RB_SHELL_ERROR_INVALID_PROPERTY_TYPE, 3715 _("Invalid property type %s for property %s"), 3716 g_type_name (G_VALUE_TYPE (value)), 3717 propname); 3718 return FALSE; 3719 } else { 3720 rhythmdb_entry_set (shell->priv->db, entry, propid, &convert); 3721 g_value_unset (&convert); 3722 } 3723 } else { 3724 rhythmdb_entry_set (shell->priv->db, entry, propid, value); 3725 } 3726 rhythmdb_commit (shell->priv->db); 3727 return TRUE; 3728 } 3729 3730 static void 3731 rb_shell_volume_widget_changed_cb (GtkScaleButton *vol, 3732 gdouble volume, 3733 RBShell *shell) 3734 { 3735 if (!shell->priv->syncing_volume) { 3736 g_object_set (shell->priv->player_shell, "volume", volume, NULL); 3737 } 3738 } 3739 3740 static void 3741 rb_shell_player_volume_changed_cb (RBShellPlayer *player, 3742 GParamSpec *arg, 3743 RBShell *shell) 3744 { 3745 float volume; 3746 3747 g_object_get (player, "volume", &volume, NULL); 3748 shell->priv->syncing_volume = TRUE; 3749 gtk_scale_button_set_value (GTK_SCALE_BUTTON (shell->priv->volume_button), volume); 3750 shell->priv->syncing_volume = FALSE; 3751 3752 } 3753 3754 static GtkBox* 3755 rb_shell_get_box_for_ui_location (RBShell *shell, RBShellUILocation location) 3756 { 3757 GtkBox *box = NULL; 3758 3759 switch (location) { 3760 case RB_SHELL_UI_LOCATION_SIDEBAR: 3761 box = shell->priv->sidebar_container; 3762 break; 3763 case RB_SHELL_UI_LOCATION_RIGHT_SIDEBAR: 3764 box = shell->priv->right_sidebar_container; 3765 break; 3766 case RB_SHELL_UI_LOCATION_MAIN_TOP: 3767 box = shell->priv->top_container; 3768 break; 3769 case RB_SHELL_UI_LOCATION_MAIN_BOTTOM: 3770 box = shell->priv->bottom_container; 3771 break; 3772 default: 3773 break; 3774 } 3775 3776 return box; 3777 } 3778 3779 /** 3780 * rb_shell_add_widget: 3781 * @shell: the #RBShell 3782 * @widget: the #GtkWidget to insert into the main window 3783 * @location: the location at which to insert the widget 3784 * @expand: whether the widget should be given extra space 3785 * @fill: whether the widget should fill all space allocated to it 3786 * 3787 * Adds a widget to the main Rhythmbox window. See #gtk_box_pack_start for 3788 * details on how the expand and fill parameters work. 3789 */ 3790 void 3791 rb_shell_add_widget (RBShell *shell, GtkWidget *widget, RBShellUILocation location, gboolean expand, gboolean fill) 3792 { 3793 GtkBox *box; 3794 3795 switch (location) { 3796 case RB_SHELL_UI_LOCATION_RIGHT_SIDEBAR: 3797 if (!shell->priv->right_sidebar_widget_count) 3798 gtk_widget_show (GTK_WIDGET (shell->priv->right_sidebar_container)); 3799 shell->priv->right_sidebar_widget_count++; 3800 default: 3801 box = rb_shell_get_box_for_ui_location (shell, location); 3802 g_return_if_fail (box != NULL); 3803 3804 gtk_box_pack_start (box, widget, expand, fill, 0); 3805 break; 3806 } 3807 } 3808 3809 /** 3810 * rb_shell_remove_widget: 3811 * @shell: the #RBShell 3812 * @widget: the #GtkWidget to remove from the main window 3813 * @location: the UI location to which the widget was originally added 3814 * 3815 * Removes a widget added with #rb_shell_add_widget from the main window. 3816 */ 3817 void 3818 rb_shell_remove_widget (RBShell *shell, GtkWidget *widget, RBShellUILocation location) 3819 { 3820 GtkBox *box; 3821 3822 switch (location) { 3823 case RB_SHELL_UI_LOCATION_RIGHT_SIDEBAR: 3824 shell->priv->right_sidebar_widget_count--; 3825 if (!shell->priv->right_sidebar_widget_count) 3826 gtk_widget_hide (GTK_WIDGET (shell->priv->right_sidebar_container)); 3827 default: 3828 box = rb_shell_get_box_for_ui_location (shell, location); 3829 g_return_if_fail (box != NULL); 3830 3831 gtk_container_remove (GTK_CONTAINER (box), widget); 3832 break; 3833 } 3834 } 3835 3836 /* This should really be standard. */ 3837 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } 3838 3839 GType 3840 rb_shell_activation_type_get_type (void) 3841 { 3842 static GType etype = 0; 3843 3844 if (etype == 0) { 3845 static const GEnumValue values[] = { 3846 ENUM_ENTRY (RB_SHELL_ACTIVATION_SELECT, "select"), 3847 ENUM_ENTRY (RB_SHELL_ACTIVATION_PLAY, "play"), 3848 ENUM_ENTRY (RB_SHELL_ACTIVATION_ALWAYS_PLAY, "always-play"), 3849 { 0, 0, 0 } 3850 }; 3851 3852 etype = g_enum_register_static ("RBShellActivationType", values); 3853 } 3854 3855 return etype; 3856 } 3857 3858 GType 3859 rb_shell_ui_location_get_type (void) 3860 { 3861 static GType etype = 0; 3862 3863 if (etype == 0) { 3864 static const GEnumValue values[] = { 3865 ENUM_ENTRY (RB_SHELL_UI_LOCATION_SIDEBAR, "sidebar"), 3866 ENUM_ENTRY (RB_SHELL_UI_LOCATION_RIGHT_SIDEBAR, "right-sidebar"), 3867 ENUM_ENTRY (RB_SHELL_UI_LOCATION_MAIN_TOP, "main-top"), 3868 ENUM_ENTRY (RB_SHELL_UI_LOCATION_MAIN_BOTTOM, "main-bottom"), 3869 { 0, 0, 0 } 3870 }; 3871 3872 etype = g_enum_register_static ("RBShellUILocation", values); 3873 } 3874 3875 return etype; 3876 } 3877 3878 GType 3879 rb_shell_error_get_type (void) 3880 { 3881 static GType etype = 0; 3882 3883 if (etype == 0) { 3884 static const GEnumValue values[] = { 3885 ENUM_ENTRY(RB_SHELL_ERROR_NO_SUCH_URI, "no-such-uri"), 3886 ENUM_ENTRY(RB_SHELL_ERROR_NO_SUCH_PROPERTY, "no-such-property"), 3887 ENUM_ENTRY(RB_SHELL_ERROR_IMMUTABLE_PROPERTY, "immutable-property"), 3888 ENUM_ENTRY(RB_SHELL_ERROR_INVALID_PROPERTY_TYPE, "invalid-property-type"), 3889 ENUM_ENTRY(RB_SHELL_ERROR_NO_SOURCE_FOR_URI, "no-source-for-uri"), 3890 { 0, 0, 0 } 3891 }; 3892 etype = g_enum_register_static ("RBShellErrorType", values); 3893 } 3894 3895 return etype; 3896 }