1 /******************************************************************************
2 * $Id: main.c 13237 2012-02-24 19:52:50Z jordan $
4 * Copyright (c) Transmission authors and contributors
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
29 #include <stdlib.h> /* exit() */
32 #include <glib/gi18n.h>
33 #include <glib/gstdio.h>
37 #include <libtransmission/transmission.h>
38 #include <libtransmission/rpcimpl.h>
39 #include <libtransmission/utils.h>
40 #include <libtransmission/version.h>
47 #include "makemeta-ui.h"
50 #include "open-dialog.h"
56 #include "tr-window.h"
60 #define MY_CONFIG_NAME "transmission"
61 #define MY_READABLE_NAME "transmission-gtk"
64 static const char * LICENSE
=
65 "The OS X client, CLI client, and parts of libtransmission are licensed under the terms of the MIT license.\n\n"
66 "The Transmission daemon, GTK+ client, Qt client, Web client, and most of libtransmission are licensed under the terms of the GNU GPL version 2, with two special exceptions:\n\n"
67 "1. The MIT-licensed portions of Transmission listed above are exempt from GPLv2 clause 2(b) and may retain their MIT license.\n\n"
68 "2. Permission is granted to link the code in this release with the OpenSSL project's 'OpenSSL' library and to distribute the linked executables. Works derived from Transmission may, at their authors' discretion, keep or delete this exception.";
73 gboolean start_paused
;
74 gboolean is_iconified
;
76 guint activation_count
;
78 guint update_model_soon_tag
;
79 guint refresh_actions_tag
;
86 GSList
* duplicates_list
;
88 GtkTreeSelection
* sel
;
93 gtr_window_present( GtkWindow
* window
)
95 gtk_window_present_with_time( window
, gtk_get_current_event_time( ) );
100 **** DETAILS DIALOGS MANAGEMENT
105 compare_integers( gconstpointer a
, gconstpointer b
)
107 return GPOINTER_TO_INT(a
) - GPOINTER_TO_INT(b
);
111 get_details_dialog_key( GSList
* id_list
)
114 GSList
* tmp
= g_slist_sort( g_slist_copy( id_list
), compare_integers
);
115 GString
* gstr
= g_string_new( NULL
);
117 for( l
=tmp
; l
!=NULL
; l
=l
->next
)
118 g_string_append_printf( gstr
, "%d ", GPOINTER_TO_INT(l
->data
) );
121 return g_string_free( gstr
, FALSE
);
125 get_selected_torrent_ids_foreach( GtkTreeModel
* model
,
126 GtkTreePath
* p UNUSED
,
131 GSList
** ids
= gdata
;
132 gtk_tree_model_get( model
, iter
, MC_TORRENT_ID
, &id
, -1 );
133 *ids
= g_slist_append( *ids
, GINT_TO_POINTER( id
) );
136 get_selected_torrent_ids( struct cbdata
* data
)
139 gtk_tree_selection_selected_foreach( data
->sel
,
140 get_selected_torrent_ids_foreach
,
146 on_details_dialog_closed( gpointer gdata
, GObject
* dead
)
148 struct cbdata
* data
= gdata
;
150 data
->details
= g_slist_remove( data
->details
, dead
);
154 show_details_dialog_for_selected_torrents( struct cbdata
* data
)
156 GtkWidget
* dialog
= NULL
;
158 GSList
* ids
= get_selected_torrent_ids( data
);
159 char * key
= get_details_dialog_key( ids
);
161 for( l
=data
->details
; dialog
==NULL
&& l
!=NULL
; l
=l
->next
)
162 if( !strcmp( key
, g_object_get_data( l
->data
, "key" ) ) )
167 dialog
= gtr_torrent_details_dialog_new( GTK_WINDOW( data
->wind
), data
->core
);
168 gtr_torrent_details_dialog_set_torrents( dialog
, ids
);
169 g_object_set_data_full( G_OBJECT( dialog
), "key", g_strdup( key
), g_free
);
170 g_object_weak_ref( G_OBJECT( dialog
), on_details_dialog_closed
, data
);
171 data
->details
= g_slist_append( data
->details
, dialog
);
172 gtk_widget_show( dialog
);
175 gtr_window_present( GTK_WINDOW( dialog
) );
182 ***** ON SELECTION CHANGED
194 get_selected_torrent_counts_foreach( GtkTreeModel
* model
, GtkTreePath
* path UNUSED
,
195 GtkTreeIter
* iter
, gpointer user_data
)
198 struct counts_data
* counts
= user_data
;
200 ++counts
->total_count
;
202 gtk_tree_model_get( model
, iter
, MC_ACTIVITY
, &activity
, -1 );
204 if( ( activity
== TR_STATUS_DOWNLOAD_WAIT
) || ( activity
== TR_STATUS_SEED_WAIT
) )
205 ++counts
->queued_count
;
207 if( activity
== TR_STATUS_STOPPED
)
208 ++counts
->stopped_count
;
212 get_selected_torrent_counts( struct cbdata
* data
, struct counts_data
* counts
)
214 counts
->total_count
= 0;
215 counts
->queued_count
= 0;
216 counts
->stopped_count
= 0;
218 gtk_tree_selection_selected_foreach( data
->sel
, get_selected_torrent_counts_foreach
, counts
);
222 count_updatable_foreach( GtkTreeModel
* model
, GtkTreePath
* path UNUSED
,
223 GtkTreeIter
* iter
, gpointer accumulated_status
)
226 gtk_tree_model_get( model
, iter
, MC_TORRENT
, &tor
, -1 );
227 *(int*)accumulated_status
|= tr_torrentCanManualUpdate( tor
);
231 refresh_actions( gpointer gdata
)
234 struct counts_data sel_counts
;
235 struct cbdata
* data
= gdata
;
236 const size_t total
= gtr_core_get_torrent_count( data
->core
);
237 const size_t active
= gtr_core_get_active_torrent_count( data
->core
);
238 const int torrent_count
= gtk_tree_model_iter_n_children( gtr_core_model( data
->core
), NULL
);
241 get_selected_torrent_counts( data
, &sel_counts
);
242 has_selection
= sel_counts
.total_count
> 0;
244 gtr_action_set_sensitive( "select-all", torrent_count
!= 0 );
245 gtr_action_set_sensitive( "deselect-all", torrent_count
!= 0 );
246 gtr_action_set_sensitive( "pause-all-torrents", active
!= 0 );
247 gtr_action_set_sensitive( "start-all-torrents", active
!= total
);
249 gtr_action_set_sensitive( "torrent-stop", ( sel_counts
.stopped_count
< sel_counts
.total_count
) );
250 gtr_action_set_sensitive( "torrent-start", ( sel_counts
.stopped_count
) > 0 );
251 gtr_action_set_sensitive( "torrent-start-now", ( sel_counts
.stopped_count
+ sel_counts
.queued_count
) > 0 );
252 gtr_action_set_sensitive( "torrent-verify", has_selection
);
253 gtr_action_set_sensitive( "remove-torrent", has_selection
);
254 gtr_action_set_sensitive( "delete-torrent", has_selection
);
255 gtr_action_set_sensitive( "relocate-torrent", has_selection
);
256 gtr_action_set_sensitive( "queue-move-top", has_selection
);
257 gtr_action_set_sensitive( "queue-move-up", has_selection
);
258 gtr_action_set_sensitive( "queue-move-down", has_selection
);
259 gtr_action_set_sensitive( "queue-move-bottom", has_selection
);
260 gtr_action_set_sensitive( "show-torrent-properties", has_selection
);
261 gtr_action_set_sensitive( "open-torrent-folder", sel_counts
.total_count
== 1 );
262 gtr_action_set_sensitive( "copy-magnet-link-to-clipboard", sel_counts
.total_count
== 1 );
265 gtk_tree_selection_selected_foreach( data
->sel
, count_updatable_foreach
, &canUpdate
);
266 gtr_action_set_sensitive( "torrent-reannounce", canUpdate
!= 0 );
268 data
->refresh_actions_tag
= 0;
273 refresh_actions_soon( gpointer gdata
)
275 struct cbdata
* data
= gdata
;
277 if( data
->refresh_actions_tag
== 0 )
278 data
->refresh_actions_tag
= gdk_threads_add_idle( refresh_actions
, data
);
282 on_selection_changed( GtkTreeSelection
* s UNUSED
, gpointer gdata
)
284 refresh_actions_soon( gdata
);
292 register_magnet_link_handler( void )
294 GAppInfo
* app_info
= g_app_info_get_default_for_uri_scheme( "magnet" );
295 if( app_info
== NULL
)
297 /* there's no default magnet handler, so register ourselves for the job... */
298 GError
* error
= NULL
;
299 app_info
= g_app_info_create_from_commandline( "transmission-gtk", "transmission-gtk", G_APP_INFO_CREATE_SUPPORTS_URIS
, NULL
);
300 g_app_info_set_as_default_for_type( app_info
, "x-scheme-handler/magnet", &error
);
303 g_warning( _( "Error registering Transmission as x-scheme-handler/magnet handler: %s" ), error
->message
);
304 g_clear_error( &error
);
310 on_main_window_size_allocated( GtkWidget
* gtk_window
,
311 GtkAllocation
* alloc UNUSED
,
312 gpointer gdata UNUSED
)
314 GdkWindow
* gdk_window
= gtk_widget_get_window( gtk_window
);
315 const gboolean isMaximized
= ( gdk_window
!= NULL
)
316 && ( gdk_window_get_state( gdk_window
) & GDK_WINDOW_STATE_MAXIMIZED
);
318 gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_IS_MAXIMIZED
, isMaximized
);
323 gtk_window_get_position( GTK_WINDOW( gtk_window
), &x
, &y
);
324 gtk_window_get_size( GTK_WINDOW( gtk_window
), &w
, &h
);
325 gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_X
, x
);
326 gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_Y
, y
);
327 gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_WIDTH
, w
);
328 gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_HEIGHT
, h
);
333 **** listen to changes that come from RPC
340 gboolean delete_files
;
344 rpc_torrent_remove_idle( gpointer gdata
)
346 struct rpc_idle_data
* data
= gdata
;
348 gtr_core_remove_torrent( data
->core
, data
->id
, data
->delete_files
);
351 return FALSE
; /* tell g_idle not to call this func twice */
355 rpc_torrent_add_idle( gpointer gdata
)
358 struct rpc_idle_data
* data
= gdata
;
360 if(( tor
= gtr_core_find_torrent( data
->core
, data
->id
)))
361 gtr_core_add_torrent( data
->core
, tor
, TRUE
);
364 return FALSE
; /* tell g_idle not to call this func twice */
367 static tr_rpc_callback_status
368 on_rpc_changed( tr_session
* session
,
369 tr_rpc_callback_type type
,
370 struct tr_torrent
* tor
,
373 tr_rpc_callback_status status
= TR_RPC_OK
;
374 struct cbdata
* cbdata
= gdata
;
375 gdk_threads_enter( );
379 case TR_RPC_SESSION_CLOSE
:
380 gtr_action_activate( "quit" );
383 case TR_RPC_TORRENT_ADDED
: {
384 struct rpc_idle_data
* data
= g_new0( struct rpc_idle_data
, 1 );
385 data
->id
= tr_torrentId( tor
);
386 data
->core
= cbdata
->core
;
387 gdk_threads_add_idle( rpc_torrent_add_idle
, data
);
391 case TR_RPC_TORRENT_REMOVING
:
392 case TR_RPC_TORRENT_TRASHING
: {
393 struct rpc_idle_data
* data
= g_new0( struct rpc_idle_data
, 1 );
394 data
->id
= tr_torrentId( tor
);
395 data
->core
= cbdata
->core
;
396 data
->delete_files
= type
== TR_RPC_TORRENT_TRASHING
;
397 gdk_threads_add_idle( rpc_torrent_remove_idle
, data
);
398 status
= TR_RPC_NOREMOVE
;
402 case TR_RPC_SESSION_CHANGED
: {
406 tr_benc
* oldvals
= gtr_pref_get_all( );
409 GSList
* changed_keys
= NULL
;
410 tr_bencInitDict( &tmp
, 100 );
411 tr_sessionGetSettings( session
, &tmp
);
412 for( i
=0; tr_bencDictChild( &tmp
, i
, &key
, &newval
); ++i
)
415 tr_benc
* oldval
= tr_bencDictFind( oldvals
, key
);
419 char * a
= tr_bencToStr( oldval
, TR_FMT_BENC
, NULL
);
420 char * b
= tr_bencToStr( newval
, TR_FMT_BENC
, NULL
);
421 changed
= strcmp( a
, b
) != 0;
427 changed_keys
= g_slist_append( changed_keys
, (gpointer
)key
);
429 tr_sessionGetSettings( session
, oldvals
);
431 for( l
=changed_keys
; l
!=NULL
; l
=l
->next
)
432 gtr_core_pref_changed( cbdata
->core
, l
->data
);
434 g_slist_free( changed_keys
);
439 case TR_RPC_TORRENT_CHANGED
:
440 case TR_RPC_TORRENT_MOVED
:
441 case TR_RPC_TORRENT_STARTED
:
442 case TR_RPC_TORRENT_STOPPED
:
443 /* nothing interesting to do here */
447 gdk_threads_leave( );
455 static sig_atomic_t global_sigcount
= 0;
456 static struct cbdata
* sighandler_cbdata
= NULL
;
459 signal_handler( int sig
)
461 if( ++global_sigcount
> 1 )
463 signal( sig
, SIG_DFL
);
466 else if(( sig
== SIGINT
) || ( sig
== SIGTERM
))
468 g_message( _( "Got signal %d; trying to shut down cleanly. Do it again if it gets stuck." ), sig
);
469 gtr_actions_handler( "quit", sighandler_cbdata
);
478 static void app_setup( TrWindow
* wind
, struct cbdata
* cbdata
);
481 on_startup( GApplication
* application
, gpointer user_data
)
485 GtkUIManager
* ui_manager
;
486 tr_session
* session
;
487 struct cbdata
* cbdata
= user_data
;
489 signal( SIGINT
, signal_handler
);
490 signal( SIGTERM
, signal_handler
);
492 sighandler_cbdata
= cbdata
;
494 /* ensure the directories are created */
495 if(( str
= gtr_pref_string_get( TR_PREFS_KEY_DOWNLOAD_DIR
)))
496 g_mkdir_with_parents( str
, 0777 );
497 if(( str
= gtr_pref_string_get( TR_PREFS_KEY_INCOMPLETE_DIR
)))
498 g_mkdir_with_parents( str
, 0777 );
500 /* initialize the libtransmission session */
501 session
= tr_sessionInit( "gtk", cbdata
->config_dir
, TRUE
, gtr_pref_get_all( ) );
503 gtr_pref_flag_set( TR_PREFS_KEY_ALT_SPEED_ENABLED
, tr_sessionUsesAltSpeed( session
) );
504 gtr_pref_int_set( TR_PREFS_KEY_PEER_PORT
, tr_sessionGetPeerPort( session
) );
505 cbdata
->core
= gtr_core_new( session
);
507 /* init the ui manager */
508 ui_manager
= gtk_ui_manager_new ( );
509 gtr_actions_init ( ui_manager
, cbdata
);
510 gtk_ui_manager_add_ui_from_string ( ui_manager
, fallback_ui_file
, -1, NULL
);
511 gtk_ui_manager_ensure_update ( ui_manager
);
513 /* create main window now to be a parent to any error dialogs */
514 win
= GTK_WINDOW( gtr_window_new( ui_manager
, cbdata
->core
) );
515 g_signal_connect( win
, "size-allocate", G_CALLBACK( on_main_window_size_allocated
), cbdata
);
516 g_application_hold( application
);
517 g_object_weak_ref( G_OBJECT( win
), (GWeakNotify
)g_application_release
, application
);
518 app_setup( win
, cbdata
);
519 tr_sessionSetRPCCallback( session
, on_rpc_changed
, cbdata
);
521 /* check & see if it's time to update the blocklist */
522 if( gtr_pref_flag_get( TR_PREFS_KEY_BLOCKLIST_ENABLED
) ) {
523 if( gtr_pref_flag_get( PREF_KEY_BLOCKLIST_UPDATES_ENABLED
) ) {
524 const int64_t last_time
= gtr_pref_int_get( "blocklist-date" );
525 const int SECONDS_IN_A_WEEK
= 7 * 24 * 60 * 60;
526 const time_t now
= time( NULL
);
527 if( last_time
+ SECONDS_IN_A_WEEK
< now
)
528 gtr_core_blocklist_update( cbdata
->core
);
532 /* if there's no magnet link handler registered, register us */
533 register_magnet_link_handler( );
537 on_activate( GApplication
* app UNUSED
, struct cbdata
* cbdata
)
539 cbdata
->activation_count
++;
541 /* GApplication emits an 'activate' signal when bootstrapping the primary.
542 * Ordinarily we handle that by presenting the main window, but if the user
543 * user started Transmission minimized, ignore that initial signal... */
544 if( cbdata
->is_iconified
&& ( cbdata
->activation_count
== 1 ) )
547 gtr_action_activate( "present-main-window" );
551 open_files( GSList
* files
, gpointer gdata
)
553 struct cbdata
* cbdata
= gdata
;
554 const gboolean do_start
= gtr_pref_flag_get( TR_PREFS_KEY_START
) && !cbdata
->start_paused
;
555 const gboolean do_prompt
= gtr_pref_flag_get( PREF_KEY_OPTIONS_PROMPT
);
556 const gboolean do_notify
= TRUE
;
558 gtr_core_add_files( cbdata
->core
, files
, do_start
, do_prompt
, do_notify
);
562 on_open (GApplication
* application UNUSED
,
569 GSList
* files
= NULL
;
571 for( i
=0; i
<file_count
; ++i
)
572 files
= g_slist_prepend( files
, f
[i
] );
574 open_files( files
, gdata
);
576 g_slist_free( files
);
584 main( int argc
, char ** argv
)
588 char * application_id
;
590 GOptionContext
* option_context
;
591 bool show_version
= false;
592 GError
* error
= NULL
;
593 struct cbdata cbdata
;
595 GOptionEntry option_entries
[] = {
596 { "config-dir", 'g', 0, G_OPTION_ARG_FILENAME
, &cbdata
.config_dir
, _( "Where to look for configuration files" ), NULL
},
597 { "paused", 'p', 0, G_OPTION_ARG_NONE
, &cbdata
.start_paused
, _( "Start with all torrents paused" ), NULL
},
598 { "minimized", 'm', 0, G_OPTION_ARG_NONE
, &cbdata
.is_iconified
, _( "Start minimized in notification area" ), NULL
},
599 { "version", 'v', 0, G_OPTION_ARG_NONE
, &show_version
, _( "Show version number and exit" ), NULL
},
600 { NULL
, 0, 0, 0, NULL
, NULL
, NULL
}
603 /* default settings */
604 memset( &cbdata
, 0, sizeof( struct cbdata
) );
605 cbdata
.config_dir
= (char*) tr_getDefaultConfigDir( MY_CONFIG_NAME
);
608 setlocale( LC_ALL
, "" );
609 bindtextdomain( MY_READABLE_NAME
, TRANSMISSIONLOCALEDIR
);
610 bind_textdomain_codeset( MY_READABLE_NAME
, "UTF-8" );
611 textdomain( MY_READABLE_NAME
);
614 g_thread_init (NULL
);
616 gtk_init (&argc
, &argv
);
617 g_set_application_name (_( "Transmission" ));
618 gtk_window_set_default_icon_name (MY_CONFIG_NAME
);
620 /* parse the command line */
621 option_context
= g_option_context_new( _( "[torrent files or urls]" ) );
622 g_option_context_add_main_entries( option_context
, option_entries
, GETTEXT_PACKAGE
);
623 g_option_context_set_translation_domain( option_context
, GETTEXT_PACKAGE
);
624 if( !g_option_context_parse( option_context
, &argc
, &argv
, &error
) ) {
625 g_print (_("%s\nRun '%s --help' to see a full list of available command line options.\n"), error
->message
, argv
[0]);
626 g_error_free (error
);
627 g_option_context_free (option_context
);
630 g_option_context_free (option_context
);
632 /* handle the trivial "version" option */
634 fprintf( stderr
, "%s %s\n", MY_READABLE_NAME
, LONG_VERSION_STRING
);
638 /* init the unit formatters */
639 tr_formatter_mem_init( mem_K
, _(mem_K_str
), _(mem_M_str
), _(mem_G_str
), _(mem_T_str
) );
640 tr_formatter_size_init( disk_K
, _(disk_K_str
), _(disk_M_str
), _(disk_G_str
), _(disk_T_str
) );
641 tr_formatter_speed_init( speed_K
, _(speed_K_str
), _(speed_M_str
), _(speed_G_str
), _(speed_T_str
) );
643 /* set up the config dir */
644 gtr_pref_init( cbdata
.config_dir
);
645 g_mkdir_with_parents( cbdata
.config_dir
, 0755 );
647 /* init notifications */
650 /* init the application for the specified config dir */
651 stat( cbdata
.config_dir
, &sb
);
652 application_id
= g_strdup_printf( "com.transmissionbt.transmission_%lu_%lu", (unsigned long)sb
.st_dev
, (unsigned long)sb
.st_ino
);
653 app
= g_application_new( application_id
, G_APPLICATION_HANDLES_OPEN
);
654 g_signal_connect( app
, "open", G_CALLBACK(on_open
), &cbdata
);
655 g_signal_connect( app
, "startup", G_CALLBACK(on_startup
), &cbdata
);
656 g_signal_connect( app
, "activate", G_CALLBACK(on_activate
), &cbdata
);
657 ret
= g_application_run (app
, argc
, argv
);
658 g_object_unref( app
);
659 g_free( application_id
);
664 on_core_busy( TrCore
* core UNUSED
, gboolean busy
, struct cbdata
* c
)
666 gtr_window_set_busy( c
->wind
, busy
);
669 static void on_core_error( TrCore
*, guint
, const char *, struct cbdata
* );
670 static void on_add_torrent( TrCore
*, tr_ctor
*, gpointer
);
671 static void on_prefs_changed( TrCore
* core
, const char * key
, gpointer
);
672 static void main_window_setup( struct cbdata
* cbdata
, TrWindow
* wind
);
673 static gboolean
update_model_loop( gpointer gdata
);
674 static gboolean
update_model_once( gpointer gdata
);
677 app_setup( TrWindow
* wind
, struct cbdata
* cbdata
)
679 if( cbdata
->is_iconified
)
680 gtr_pref_flag_set( PREF_KEY_SHOW_TRAY_ICON
, TRUE
);
682 gtr_actions_set_core( cbdata
->core
);
684 /* set up core handlers */
685 g_signal_connect( cbdata
->core
, "busy", G_CALLBACK( on_core_busy
), cbdata
);
686 g_signal_connect( cbdata
->core
, "add-error", G_CALLBACK( on_core_error
), cbdata
);
687 g_signal_connect( cbdata
->core
, "add-prompt", G_CALLBACK( on_add_torrent
), cbdata
);
688 g_signal_connect( cbdata
->core
, "prefs-changed", G_CALLBACK( on_prefs_changed
), cbdata
);
690 /* add torrents from command-line and saved state */
691 gtr_core_load( cbdata
->core
, cbdata
->start_paused
);
692 gtr_core_torrents_added( cbdata
->core
);
694 /* set up main window */
695 main_window_setup( cbdata
, wind
);
697 /* set up the icon */
698 on_prefs_changed( cbdata
->core
, PREF_KEY_SHOW_TRAY_ICON
, cbdata
);
700 /* start model update timer */
701 cbdata
->timer
= gdk_threads_add_timeout_seconds( MAIN_WINDOW_REFRESH_INTERVAL_SECONDS
, update_model_loop
, cbdata
);
702 update_model_once( cbdata
);
704 /* either show the window or iconify it */
705 if( !cbdata
->is_iconified
)
706 gtk_widget_show( GTK_WIDGET( wind
) );
709 gtk_window_set_skip_taskbar_hint( cbdata
->wind
,
710 cbdata
->icon
!= NULL
);
711 cbdata
->is_iconified
= FALSE
; // ensure that the next toggle iconifies
712 gtr_action_set_toggled( "toggle-main-window", FALSE
);
715 if( !gtr_pref_flag_get( PREF_KEY_USER_HAS_GIVEN_INFORMED_CONSENT
) )
717 GtkWidget
* w
= gtk_message_dialog_new( GTK_WINDOW( wind
),
718 GTK_DIALOG_DESTROY_WITH_PARENT
,
722 _( "Transmission is a file-sharing program. When you run a torrent, its data will be made available to others by means of upload. You and you alone are fully responsible for exercising proper judgement and abiding by your local laws." ) );
723 gtk_dialog_add_button( GTK_DIALOG( w
), GTK_STOCK_QUIT
, GTK_RESPONSE_REJECT
);
724 gtk_dialog_add_button( GTK_DIALOG( w
), _( "I _Accept" ), GTK_RESPONSE_ACCEPT
);
725 gtk_dialog_set_default_response( GTK_DIALOG( w
), GTK_RESPONSE_ACCEPT
);
726 switch( gtk_dialog_run( GTK_DIALOG( w
) ) ) {
727 case GTK_RESPONSE_ACCEPT
:
728 /* only show it once */
729 gtr_pref_flag_set( PREF_KEY_USER_HAS_GIVEN_INFORMED_CONSENT
, TRUE
);
730 gtk_widget_destroy( w
);
739 presentMainWindow( struct cbdata
* cbdata
)
741 GtkWindow
* window
= cbdata
->wind
;
743 if( cbdata
->is_iconified
)
745 cbdata
->is_iconified
= false;
747 gtk_window_set_skip_taskbar_hint( window
, FALSE
);
750 if( !gtk_widget_get_visible( GTK_WIDGET( window
) ) )
752 gtk_window_resize( window
, gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_WIDTH
),
753 gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_HEIGHT
) );
754 gtk_window_move( window
, gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_X
),
755 gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_Y
) );
756 gtr_widget_set_visible( GTK_WIDGET( window
), TRUE
);
758 gtr_window_present( window
);
762 hideMainWindow( struct cbdata
* cbdata
)
764 GtkWindow
* window
= cbdata
->wind
;
765 gtk_window_set_skip_taskbar_hint( window
, TRUE
);
766 gtr_widget_set_visible( GTK_WIDGET( window
), FALSE
);
767 cbdata
->is_iconified
= true;
771 toggleMainWindow( struct cbdata
* cbdata
)
773 if( cbdata
->is_iconified
)
774 presentMainWindow( cbdata
);
776 hideMainWindow( cbdata
);
779 static void on_app_exit( gpointer vdata
);
782 winclose( GtkWidget
* w UNUSED
,
783 GdkEvent
* event UNUSED
,
786 struct cbdata
* cbdata
= gdata
;
788 if( cbdata
->icon
!= NULL
)
789 gtr_action_activate ( "toggle-main-window" );
791 on_app_exit( cbdata
);
793 return TRUE
; /* don't propagate event further */
797 rowChangedCB( GtkTreeModel
* model UNUSED
,
799 GtkTreeIter
* iter UNUSED
,
802 struct cbdata
* data
= gdata
;
804 if( gtk_tree_selection_path_is_selected ( data
->sel
, path
) )
805 refresh_actions_soon( data
);
809 on_drag_data_received( GtkWidget
* widget UNUSED
,
810 GdkDragContext
* drag_context
,
813 GtkSelectionData
* selection_data
,
819 char ** uris
= gtk_selection_data_get_uris( selection_data
);
820 const guint file_count
= g_strv_length( uris
);
821 GSList
* files
= NULL
;
823 for( i
=0; i
<file_count
; ++i
)
824 files
= g_slist_prepend( files
, g_file_new_for_uri( uris
[i
] ) );
826 open_files( files
, gdata
);
829 g_slist_foreach( files
, (GFunc
)g_object_unref
, NULL
);
830 g_slist_free( files
);
833 gtk_drag_finish( drag_context
, true, FALSE
, time_
);
837 main_window_setup( struct cbdata
* cbdata
, TrWindow
* wind
)
840 GtkTreeModel
* model
;
841 GtkTreeSelection
* sel
;
843 g_assert( NULL
== cbdata
->wind
);
844 cbdata
->wind
= GTK_WINDOW( wind
);
845 cbdata
->sel
= sel
= GTK_TREE_SELECTION( gtr_window_get_selection( cbdata
->wind
) );
847 g_signal_connect( sel
, "changed", G_CALLBACK( on_selection_changed
), cbdata
);
848 on_selection_changed( sel
, cbdata
);
849 model
= gtr_core_model( cbdata
->core
);
850 g_signal_connect( model
, "row-changed", G_CALLBACK( rowChangedCB
), cbdata
);
851 g_signal_connect( wind
, "delete-event", G_CALLBACK( winclose
), cbdata
);
852 refresh_actions( cbdata
);
854 /* register to handle URIs that get dragged onto our main window */
855 w
= GTK_WIDGET( wind
);
856 gtk_drag_dest_set( w
, GTK_DEST_DEFAULT_ALL
, NULL
, 0, GDK_ACTION_COPY
);
857 gtk_drag_dest_add_uri_targets( w
);
858 g_signal_connect( w
, "drag-data-received", G_CALLBACK(on_drag_data_received
), cbdata
);
862 on_session_closed( gpointer gdata
)
865 struct cbdata
* cbdata
= gdata
;
867 tmp
= g_slist_copy( cbdata
->details
);
868 g_slist_foreach( tmp
, (GFunc
)gtk_widget_destroy
, NULL
);
872 gtk_widget_destroy( GTK_WIDGET( cbdata
->prefs
) );
874 gtk_widget_destroy( GTK_WIDGET( cbdata
->wind
) );
875 g_object_unref( cbdata
->core
);
877 g_object_unref( cbdata
->icon
);
878 g_slist_foreach( cbdata
->error_list
, (GFunc
)g_free
, NULL
);
879 g_slist_free( cbdata
->error_list
);
880 g_slist_foreach( cbdata
->duplicates_list
, (GFunc
)g_free
, NULL
);
881 g_slist_free( cbdata
->duplicates_list
);
887 session_close_threadfunc( gpointer gdata
)
889 /* since tr_sessionClose() is a blocking function,
890 * call it from another thread... when it's done,
891 * punt the GUI teardown back to the GTK+ thread */
892 struct cbdata
* cbdata
= gdata
;
893 gdk_threads_enter( );
894 gtr_core_close( cbdata
->core
);
895 gdk_threads_add_idle( on_session_closed
, gdata
);
896 gdk_threads_leave( );
901 exit_now_cb( GtkWidget
*w UNUSED
, gpointer data UNUSED
)
907 on_app_exit( gpointer vdata
)
909 GtkWidget
*r
, *p
, *b
, *w
, *c
;
910 struct cbdata
*cbdata
= vdata
;
912 /* stop the update timer */
915 g_source_remove( cbdata
->timer
);
919 c
= GTK_WIDGET( cbdata
->wind
);
920 gtk_container_remove( GTK_CONTAINER( c
), gtk_bin_get_child( GTK_BIN( c
) ) );
922 r
= gtk_alignment_new( 0.5, 0.5, 0.01, 0.01 );
923 gtk_container_add( GTK_CONTAINER( c
), r
);
925 p
= gtk_table_new( 3, 2, FALSE
);
926 gtk_table_set_col_spacings( GTK_TABLE( p
), GUI_PAD_BIG
);
927 gtk_container_add( GTK_CONTAINER( r
), p
);
929 w
= gtk_image_new_from_stock( GTK_STOCK_NETWORK
, GTK_ICON_SIZE_DIALOG
);
930 gtk_table_attach_defaults( GTK_TABLE( p
), w
, 0, 1, 0, 2 );
932 w
= gtk_label_new( NULL
);
933 gtk_label_set_markup( GTK_LABEL( w
), _( "<b>Closing Connections</b>" ) );
934 gtk_misc_set_alignment( GTK_MISC( w
), 0.0, 0.5 );
935 gtk_table_attach_defaults( GTK_TABLE( p
), w
, 1, 2, 0, 1 );
937 w
= gtk_label_new( _( "Sending upload/download totals to tracker…" ) );
938 gtk_misc_set_alignment( GTK_MISC( w
), 0.0, 0.5 );
939 gtk_table_attach_defaults( GTK_TABLE( p
), w
, 1, 2, 1, 2 );
941 b
= gtk_alignment_new( 0.0, 1.0, 0.01, 0.01 );
942 w
= gtk_button_new_with_mnemonic( _( "_Quit Now" ) );
943 g_signal_connect( w
, "clicked", G_CALLBACK( exit_now_cb
), NULL
);
944 gtk_container_add( GTK_CONTAINER( b
), w
);
945 gtk_table_attach( GTK_TABLE( p
), b
, 1, 2, 2, 3, GTK_FILL
, GTK_FILL
, 0, 10 );
947 gtk_widget_show_all( r
);
948 gtk_widget_grab_focus( w
);
951 gtr_core_clear( cbdata
->core
);
953 /* ensure the window is in its previous position & size.
954 * this seems to be necessary because changing the main window's
955 * child seems to unset the size */
956 gtk_window_resize( cbdata
->wind
, gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_WIDTH
),
957 gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_HEIGHT
) );
958 gtk_window_move( cbdata
->wind
, gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_X
),
959 gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_Y
) );
962 g_thread_create( session_close_threadfunc
, vdata
, TRUE
, NULL
);
966 show_torrent_errors( GtkWindow
* window
, const char * primary
, GSList
** files
)
970 GString
* s
= g_string_new( NULL
);
971 const char * leader
= g_slist_length( *files
) > 1
972 ? gtr_get_unicode_string( GTR_UNICODE_BULLET
)
975 for( l
=*files
; l
!=NULL
; l
=l
->next
)
976 g_string_append_printf( s
, "%s %s\n", leader
, (const char*)l
->data
);
978 w
= gtk_message_dialog_new( window
,
979 GTK_DIALOG_DESTROY_WITH_PARENT
,
983 gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w
),
985 g_signal_connect_swapped( w
, "response",
986 G_CALLBACK( gtk_widget_destroy
), w
);
987 gtk_widget_show( w
);
988 g_string_free( s
, TRUE
);
990 g_slist_foreach( *files
, (GFunc
)g_free
, NULL
);
991 g_slist_free( *files
);
996 flush_torrent_errors( struct cbdata
* cbdata
)
998 if( cbdata
->error_list
)
999 show_torrent_errors( cbdata
->wind
,
1000 ngettext( "Couldn't add corrupt torrent",
1001 "Couldn't add corrupt torrents",
1002 g_slist_length( cbdata
->error_list
) ),
1003 &cbdata
->error_list
);
1005 if( cbdata
->duplicates_list
)
1006 show_torrent_errors( cbdata
->wind
,
1007 ngettext( "Couldn't add duplicate torrent",
1008 "Couldn't add duplicate torrents",
1009 g_slist_length( cbdata
->duplicates_list
) ),
1010 &cbdata
->duplicates_list
);
1014 on_core_error( TrCore
* core UNUSED
, guint code
, const char * msg
, struct cbdata
* c
)
1020 g_slist_append( c
->error_list
, g_path_get_basename( msg
) );
1023 case TR_PARSE_DUPLICATE
:
1024 c
->duplicates_list
= g_slist_append( c
->duplicates_list
, g_strdup( msg
) );
1027 case TR_CORE_ERR_NO_MORE_TORRENTS
:
1028 flush_torrent_errors( c
);
1032 g_assert_not_reached( );
1038 on_main_window_focus_in( GtkWidget
* widget UNUSED
,
1039 GdkEventFocus
* event UNUSED
,
1042 struct cbdata
* cbdata
= gdata
;
1045 gtk_window_set_urgency_hint( cbdata
->wind
, FALSE
);
1050 on_add_torrent( TrCore
* core
, tr_ctor
* ctor
, gpointer gdata
)
1052 struct cbdata
* cbdata
= gdata
;
1053 GtkWidget
* w
= gtr_torrent_options_dialog_new( cbdata
->wind
, core
, ctor
);
1055 g_signal_connect( w
, "focus-in-event",
1056 G_CALLBACK( on_main_window_focus_in
), cbdata
);
1058 gtk_window_set_urgency_hint( cbdata
->wind
, TRUE
);
1060 gtk_widget_show( w
);
1064 on_prefs_changed( TrCore
* core UNUSED
, const char * key
, gpointer data
)
1066 struct cbdata
* cbdata
= data
;
1067 tr_session
* tr
= gtr_core_session( cbdata
->core
);
1069 if( !strcmp( key
, TR_PREFS_KEY_ENCRYPTION
) )
1071 tr_sessionSetEncryption( tr
, gtr_pref_int_get( key
) );
1073 else if( !strcmp( key
, TR_PREFS_KEY_DOWNLOAD_DIR
) )
1075 tr_sessionSetDownloadDir( tr
, gtr_pref_string_get( key
) );
1077 else if( !strcmp( key
, TR_PREFS_KEY_MSGLEVEL
) )
1079 tr_setMessageLevel( gtr_pref_int_get( key
) );
1081 else if( !strcmp( key
, TR_PREFS_KEY_PEER_PORT
) )
1083 tr_sessionSetPeerPort( tr
, gtr_pref_int_get( key
) );
1085 else if( !strcmp( key
, TR_PREFS_KEY_BLOCKLIST_ENABLED
) )
1087 tr_blocklistSetEnabled( tr
, gtr_pref_flag_get( key
) );
1089 else if( !strcmp( key
, TR_PREFS_KEY_BLOCKLIST_URL
) )
1091 tr_blocklistSetURL( tr
, gtr_pref_string_get( key
) );
1093 else if( !strcmp( key
, PREF_KEY_SHOW_TRAY_ICON
) )
1095 const int show
= gtr_pref_flag_get( key
);
1096 if( show
&& !cbdata
->icon
)
1097 cbdata
->icon
= gtr_icon_new( cbdata
->core
);
1098 else if( !show
&& cbdata
->icon
) {
1099 g_object_unref( cbdata
->icon
);
1100 cbdata
->icon
= NULL
;
1103 else if( !strcmp( key
, TR_PREFS_KEY_DSPEED_ENABLED
) )
1105 tr_sessionLimitSpeed( tr
, TR_DOWN
, gtr_pref_flag_get( key
) );
1107 else if( !strcmp( key
, TR_PREFS_KEY_DSPEED_KBps
) )
1109 tr_sessionSetSpeedLimit_KBps( tr
, TR_DOWN
, gtr_pref_int_get( key
) );
1111 else if( !strcmp( key
, TR_PREFS_KEY_USPEED_ENABLED
) )
1113 tr_sessionLimitSpeed( tr
, TR_UP
, gtr_pref_flag_get( key
) );
1115 else if( !strcmp( key
, TR_PREFS_KEY_USPEED_KBps
) )
1117 tr_sessionSetSpeedLimit_KBps( tr
, TR_UP
, gtr_pref_int_get( key
) );
1119 else if( !strcmp( key
, TR_PREFS_KEY_RATIO_ENABLED
) )
1121 tr_sessionSetRatioLimited( tr
, gtr_pref_flag_get( key
) );
1123 else if( !strcmp( key
, TR_PREFS_KEY_RATIO
) )
1125 tr_sessionSetRatioLimit( tr
, gtr_pref_double_get( key
) );
1127 else if( !strcmp( key
, TR_PREFS_KEY_IDLE_LIMIT
) )
1129 tr_sessionSetIdleLimit( tr
, gtr_pref_int_get( key
) );
1131 else if( !strcmp( key
, TR_PREFS_KEY_IDLE_LIMIT_ENABLED
) )
1133 tr_sessionSetIdleLimited( tr
, gtr_pref_flag_get( key
) );
1135 else if( !strcmp( key
, TR_PREFS_KEY_PORT_FORWARDING
) )
1137 tr_sessionSetPortForwardingEnabled( tr
, gtr_pref_flag_get( key
) );
1139 else if( !strcmp( key
, TR_PREFS_KEY_PEX_ENABLED
) )
1141 tr_sessionSetPexEnabled( tr
, gtr_pref_flag_get( key
) );
1143 else if( !strcmp( key
, TR_PREFS_KEY_RENAME_PARTIAL_FILES
) )
1145 tr_sessionSetIncompleteFileNamingEnabled( tr
, gtr_pref_flag_get( key
) );
1147 else if( !strcmp( key
, TR_PREFS_KEY_DOWNLOAD_QUEUE_SIZE
) )
1149 tr_sessionSetQueueSize( tr
, TR_DOWN
, gtr_pref_int_get( key
) );
1151 else if( !strcmp( key
, TR_PREFS_KEY_QUEUE_STALLED_MINUTES
) )
1153 tr_sessionSetQueueStalledMinutes( tr
, gtr_pref_int_get( key
) );
1155 else if( !strcmp( key
, TR_PREFS_KEY_DHT_ENABLED
) )
1157 tr_sessionSetDHTEnabled( tr
, gtr_pref_flag_get( key
) );
1159 else if( !strcmp( key
, TR_PREFS_KEY_UTP_ENABLED
) )
1161 tr_sessionSetUTPEnabled( tr
, gtr_pref_flag_get( key
) );
1163 else if( !strcmp( key
, TR_PREFS_KEY_LPD_ENABLED
) )
1165 tr_sessionSetLPDEnabled( tr
, gtr_pref_flag_get( key
) );
1167 else if( !strcmp( key
, TR_PREFS_KEY_RPC_PORT
) )
1169 tr_sessionSetRPCPort( tr
, gtr_pref_int_get( key
) );
1171 else if( !strcmp( key
, TR_PREFS_KEY_RPC_ENABLED
) )
1173 tr_sessionSetRPCEnabled( tr
, gtr_pref_flag_get( key
) );
1175 else if( !strcmp( key
, TR_PREFS_KEY_RPC_WHITELIST
) )
1177 tr_sessionSetRPCWhitelist( tr
, gtr_pref_string_get( key
) );
1179 else if( !strcmp( key
, TR_PREFS_KEY_RPC_WHITELIST_ENABLED
) )
1181 tr_sessionSetRPCWhitelistEnabled( tr
, gtr_pref_flag_get( key
) );
1183 else if( !strcmp( key
, TR_PREFS_KEY_RPC_USERNAME
) )
1185 tr_sessionSetRPCUsername( tr
, gtr_pref_string_get( key
) );
1187 else if( !strcmp( key
, TR_PREFS_KEY_RPC_PASSWORD
) )
1189 tr_sessionSetRPCPassword( tr
, gtr_pref_string_get( key
) );
1191 else if( !strcmp( key
, TR_PREFS_KEY_RPC_AUTH_REQUIRED
) )
1193 tr_sessionSetRPCPasswordEnabled( tr
, gtr_pref_flag_get( key
) );
1195 else if( !strcmp( key
, TR_PREFS_KEY_ALT_SPEED_UP_KBps
) )
1197 tr_sessionSetAltSpeed_KBps( tr
, TR_UP
, gtr_pref_int_get( key
) );
1199 else if( !strcmp( key
, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps
) )
1201 tr_sessionSetAltSpeed_KBps( tr
, TR_DOWN
, gtr_pref_int_get( key
) );
1203 else if( !strcmp( key
, TR_PREFS_KEY_ALT_SPEED_ENABLED
) )
1205 const gboolean b
= gtr_pref_flag_get( key
);
1206 tr_sessionUseAltSpeed( tr
, b
);
1207 gtr_action_set_toggled( key
, b
);
1209 else if( !strcmp( key
, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN
) )
1211 tr_sessionSetAltSpeedBegin( tr
, gtr_pref_int_get( key
) );
1213 else if( !strcmp( key
, TR_PREFS_KEY_ALT_SPEED_TIME_END
) )
1215 tr_sessionSetAltSpeedEnd( tr
, gtr_pref_int_get( key
) );
1217 else if( !strcmp( key
, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED
) )
1219 tr_sessionUseAltSpeedTime( tr
, gtr_pref_flag_get( key
) );
1221 else if( !strcmp( key
, TR_PREFS_KEY_ALT_SPEED_TIME_DAY
) )
1223 tr_sessionSetAltSpeedDay( tr
, gtr_pref_int_get( key
) );
1225 else if( !strcmp( key
, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START
) )
1227 tr_sessionSetPeerPortRandomOnStart( tr
, gtr_pref_flag_get( key
) );
1229 else if( !strcmp( key
, TR_PREFS_KEY_INCOMPLETE_DIR
) )
1231 tr_sessionSetIncompleteDir( tr
, gtr_pref_string_get( key
) );
1233 else if( !strcmp( key
, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED
) )
1235 tr_sessionSetIncompleteDirEnabled( tr
, gtr_pref_flag_get( key
) );
1237 else if( !strcmp( key
, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED
) )
1239 tr_sessionSetTorrentDoneScriptEnabled( tr
, gtr_pref_flag_get( key
) );
1241 else if( !strcmp( key
, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME
) )
1243 tr_sessionSetTorrentDoneScript( tr
, gtr_pref_string_get( key
) );
1245 else if( !strcmp( key
, TR_PREFS_KEY_START
) )
1247 tr_sessionSetPaused( tr
, !gtr_pref_flag_get( key
) );
1249 else if( !strcmp( key
, TR_PREFS_KEY_TRASH_ORIGINAL
) )
1251 tr_sessionSetDeleteSource( tr
, gtr_pref_flag_get( key
) );
1256 update_model_once( gpointer gdata
)
1258 struct cbdata
*data
= gdata
;
1260 /* update the torrent data in the model */
1261 gtr_core_update( data
->core
);
1263 /* refresh the main window's statusbar and toolbar buttons */
1264 if( data
->wind
!= NULL
)
1265 gtr_window_refresh( data
->wind
);
1267 /* update the actions */
1268 refresh_actions( data
);
1270 /* update the status tray icon */
1271 if( data
->icon
!= NULL
)
1272 gtr_icon_refresh( data
->icon
);
1274 data
->update_model_soon_tag
= 0;
1279 update_model_soon( gpointer gdata
)
1281 struct cbdata
*data
= gdata
;
1283 if( data
->update_model_soon_tag
== 0 )
1284 data
->update_model_soon_tag
= gdk_threads_add_idle( update_model_once
, data
);
1288 update_model_loop( gpointer gdata
)
1290 const gboolean done
= global_sigcount
;
1293 update_model_once( gdata
);
1299 show_about_dialog( GtkWindow
* parent
)
1301 const char * uri
= "http://www.transmissionbt.com/";
1302 const char * authors
[] = { "Jordan Lee (Backend; GTK+)",
1303 "Mitchell Livingston (Backend; OS X)",
1306 gtk_show_about_dialog( parent
,
1308 "comments", _( "A fast and easy BitTorrent client" ),
1309 "copyright", _( "Copyright (c) The Transmission Project" ),
1310 "logo-icon-name", MY_CONFIG_NAME
,
1311 "name", g_get_application_name( ),
1312 /* Translators: translate "translator-credits" as your name
1313 to have it appear in the credits in the "About"
1315 "translator-credits", _( "translator-credits" ),
1316 "version", LONG_VERSION_STRING
,
1318 "website-label", uri
,
1321 "wrap-license", TRUE
,
1327 append_id_to_benc_list( GtkTreeModel
* m
, GtkTreePath
* path UNUSED
,
1328 GtkTreeIter
* iter
, gpointer list
)
1330 tr_torrent
* tor
= NULL
;
1331 gtk_tree_model_get( m
, iter
, MC_TORRENT
, &tor
, -1 );
1332 tr_bencListAddInt( list
, tr_torrentId( tor
) );
1336 call_rpc_for_selected_torrents( struct cbdata
* data
, const char * method
)
1338 tr_benc top
, *args
, *ids
;
1339 gboolean invoked
= FALSE
;
1340 GtkTreeSelection
* s
= data
->sel
;
1341 tr_session
* session
= gtr_core_session( data
->core
);
1343 tr_bencInitDict( &top
, 2 );
1344 tr_bencDictAddStr( &top
, "method", method
);
1345 args
= tr_bencDictAddDict( &top
, "arguments", 1 );
1346 ids
= tr_bencDictAddList( args
, "ids", 0 );
1347 gtk_tree_selection_selected_foreach( s
, append_id_to_benc_list
, ids
);
1349 if( tr_bencListSize( ids
) != 0 )
1352 char * json
= tr_bencToStr( &top
, TR_FMT_JSON_LEAN
, &json_len
);
1353 tr_rpc_request_exec_json( session
, json
, json_len
, NULL
, NULL
);
1358 tr_bencFree( &top
);
1363 open_folder_foreach( GtkTreeModel
* model
, GtkTreePath
* path UNUSED
,
1364 GtkTreeIter
* iter
, gpointer core
)
1367 gtk_tree_model_get( model
, iter
, MC_TORRENT_ID
, &id
, -1 );
1368 gtr_core_open_folder( core
, id
);
1372 on_message_window_closed( void )
1374 gtr_action_set_toggled( "toggle-message-log", FALSE
);
1379 accumulate_selected_torrents( GtkTreeModel
* model
, GtkTreePath
* path UNUSED
,
1380 GtkTreeIter
* iter
, gpointer gdata
)
1383 GSList
** data
= gdata
;
1385 gtk_tree_model_get( model
, iter
, MC_TORRENT_ID
, &id
, -1 );
1386 *data
= g_slist_append( *data
, GINT_TO_POINTER( id
) );
1390 remove_selected( struct cbdata
* data
, gboolean delete_files
)
1394 gtk_tree_selection_selected_foreach( data
->sel
, accumulate_selected_torrents
, &l
);
1397 gtr_confirm_remove( data
->wind
, data
->core
, l
, delete_files
);
1401 start_all_torrents( struct cbdata
* data
)
1403 tr_session
* session
= gtr_core_session( data
->core
);
1404 const char * cmd
= "{ \"method\": \"torrent-start\" }";
1405 tr_rpc_request_exec_json( session
, cmd
, strlen( cmd
), NULL
, NULL
);
1409 pause_all_torrents( struct cbdata
* data
)
1411 tr_session
* session
= gtr_core_session( data
->core
);
1412 const char * cmd
= "{ \"method\": \"torrent-stop\" }";
1413 tr_rpc_request_exec_json( session
, cmd
, strlen( cmd
), NULL
, NULL
);
1417 get_first_selected_torrent( struct cbdata
* data
)
1419 tr_torrent
* tor
= NULL
;
1421 GList
* l
= gtk_tree_selection_get_selected_rows( data
->sel
, &m
);
1423 GtkTreePath
* p
= l
->data
;
1425 if( gtk_tree_model_get_iter( m
, &i
, p
) )
1426 gtk_tree_model_get( m
, &i
, MC_TORRENT
, &tor
, -1 );
1428 g_list_foreach( l
, (GFunc
)gtk_tree_path_free
, NULL
);
1434 copy_magnet_link_to_clipboard( GtkWidget
* w
, tr_torrent
* tor
)
1436 char * magnet
= tr_torrentGetMagnetLink( tor
);
1437 GdkDisplay
* display
= gtk_widget_get_display( w
);
1439 GtkClipboard
* clipboard
;
1441 /* this is The Right Thing for copy/paste... */
1442 selection
= GDK_SELECTION_CLIPBOARD
;
1443 clipboard
= gtk_clipboard_get_for_display( display
, selection
);
1444 gtk_clipboard_set_text( clipboard
, magnet
, -1 );
1446 /* ...but people using plain ol' X need this instead */
1447 selection
= GDK_SELECTION_PRIMARY
;
1448 clipboard
= gtk_clipboard_get_for_display( display
, selection
);
1449 gtk_clipboard_set_text( clipboard
, magnet
, -1 );
1456 gtr_actions_handler( const char * action_name
, gpointer user_data
)
1458 struct cbdata
* data
= user_data
;
1459 gboolean changed
= FALSE
;
1461 if( !strcmp( action_name
, "open-torrent-from-url" ) )
1463 GtkWidget
* w
= gtr_torrent_open_from_url_dialog_new( data
->wind
, data
->core
);
1464 gtk_widget_show( w
);
1466 else if( !strcmp( action_name
, "open-torrent-menu" )
1467 || !strcmp( action_name
, "open-torrent-toolbar" ) )
1469 GtkWidget
* w
= gtr_torrent_open_from_file_dialog_new( data
->wind
, data
->core
);
1470 gtk_widget_show( w
);
1472 else if( !strcmp( action_name
, "show-stats" ) )
1474 GtkWidget
* dialog
= gtr_stats_dialog_new( data
->wind
, data
->core
);
1475 gtk_widget_show( dialog
);
1477 else if( !strcmp( action_name
, "donate" ) )
1479 gtr_open_uri( "http://www.transmissionbt.com/donate.php" );
1481 else if( !strcmp( action_name
, "pause-all-torrents" ) )
1483 pause_all_torrents( data
);
1485 else if( !strcmp( action_name
, "start-all-torrents" ) )
1487 start_all_torrents( data
);
1489 else if( !strcmp( action_name
, "copy-magnet-link-to-clipboard" ) )
1491 tr_torrent
* tor
= get_first_selected_torrent( data
);
1494 copy_magnet_link_to_clipboard( GTK_WIDGET( data
->wind
), tor
);
1497 else if( !strcmp( action_name
, "relocate-torrent" ) )
1499 GSList
* ids
= get_selected_torrent_ids( data
);
1502 GtkWindow
* parent
= data
->wind
;
1503 GtkWidget
* w
= gtr_relocate_dialog_new( parent
, data
->core
, ids
);
1504 gtk_widget_show( w
);
1507 else if( !strcmp( action_name
, "torrent-start" )
1508 || !strcmp( action_name
, "torrent-start-now" )
1509 || !strcmp( action_name
, "torrent-stop" )
1510 || !strcmp( action_name
, "torrent-reannounce" )
1511 || !strcmp( action_name
, "torrent-verify" )
1512 || !strcmp( action_name
, "queue-move-top" )
1513 || !strcmp( action_name
, "queue-move-up" )
1514 || !strcmp( action_name
, "queue-move-down" )
1515 || !strcmp( action_name
, "queue-move-bottom" ) )
1517 changed
|= call_rpc_for_selected_torrents( data
, action_name
);
1519 else if( !strcmp( action_name
, "open-torrent-folder" ) )
1521 gtk_tree_selection_selected_foreach( data
->sel
, open_folder_foreach
, data
->core
);
1523 else if( !strcmp( action_name
, "show-torrent-properties" ) )
1525 show_details_dialog_for_selected_torrents( data
);
1527 else if( !strcmp( action_name
, "new-torrent" ) )
1529 GtkWidget
* w
= gtr_torrent_creation_dialog_new( data
->wind
, data
->core
);
1530 gtk_widget_show( w
);
1532 else if( !strcmp( action_name
, "remove-torrent" ) )
1534 remove_selected( data
, FALSE
);
1536 else if( !strcmp( action_name
, "delete-torrent" ) )
1538 remove_selected( data
, TRUE
);
1540 else if( !strcmp( action_name
, "quit" ) )
1542 on_app_exit( data
);
1544 else if( !strcmp( action_name
, "select-all" ) )
1546 gtk_tree_selection_select_all( data
->sel
);
1548 else if( !strcmp( action_name
, "deselect-all" ) )
1550 gtk_tree_selection_unselect_all( data
->sel
);
1552 else if( !strcmp( action_name
, "edit-preferences" ) )
1554 if( NULL
== data
->prefs
)
1556 data
->prefs
= gtr_prefs_dialog_new( data
->wind
, G_OBJECT( data
->core
) );
1557 g_signal_connect( data
->prefs
, "destroy",
1558 G_CALLBACK( gtk_widget_destroyed
), &data
->prefs
);
1560 gtr_window_present( GTK_WINDOW( data
->prefs
) );
1562 else if( !strcmp( action_name
, "toggle-message-log" ) )
1566 GtkWidget
* win
= gtr_message_log_window_new( data
->wind
, data
->core
);
1567 g_signal_connect( win
, "destroy", G_CALLBACK( on_message_window_closed
), NULL
);
1572 gtr_action_set_toggled( "toggle-message-log", FALSE
);
1573 gtk_widget_destroy( data
->msgwin
);
1574 data
->msgwin
= NULL
;
1577 else if( !strcmp( action_name
, "show-about-dialog" ) )
1579 show_about_dialog( data
->wind
);
1581 else if( !strcmp ( action_name
, "help" ) )
1583 gtr_open_uri( gtr_get_help_uri( ) );
1585 else if( !strcmp( action_name
, "toggle-main-window" ) )
1587 toggleMainWindow( data
);
1589 else if( !strcmp( action_name
, "present-main-window" ) )
1591 presentMainWindow( data
);
1593 else g_error ( "Unhandled action: %s", action_name
);
1596 update_model_soon( data
);