transmission 2.51 update
[tomato.git] / release / src / router / transmission / gtk / main.c
blob3413546709864c1e6bf993585933da6c390a649b
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 *****************************************************************************/
25 #include <locale.h>
26 #include <signal.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <stdlib.h> /* exit() */
30 #include <time.h>
32 #include <glib/gi18n.h>
33 #include <glib/gstdio.h>
34 #include <gio/gio.h>
35 #include <gtk/gtk.h>
37 #include <libtransmission/transmission.h>
38 #include <libtransmission/rpcimpl.h>
39 #include <libtransmission/utils.h>
40 #include <libtransmission/version.h>
42 #include "actions.h"
43 #include "conf.h"
44 #include "details.h"
45 #include "dialogs.h"
46 #include "hig.h"
47 #include "makemeta-ui.h"
48 #include "msgwin.h"
49 #include "notify.h"
50 #include "open-dialog.h"
51 #include "relocate.h"
52 #include "stats.h"
53 #include "tr-core.h"
54 #include "tr-icon.h"
55 #include "tr-prefs.h"
56 #include "tr-window.h"
57 #include "util.h"
58 #include "ui.h"
60 #define MY_CONFIG_NAME "transmission"
61 #define MY_READABLE_NAME "transmission-gtk"
63 #define SHOW_LICENSE
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.";
70 struct cbdata
72 char * config_dir;
73 gboolean start_paused;
74 gboolean is_iconified;
76 guint activation_count;
77 guint timer;
78 guint update_model_soon_tag;
79 guint refresh_actions_tag;
80 gpointer icon;
81 GtkWindow * wind;
82 TrCore * core;
83 GtkWidget * msgwin;
84 GtkWidget * prefs;
85 GSList * error_list;
86 GSList * duplicates_list;
87 GSList * details;
88 GtkTreeSelection * sel;
89 gpointer quit_dialog;
92 static void
93 gtr_window_present( GtkWindow * window )
95 gtk_window_present_with_time( window, gtk_get_current_event_time( ) );
98 /***
99 ****
100 **** DETAILS DIALOGS MANAGEMENT
101 ****
102 ***/
104 static int
105 compare_integers( gconstpointer a, gconstpointer b )
107 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
110 static char*
111 get_details_dialog_key( GSList * id_list )
113 GSList * l;
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) );
120 g_slist_free( tmp );
121 return g_string_free( gstr, FALSE );
124 static void
125 get_selected_torrent_ids_foreach( GtkTreeModel * model,
126 GtkTreePath * p UNUSED,
127 GtkTreeIter * iter,
128 gpointer gdata )
130 int id;
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 ) );
135 static GSList*
136 get_selected_torrent_ids( struct cbdata * data )
138 GSList * ids = NULL;
139 gtk_tree_selection_selected_foreach( data->sel,
140 get_selected_torrent_ids_foreach,
141 &ids );
142 return ids;
145 static void
146 on_details_dialog_closed( gpointer gdata, GObject * dead )
148 struct cbdata * data = gdata;
150 data->details = g_slist_remove( data->details, dead );
153 static void
154 show_details_dialog_for_selected_torrents( struct cbdata * data )
156 GtkWidget * dialog = NULL;
157 GSList * l;
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" ) ) )
163 dialog = l->data;
165 if( dialog == NULL )
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 ) );
176 g_free( key );
177 g_slist_free( ids );
180 /****
181 *****
182 ***** ON SELECTION CHANGED
183 *****
184 ****/
186 struct counts_data
188 int total_count;
189 int queued_count;
190 int stopped_count;
193 static void
194 get_selected_torrent_counts_foreach( GtkTreeModel * model, GtkTreePath * path UNUSED,
195 GtkTreeIter * iter, gpointer user_data )
197 int activity = 0;
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;
211 static void
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 );
221 static void
222 count_updatable_foreach( GtkTreeModel * model, GtkTreePath * path UNUSED,
223 GtkTreeIter * iter, gpointer accumulated_status )
225 tr_torrent * tor;
226 gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
227 *(int*)accumulated_status |= tr_torrentCanManualUpdate( tor );
230 static gboolean
231 refresh_actions( gpointer gdata )
233 int canUpdate;
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 );
239 bool has_selection;
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 );
264 canUpdate = 0;
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;
269 return FALSE;
272 static void
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 );
281 static void
282 on_selection_changed( GtkTreeSelection * s UNUSED, gpointer gdata )
284 refresh_actions_soon( gdata );
287 /***
288 ****
289 ***/
291 static void
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 );
301 if( error != NULL )
303 g_warning( _( "Error registering Transmission as x-scheme-handler/magnet handler: %s" ), error->message );
304 g_clear_error( &error );
309 static void
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 );
320 if( !isMaximized )
322 int x, y, w, h;
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 );
332 /***
333 **** listen to changes that come from RPC
334 ***/
336 struct rpc_idle_data
338 TrCore * core;
339 int id;
340 gboolean delete_files;
343 static gboolean
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 );
350 g_free( data );
351 return FALSE; /* tell g_idle not to call this func twice */
354 static gboolean
355 rpc_torrent_add_idle( gpointer gdata )
357 tr_torrent * tor;
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 );
363 g_free( data );
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,
371 void * gdata )
373 tr_rpc_callback_status status = TR_RPC_OK;
374 struct cbdata * cbdata = gdata;
375 gdk_threads_enter( );
377 switch( type )
379 case TR_RPC_SESSION_CLOSE:
380 gtr_action_activate( "quit" );
381 break;
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 );
388 break;
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;
399 break;
402 case TR_RPC_SESSION_CHANGED: {
403 int i;
404 tr_benc tmp;
405 tr_benc * newval;
406 tr_benc * oldvals = gtr_pref_get_all( );
407 const char * key;
408 GSList * l;
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 )
414 bool changed;
415 tr_benc * oldval = tr_bencDictFind( oldvals, key );
416 if( !oldval )
417 changed = true;
418 else {
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;
422 tr_free( b );
423 tr_free( a );
426 if( changed )
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 );
435 tr_bencFree( &tmp );
436 break;
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 */
444 break;
447 gdk_threads_leave( );
448 return status;
451 /***
452 **** signal handling
453 ***/
455 static sig_atomic_t global_sigcount = 0;
456 static struct cbdata * sighandler_cbdata = NULL;
458 static void
459 signal_handler( int sig )
461 if( ++global_sigcount > 1 )
463 signal( sig, SIG_DFL );
464 raise( sig );
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 );
473 /****
474 *****
475 *****
476 ****/
478 static void app_setup( TrWindow * wind, struct cbdata * cbdata );
480 static void
481 on_startup( GApplication * application, gpointer user_data )
483 const char * str;
484 GtkWindow * win;
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( );
536 static void
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 ) )
545 return;
547 gtr_action_activate( "present-main-window" );
550 static void
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 );
561 static void
562 on_open (GApplication * application UNUSED,
563 GFile ** f,
564 gint file_count,
565 gchar * hint UNUSED,
566 gpointer gdata )
568 int i;
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 );
579 /***
580 ****
581 ***/
584 main( int argc, char ** argv )
586 int ret;
587 struct stat sb;
588 char * application_id;
589 GApplication * app;
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 );
607 /* init i18n */
608 setlocale( LC_ALL, "" );
609 bindtextdomain( MY_READABLE_NAME, TRANSMISSIONLOCALEDIR );
610 bind_textdomain_codeset( MY_READABLE_NAME, "UTF-8" );
611 textdomain( MY_READABLE_NAME );
613 /* init glib/gtk */
614 g_thread_init (NULL);
615 g_type_init ();
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);
628 return 1;
630 g_option_context_free (option_context);
632 /* handle the trivial "version" option */
633 if( show_version ) {
634 fprintf( stderr, "%s %s\n", MY_READABLE_NAME, LONG_VERSION_STRING );
635 return 0;
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 */
648 gtr_notify_init( );
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 );
660 return ret;
663 static void
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 );
676 static void
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 ) );
707 else
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,
719 GTK_MESSAGE_INFO,
720 GTK_BUTTONS_NONE,
721 "%s",
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 );
731 break;
732 default:
733 exit( 0 );
738 static void
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 );
761 static void
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;
770 static void
771 toggleMainWindow( struct cbdata * cbdata )
773 if( cbdata->is_iconified )
774 presentMainWindow( cbdata );
775 else
776 hideMainWindow( cbdata );
779 static void on_app_exit( gpointer vdata );
781 static gboolean
782 winclose( GtkWidget * w UNUSED,
783 GdkEvent * event UNUSED,
784 gpointer gdata )
786 struct cbdata * cbdata = gdata;
788 if( cbdata->icon != NULL )
789 gtr_action_activate ( "toggle-main-window" );
790 else
791 on_app_exit( cbdata );
793 return TRUE; /* don't propagate event further */
796 static void
797 rowChangedCB( GtkTreeModel * model UNUSED,
798 GtkTreePath * path,
799 GtkTreeIter * iter UNUSED,
800 gpointer gdata )
802 struct cbdata * data = gdata;
804 if( gtk_tree_selection_path_is_selected ( data->sel, path ) )
805 refresh_actions_soon( data );
808 static void
809 on_drag_data_received( GtkWidget * widget UNUSED,
810 GdkDragContext * drag_context,
811 gint x UNUSED,
812 gint y UNUSED,
813 GtkSelectionData * selection_data,
814 guint info UNUSED,
815 guint time_,
816 gpointer gdata )
818 guint i;
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 );
828 /* cleanup */
829 g_slist_foreach( files, (GFunc)g_object_unref, NULL );
830 g_slist_free( files );
831 g_strfreev( uris );
833 gtk_drag_finish( drag_context, true, FALSE, time_ );
836 static void
837 main_window_setup( struct cbdata * cbdata, TrWindow * wind )
839 GtkWidget * w;
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 );
861 static gboolean
862 on_session_closed( gpointer gdata )
864 GSList * tmp;
865 struct cbdata * cbdata = gdata;
867 tmp = g_slist_copy( cbdata->details );
868 g_slist_foreach( tmp, (GFunc)gtk_widget_destroy, NULL );
869 g_slist_free( tmp );
871 if( cbdata->prefs )
872 gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
873 if( cbdata->wind )
874 gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
875 g_object_unref( cbdata->core );
876 if( cbdata->icon )
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 );
883 return FALSE;
886 static gpointer
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( );
897 return NULL;
900 static void
901 exit_now_cb( GtkWidget *w UNUSED, gpointer data UNUSED )
903 exit( 0 );
906 static void
907 on_app_exit( gpointer vdata )
909 GtkWidget *r, *p, *b, *w, *c;
910 struct cbdata *cbdata = vdata;
912 /* stop the update timer */
913 if( cbdata->timer )
915 g_source_remove( cbdata->timer );
916 cbdata->timer = 0;
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 );
950 /* clear the UI */
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 ) );
961 /* shut down libT */
962 g_thread_create( session_close_threadfunc, vdata, TRUE, NULL );
965 static void
966 show_torrent_errors( GtkWindow * window, const char * primary, GSList ** files )
968 GSList * l;
969 GtkWidget * w;
970 GString * s = g_string_new( NULL );
971 const char * leader = g_slist_length( *files ) > 1
972 ? gtr_get_unicode_string( GTR_UNICODE_BULLET )
973 : "";
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,
980 GTK_MESSAGE_ERROR,
981 GTK_BUTTONS_CLOSE,
982 "%s", primary );
983 gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
984 "%s", s->str );
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 );
992 *files = NULL;
995 static void
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 );
1013 static void
1014 on_core_error( TrCore * core UNUSED, guint code, const char * msg, struct cbdata * c )
1016 switch( code )
1018 case TR_PARSE_ERR:
1019 c->error_list =
1020 g_slist_append( c->error_list, g_path_get_basename( msg ) );
1021 break;
1023 case TR_PARSE_DUPLICATE:
1024 c->duplicates_list = g_slist_append( c->duplicates_list, g_strdup( msg ) );
1025 break;
1027 case TR_CORE_ERR_NO_MORE_TORRENTS:
1028 flush_torrent_errors( c );
1029 break;
1031 default:
1032 g_assert_not_reached( );
1033 break;
1037 static gboolean
1038 on_main_window_focus_in( GtkWidget * widget UNUSED,
1039 GdkEventFocus * event UNUSED,
1040 gpointer gdata )
1042 struct cbdata * cbdata = gdata;
1044 if( cbdata->wind )
1045 gtk_window_set_urgency_hint( cbdata->wind, FALSE );
1046 return FALSE;
1049 static void
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 );
1057 if( cbdata->wind )
1058 gtk_window_set_urgency_hint( cbdata->wind, TRUE );
1060 gtk_widget_show( w );
1063 static void
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 ) );
1255 static gboolean
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;
1275 return FALSE;
1278 static void
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 );
1287 static gboolean
1288 update_model_loop( gpointer gdata )
1290 const gboolean done = global_sigcount;
1292 if( !done )
1293 update_model_once( gdata );
1295 return !done;
1298 static void
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)",
1304 NULL };
1306 gtk_show_about_dialog( parent,
1307 "authors", authors,
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"
1314 dialog */
1315 "translator-credits", _( "translator-credits" ),
1316 "version", LONG_VERSION_STRING,
1317 "website", uri,
1318 "website-label", uri,
1319 #ifdef SHOW_LICENSE
1320 "license", LICENSE,
1321 "wrap-license", TRUE,
1322 #endif
1323 NULL );
1326 static void
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 ) );
1335 static gboolean
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 )
1351 int json_len;
1352 char * json = tr_bencToStr( &top, TR_FMT_JSON_LEAN, &json_len );
1353 tr_rpc_request_exec_json( session, json, json_len, NULL, NULL );
1354 g_free( json );
1355 invoked = TRUE;
1358 tr_bencFree( &top );
1359 return invoked;
1362 static void
1363 open_folder_foreach( GtkTreeModel * model, GtkTreePath * path UNUSED,
1364 GtkTreeIter * iter, gpointer core )
1366 int id;
1367 gtk_tree_model_get( model, iter, MC_TORRENT_ID, &id, -1 );
1368 gtr_core_open_folder( core, id );
1371 static gboolean
1372 on_message_window_closed( void )
1374 gtr_action_set_toggled( "toggle-message-log", FALSE );
1375 return FALSE;
1378 static void
1379 accumulate_selected_torrents( GtkTreeModel * model, GtkTreePath * path UNUSED,
1380 GtkTreeIter * iter, gpointer gdata )
1382 int id;
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 ) );
1389 static void
1390 remove_selected( struct cbdata * data, gboolean delete_files )
1392 GSList * l = NULL;
1394 gtk_tree_selection_selected_foreach( data->sel, accumulate_selected_torrents, &l );
1396 if( l != NULL )
1397 gtr_confirm_remove( data->wind, data->core, l, delete_files );
1400 static void
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 );
1408 static void
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 );
1416 static tr_torrent*
1417 get_first_selected_torrent( struct cbdata * data )
1419 tr_torrent * tor = NULL;
1420 GtkTreeModel * m;
1421 GList * l = gtk_tree_selection_get_selected_rows( data->sel, &m );
1422 if( l != NULL ) {
1423 GtkTreePath * p = l->data;
1424 GtkTreeIter i;
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 );
1429 g_list_free( l );
1430 return tor;
1433 static void
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 );
1438 GdkAtom selection;
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 );
1451 /* cleanup */
1452 tr_free( magnet );
1455 void
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 );
1492 if( tor != NULL )
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 );
1500 if( ids != NULL )
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" ) )
1564 if( !data->msgwin )
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 );
1568 data->msgwin = win;
1570 else
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 );
1595 if( changed )
1596 update_model_soon( data );