source: trunk/gtk/main.c @ 12655

Last change on this file since 12655 was 12655, checked in by jordan, 11 years ago

remove the gtr_idle_add() portability wrapper around gdk_threads_add_idle() -- it's unnecessary now that the minimum gtk version's been bumped.

  • Property svn:keywords set to Date Rev Author Id
File size: 56.1 KB
Line 
1/******************************************************************************
2 * $Id: main.c 12655 2011-08-08 17:10:55Z jordan $
3 *
4 * Copyright (c) Transmission authors and contributors
5 *
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:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
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 *****************************************************************************/
24
25#include <locale.h>
26#include <signal.h>
27#include <string.h>
28#include <stdio.h>
29#include <stdlib.h> /* exit() */
30#include <sys/param.h>
31#include <time.h>
32#include <unistd.h>
33
34#include <curl/curl.h>
35
36#include <glib/gi18n.h>
37#include <glib/gstdio.h>
38#include <gio/gio.h>
39#include <gtk/gtk.h>
40
41#include <libtransmission/transmission.h>
42#include <libtransmission/rpcimpl.h>
43#include <libtransmission/utils.h>
44#include <libtransmission/version.h>
45#include <libtransmission/web.h>
46
47#include "actions.h"
48#include "conf.h"
49#include "details.h"
50#include "dialogs.h"
51#include "hig.h"
52#include "makemeta-ui.h"
53#include "msgwin.h"
54#include "open-dialog.h"
55#include "relocate.h"
56#include "stats.h"
57#include "tr-core.h"
58#include "tr-icon.h"
59#include "tr-prefs.h"
60#include "tr-window.h"
61#include "util.h"
62#include "ui.h"
63
64#define MY_CONFIG_NAME "transmission"
65#define MY_READABLE_NAME "transmission-gtk"
66
67#define SHOW_LICENSE
68static const char * LICENSE =
69"The OS X client, CLI client, and parts of libtransmission are licensed under the terms of the MIT license.\n\n"
70"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"
71"1. The MIT-licensed portions of Transmission listed above are exempt from GPLv2 clause 2(b) and may retain their MIT license.\n\n"
72"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
74struct cbdata
75{
76    gboolean                    is_iconified;
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;
90};
91
92/***
93****
94****  DETAILS DIALOGS MANAGEMENT
95****
96***/
97
98static void
99gtr_window_present( GtkWindow * window )
100{
101    gtk_window_present_with_time( window, gtk_get_current_event_time( ) );
102}
103
104/***
105****
106****  DETAILS DIALOGS MANAGEMENT
107****
108***/
109
110static int
111compare_integers( const void * a, const void * b )
112{
113    return *(int*)a - *(int*)b;
114}
115
116static char*
117get_details_dialog_key( GSList * id_list )
118{
119    int i;
120    int n;
121    int * ids;
122    GSList * l;
123    GString * gstr = g_string_new( NULL );
124
125    n = g_slist_length( id_list );
126    ids = g_new( int, n );
127    i = 0;
128    for( l=id_list; l!=NULL; l=l->next )
129        ids[i++] = GPOINTER_TO_INT( l->data );
130    g_assert( i == n );
131    qsort( ids, n, sizeof(int), compare_integers );
132
133    for( i=0; i<n; ++i )
134        g_string_append_printf( gstr, "%d ", ids[i] );
135
136    g_free( ids );
137    return g_string_free( gstr, FALSE );
138}
139
140struct DetailsDialogHandle
141{
142    char * key;
143    GtkWidget * dialog;
144};
145
146static GSList*
147getSelectedTorrentIds( struct cbdata * data )
148{
149    GList * l;
150    GtkTreeModel * model;
151    GSList * ids = NULL;
152    GList * paths = NULL;
153    GtkTreeSelection * s = data->sel;
154
155    /* build a list of the selected torrents' ids */
156    for( paths=l=gtk_tree_selection_get_selected_rows(s,&model); l; l=l->next ) {
157        GtkTreeIter iter;
158        if( gtk_tree_model_get_iter( model, &iter, l->data ) ) {
159            int id;
160            gtk_tree_model_get( model, &iter, MC_TORRENT_ID, &id, -1 );
161            ids = g_slist_append( ids, GINT_TO_POINTER( id ) );
162        }
163    }
164
165    /* cleanup */
166    g_list_foreach( paths, (GFunc)gtk_tree_path_free, NULL );
167    g_list_free( paths );
168    return ids;
169}
170
171static struct DetailsDialogHandle*
172find_details_dialog_from_ids( struct cbdata * cbdata, GSList * ids )
173{
174    GSList * l;
175    struct DetailsDialogHandle * ret = NULL;
176    char * key = get_details_dialog_key( ids );
177
178    for( l=cbdata->details; l!=NULL && ret==NULL; l=l->next ) {
179        struct DetailsDialogHandle * h = l->data;
180        if( !strcmp( h->key, key ) )
181            ret = h;
182    }
183
184    g_free( key );
185    return ret;
186}
187
188static struct DetailsDialogHandle*
189find_details_dialog_from_widget( struct cbdata * cbdata, gpointer w )
190{
191    GSList * l;
192    struct DetailsDialogHandle * ret = NULL;
193
194    for( l=cbdata->details; l!=NULL && ret==NULL; l=l->next ) {
195        struct DetailsDialogHandle * h = l->data;
196        if( h->dialog == w )
197            ret = h;
198    }
199
200    return ret;
201}
202
203static void
204on_details_dialog_closed( gpointer gdata, GObject * dead )
205{
206    struct cbdata * data = gdata;
207    struct DetailsDialogHandle * h = find_details_dialog_from_widget( data, dead );
208
209    if( h != NULL )
210    {
211        data->details = g_slist_remove( data->details, h );
212        g_free( h->key );
213        g_free( h );
214    }
215}
216
217static void
218show_details_dialog_for_selected_torrents( struct cbdata * data )
219{
220    GtkWidget * w;
221    GSList * ids = getSelectedTorrentIds( data );
222    struct DetailsDialogHandle * h = find_details_dialog_from_ids( data, ids );
223
224    if( h != NULL )
225        w = h->dialog;
226    else {
227        h = g_new( struct DetailsDialogHandle, 1 );
228        h->key = get_details_dialog_key( ids );
229        h->dialog = w = gtr_torrent_details_dialog_new( data->wind, data->core );
230        gtr_torrent_details_dialog_set_torrents( w, ids );
231        data->details = g_slist_append( data->details, h );
232        g_object_weak_ref( G_OBJECT( w ), on_details_dialog_closed, data );
233        gtk_widget_show( w );
234    }
235    gtr_window_present( GTK_WINDOW( w ) );
236    g_slist_free( ids );
237}
238
239/****
240*****
241*****  ON SELECTION CHANGED
242*****
243****/
244
245struct counts_data
246{
247    int total_count;
248    int queued_count;
249    int stopped_count;
250};
251
252static void
253get_selected_torrent_counts_foreach( GtkTreeModel * model, GtkTreePath * path UNUSED,
254                                     GtkTreeIter * iter, gpointer user_data )
255{
256    int activity = 0;
257    struct counts_data * counts = user_data;
258
259    ++counts->total_count;
260
261    gtk_tree_model_get( model, iter, MC_ACTIVITY, &activity, -1 );
262
263    if( ( activity == TR_STATUS_DOWNLOAD_WAIT ) || ( activity == TR_STATUS_SEED_WAIT ) )
264        ++counts->queued_count;
265
266    if( activity == TR_STATUS_STOPPED )
267        ++counts->stopped_count;
268}
269
270static void
271get_selected_torrent_counts( struct cbdata * data, struct counts_data * counts )
272{
273    counts->total_count = 0;
274    counts->queued_count = 0;
275    counts->stopped_count = 0;
276
277    gtk_tree_selection_selected_foreach( data->sel, get_selected_torrent_counts_foreach, counts );
278}
279
280static void
281count_updatable_foreach( GtkTreeModel * model, GtkTreePath * path UNUSED,
282                         GtkTreeIter * iter, gpointer accumulated_status )
283{
284    tr_torrent * tor;
285    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
286    *(int*)accumulated_status |= tr_torrentCanManualUpdate( tor );
287}
288
289static gboolean
290refresh_actions( gpointer gdata )
291{
292    int canUpdate;
293    struct counts_data sel_counts;
294    struct cbdata * data = gdata;
295    const size_t total = gtr_core_get_torrent_count( data->core );
296    const size_t active = gtr_core_get_active_torrent_count( data->core );
297    const int torrent_count = gtk_tree_model_iter_n_children( gtr_core_model( data->core ), NULL );
298    bool has_selection;
299
300    get_selected_torrent_counts( data, &sel_counts );
301    has_selection = sel_counts.total_count > 0;
302
303    gtr_action_set_sensitive( "select-all", torrent_count != 0 );
304    gtr_action_set_sensitive( "deselect-all", torrent_count != 0 );
305    gtr_action_set_sensitive( "pause-all-torrents", active != 0 );
306    gtr_action_set_sensitive( "start-all-torrents", active != total );
307
308    gtr_action_set_sensitive( "torrent-stop", ( sel_counts.stopped_count < sel_counts.total_count ) );
309    gtr_action_set_sensitive( "torrent-start", ( sel_counts.stopped_count ) > 0 );
310    gtr_action_set_sensitive( "torrent-start-now", ( sel_counts.stopped_count + sel_counts.queued_count ) > 0 );
311    gtr_action_set_sensitive( "torrent-verify",          has_selection );
312    gtr_action_set_sensitive( "remove-torrent",          has_selection );
313    gtr_action_set_sensitive( "delete-torrent",          has_selection );
314    gtr_action_set_sensitive( "relocate-torrent",        has_selection );
315    gtr_action_set_sensitive( "queue-move-top",          has_selection );
316    gtr_action_set_sensitive( "queue-move-up",           has_selection );
317    gtr_action_set_sensitive( "queue-move-down",         has_selection );
318    gtr_action_set_sensitive( "queue-move-bottom",       has_selection );
319    gtr_action_set_sensitive( "show-torrent-properties", has_selection );
320    gtr_action_set_sensitive( "open-torrent-folder", sel_counts.total_count == 1 );
321    gtr_action_set_sensitive( "copy-magnet-link-to-clipboard", sel_counts.total_count == 1 );
322
323    canUpdate = 0;
324    gtk_tree_selection_selected_foreach( data->sel, count_updatable_foreach, &canUpdate );
325    gtr_action_set_sensitive( "torrent-reannounce", canUpdate != 0 );
326
327    data->refresh_actions_tag = 0;
328    return FALSE;
329}
330
331static void
332refresh_actions_soon( gpointer gdata )
333{
334    struct cbdata * data = gdata;
335
336    if( data->refresh_actions_tag == 0 )
337        data->refresh_actions_tag = gdk_threads_add_idle( refresh_actions, data );
338}
339
340static void
341on_selection_changed( GtkTreeSelection * s UNUSED, gpointer gdata )
342{
343    refresh_actions_soon( gdata );
344}
345
346/***
347****
348****
349***/
350
351static void app_setup( TrWindow       * wind,
352                       GSList         * torrent_files,
353                       struct cbdata  * cbdata,
354                       gboolean         paused,
355                       gboolean         minimized );
356
357static void main_window_setup( struct cbdata * cbdata, TrWindow * wind );
358
359static void on_app_exit( gpointer vdata );
360
361static void on_core_error( TrCore *, guint, const char *, struct cbdata * );
362
363static void on_add_torrent( TrCore *, tr_ctor *, gpointer );
364
365static void on_prefs_changed( TrCore * core, const char * key, gpointer );
366
367static gboolean update_model_loop( gpointer gdata );
368static gboolean update_model_once( gpointer gdata );
369
370/***
371****
372***/
373
374static void
375register_magnet_link_handler( void )
376{
377    GAppInfo * app_info = g_app_info_get_default_for_uri_scheme( "magnet" );
378    if( app_info == NULL )
379    {
380        /* there's no default magnet handler, so register ourselves for the job... */
381        GError * error = NULL;
382        app_info = g_app_info_create_from_commandline( "transmission-gtk", "transmission-gtk", G_APP_INFO_CREATE_SUPPORTS_URIS, NULL );
383        g_app_info_set_as_default_for_type( app_info, "x-scheme-handler/magnet", &error );
384        if( error != NULL )
385        {
386            g_warning( _( "Error registering Transmission as x-scheme-handler/magnet handler: %s" ), error->message );
387            g_clear_error( &error );
388        }
389    }
390}
391
392static void
393on_main_window_size_allocated( GtkWidget      * gtk_window,
394                               GtkAllocation  * alloc UNUSED,
395                               gpointer         gdata UNUSED )
396{
397    GdkWindow * gdk_window = gtk_widget_get_window( gtk_window );
398    const gboolean isMaximized = ( gdk_window != NULL )
399                              && ( gdk_window_get_state( gdk_window ) & GDK_WINDOW_STATE_MAXIMIZED );
400
401    gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_IS_MAXIMIZED, isMaximized );
402
403    if( !isMaximized )
404    {
405        int x, y, w, h;
406        gtk_window_get_position( GTK_WINDOW( gtk_window ), &x, &y );
407        gtk_window_get_size( GTK_WINDOW( gtk_window ), &w, &h );
408        gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_X, x );
409        gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_Y, y );
410        gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_WIDTH, w );
411        gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_HEIGHT, h );
412    }
413}
414
415/***
416**** listen to changes that come from RPC
417***/
418
419struct torrent_idle_data
420{
421    TrCore * core;
422    int id;
423    gboolean delete_files;
424};
425
426static gboolean
427rpc_torrent_remove_idle( gpointer gdata )
428{
429    struct torrent_idle_data * data = gdata;
430
431    gtr_core_remove_torrent( data->core, data->id, data->delete_files );
432
433    g_free( data );
434    return FALSE; /* tell g_idle not to call this func twice */
435}
436
437static gboolean
438rpc_torrent_add_idle( gpointer gdata )
439{
440    tr_torrent * tor;
441    struct torrent_idle_data * data = gdata;
442
443    if(( tor = gtr_core_find_torrent( data->core, data->id )))
444        gtr_core_add_torrent( data->core, tor, TRUE );
445
446    g_free( data );
447    return FALSE; /* tell g_idle not to call this func twice */
448}
449
450static tr_rpc_callback_status
451on_rpc_changed( tr_session            * session,
452                tr_rpc_callback_type    type,
453                struct tr_torrent     * tor,
454                void                  * gdata )
455{
456    tr_rpc_callback_status status = TR_RPC_OK;
457    struct cbdata * cbdata = gdata;
458    gdk_threads_enter( );
459
460    switch( type )
461    {
462        case TR_RPC_SESSION_CLOSE:
463            gtr_action_activate( "quit" );
464            break;
465
466        case TR_RPC_TORRENT_ADDED: {
467            struct torrent_idle_data * data = g_new0( struct torrent_idle_data, 1 );
468            data->id = tr_torrentId( tor );
469            data->core = cbdata->core;
470            gdk_threads_add_idle( rpc_torrent_add_idle, data );
471            break;
472        }
473
474        case TR_RPC_TORRENT_REMOVING:
475        case TR_RPC_TORRENT_TRASHING: {
476            struct torrent_idle_data * data = g_new0( struct torrent_idle_data, 1 );
477            data->id = tr_torrentId( tor );
478            data->core = cbdata->core;
479            data->delete_files = type == TR_RPC_TORRENT_TRASHING;
480            gdk_threads_add_idle( rpc_torrent_remove_idle, data );
481            status = TR_RPC_NOREMOVE;
482            break;
483        }
484
485        case TR_RPC_SESSION_CHANGED: {
486            int i;
487            tr_benc tmp;
488            tr_benc * newval;
489            tr_benc * oldvals = gtr_pref_get_all( );
490            const char * key;
491            GSList * l;
492            GSList * changed_keys = NULL;
493            tr_bencInitDict( &tmp, 100 );
494            tr_sessionGetSettings( session, &tmp );
495            for( i=0; tr_bencDictChild( &tmp, i, &key, &newval ); ++i )
496            {
497                bool changed;
498                tr_benc * oldval = tr_bencDictFind( oldvals, key );
499                if( !oldval )
500                    changed = true;
501                else {
502                    char * a = tr_bencToStr( oldval, TR_FMT_BENC, NULL );
503                    char * b = tr_bencToStr( newval, TR_FMT_BENC, NULL );
504                    changed = strcmp( a, b ) != 0;
505                    tr_free( b );
506                    tr_free( a );
507                }
508
509                if( changed )
510                    changed_keys = g_slist_append( changed_keys, (gpointer)key );
511            }
512            tr_sessionGetSettings( session, oldvals );
513
514            for( l=changed_keys; l!=NULL; l=l->next )
515                gtr_core_pref_changed( cbdata->core, l->data );
516
517            g_slist_free( changed_keys );
518            tr_bencFree( &tmp );
519            break;
520        }
521
522        case TR_RPC_TORRENT_CHANGED:
523        case TR_RPC_TORRENT_MOVED:
524        case TR_RPC_TORRENT_STARTED:
525        case TR_RPC_TORRENT_STOPPED:
526            /* nothing interesting to do here */
527            break;
528    }
529
530    gdk_threads_leave( );
531    return status;
532}
533
534/***
535****  signal handling
536***/
537
538static sig_atomic_t global_sigcount = 0;
539static struct cbdata * sighandler_cbdata = NULL;
540
541static void
542signal_handler( int sig )
543{
544    if( ++global_sigcount > 1 )
545    {
546        signal( sig, SIG_DFL );
547        raise( sig );
548    }
549    else switch( sig )
550    {
551        case SIGINT:
552        case SIGTERM:
553            g_message( _( "Got signal %d; trying to shut down cleanly. Do it again if it gets stuck." ), sig );
554            gtr_actions_handler( "quit", sighandler_cbdata );
555            break;
556
557        default:
558            g_message( "unhandled signal" );
559            break;
560    }
561}
562
563static void
564setupsighandlers( void )
565{
566    signal( SIGINT, signal_handler );
567    signal( SIGKILL, signal_handler );
568}
569
570/***
571****
572***/
573
574static GSList *
575checkfilenames( int argc, char **argv )
576{
577    int i;
578    GSList * ret = NULL;
579    char * pwd = g_get_current_dir( );
580
581    for( i=0; i<argc; ++i )
582    {
583        const char * arg = argv[i];
584
585        if( gtr_is_supported_url( arg ) || gtr_is_magnet_link( arg ) )
586        {
587            ret = g_slist_prepend( ret, g_strdup( arg ) );
588        }
589        else /* local file */
590        {
591            char * filename;
592
593            if( g_path_is_absolute( arg ) )
594                filename = g_strdup( arg );
595            else {
596                filename = g_filename_from_uri( arg, NULL, NULL );
597
598                if( filename == NULL )
599                    filename = g_build_filename( pwd, arg, NULL );
600            }
601
602            if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
603                ret = g_slist_prepend( ret, filename );
604            else {
605                if( gtr_is_hex_hashcode( argv[i] ) )
606                    ret = g_slist_prepend( ret, g_strdup_printf( "magnet:?xt=urn:btih:%s", argv[i] ) );
607                g_free( filename );
608            }
609        }
610    }
611
612    g_free( pwd );
613    return g_slist_reverse( ret );
614}
615
616/****
617*****
618*****
619****/
620
621int
622main( int argc, char ** argv )
623{
624    char * err = NULL;
625    GSList * argfiles;
626    GError * gerr;
627    gboolean didinit = FALSE;
628    gboolean didlock = FALSE;
629    gboolean showversion = FALSE;
630    gboolean startpaused = FALSE;
631    gboolean startminimized = FALSE;
632    const char * domain = MY_READABLE_NAME;
633    char * configDir = NULL;
634    gtr_lockfile_state_t tr_state;
635
636    GOptionEntry entries[] = {
637        { "paused",     'p', 0, G_OPTION_ARG_NONE,
638          &startpaused, _( "Start with all torrents paused" ), NULL },
639        { "version",    '\0', 0, G_OPTION_ARG_NONE,
640          &showversion, _( "Show version number and exit" ), NULL },
641        { "minimized",  'm', 0, G_OPTION_ARG_NONE,
642          &startminimized,
643          _( "Start minimized in notification area" ), NULL },
644        { "config-dir", 'g', 0, G_OPTION_ARG_FILENAME, &configDir,
645          _( "Where to look for configuration files" ), NULL },
646        { NULL, 0,   0, 0, NULL, NULL, NULL }
647    };
648
649    /* bind the gettext domain */
650    setlocale( LC_ALL, "" );
651    bindtextdomain( domain, TRANSMISSIONLOCALEDIR );
652    bind_textdomain_codeset( domain, "UTF-8" );
653    textdomain( domain );
654    g_set_application_name( _( "Transmission" ) );
655    tr_formatter_mem_init( mem_K, _(mem_K_str), _(mem_M_str), _(mem_G_str), _(mem_T_str) );
656    tr_formatter_size_init( disk_K, _(disk_K_str), _(disk_M_str), _(disk_G_str), _(disk_T_str) );
657    tr_formatter_speed_init( speed_K, _(speed_K_str), _(speed_M_str), _(speed_G_str), _(speed_T_str) );
658
659    /* initialize gtk */
660    if( !g_thread_supported( ) )
661        g_thread_init( NULL );
662
663    gerr = NULL;
664    if( !gtk_init_with_args( &argc, &argv, (char*)_( "[torrent files or urls]" ), entries,
665                             (char*)domain, &gerr ) )
666    {
667        fprintf( stderr, "%s\n", gerr->message );
668        g_clear_error( &gerr );
669        return 0;
670    }
671
672    if( showversion )
673    {
674        fprintf( stderr, "%s %s\n", MY_READABLE_NAME, LONG_VERSION_STRING );
675        return 0;
676    }
677
678    if( configDir == NULL )
679        configDir = (char*) tr_getDefaultConfigDir( MY_CONFIG_NAME );
680
681    didinit = cf_init( configDir, NULL ); /* must come before actions_init */
682
683    setupsighandlers( ); /* set up handlers for fatal signals */
684
685    didlock = cf_lock( &tr_state, &err );
686    argfiles = checkfilenames( argc - 1, argv + 1 );
687
688    if( !didlock && argfiles )
689    {
690        /* We have torrents to add but there's another copy of Transmsision
691         * running... chances are we've been invoked from a browser, etc.
692         * So send the files over to the "real" copy of Transmission, and
693         * if that goes well, then our work is done. */
694        GSList * l;
695        gboolean delegated = FALSE;
696        const gboolean trash_originals = gtr_pref_flag_get( TR_PREFS_KEY_TRASH_ORIGINAL );
697
698        for( l=argfiles; l!=NULL; l=l->next )
699        {
700            const char * filename = l->data;
701            const gboolean added = gtr_dbus_add_torrent( filename );
702
703            if( added && trash_originals )
704                gtr_file_trash_or_remove( filename );
705
706            delegated |= added;
707        }
708
709        if( delegated ) {
710            g_slist_foreach( argfiles, (GFunc)g_free, NULL );
711            g_slist_free( argfiles );
712            argfiles = NULL;
713
714            if( err ) {
715                g_free( err );
716                err = NULL;
717            }
718        }
719    }
720    else if( ( !didlock ) && ( tr_state == GTR_LOCKFILE_ELOCK ) )
721    {
722        /* There's already another copy of Transmission running,
723         * so tell it to present its window to the user */
724        err = NULL;
725        if( !gtr_dbus_present_window( ) )
726            err = g_strdup( _( "Transmission is already running, but is not responding. To start a new session, you must first close the existing Transmission process." ) );
727    }
728
729    if( didlock && ( didinit || cf_init( configDir, &err ) ) )
730    {
731        /* No other copy of Transmission running...
732         * so we're going to be the primary. */
733
734        const char * str;
735        GtkWindow * win;
736        GtkUIManager * myUIManager;
737        tr_session * session;
738        struct cbdata * cbdata = g_new0( struct cbdata, 1 );
739
740        sighandler_cbdata = cbdata;
741
742        /* ensure the directories are created */
743        if(( str = gtr_pref_string_get( TR_PREFS_KEY_DOWNLOAD_DIR )))
744            g_mkdir_with_parents( str, 0777 );
745        if(( str = gtr_pref_string_get( TR_PREFS_KEY_INCOMPLETE_DIR )))
746            g_mkdir_with_parents( str, 0777 );
747
748        /* initialize the libtransmission session */
749        session = tr_sessionInit( "gtk", configDir, TRUE, gtr_pref_get_all( ) );
750
751        gtr_pref_flag_set( TR_PREFS_KEY_ALT_SPEED_ENABLED, tr_sessionUsesAltSpeed( session ) );
752        gtr_pref_int_set( TR_PREFS_KEY_PEER_PORT, tr_sessionGetPeerPort( session ) );
753        cbdata->core = gtr_core_new( session );
754
755        /* init the ui manager */
756        myUIManager = gtk_ui_manager_new ( );
757        gtr_actions_init ( myUIManager, cbdata );
758        gtk_ui_manager_add_ui_from_string ( myUIManager, fallback_ui_file, -1, NULL );
759        gtk_ui_manager_ensure_update ( myUIManager );
760        gtk_window_set_default_icon_name ( MY_CONFIG_NAME );
761
762        /* create main window now to be a parent to any error dialogs */
763        win = GTK_WINDOW( gtr_window_new( myUIManager, cbdata->core ) );
764        g_signal_connect( win, "size-allocate", G_CALLBACK( on_main_window_size_allocated ), cbdata );
765
766        app_setup( win, argfiles, cbdata, startpaused, startminimized );
767        tr_sessionSetRPCCallback( session, on_rpc_changed, cbdata );
768
769        /* on startup, check & see if it's time to update the blocklist */
770        if( gtr_pref_flag_get( TR_PREFS_KEY_BLOCKLIST_ENABLED ) ) {
771            if( gtr_pref_flag_get( PREF_KEY_BLOCKLIST_UPDATES_ENABLED ) ) {
772                const int64_t last_time = gtr_pref_int_get( "blocklist-date" );
773                const int SECONDS_IN_A_WEEK = 7 * 24 * 60 * 60;
774                const time_t now = time( NULL );
775                if( last_time + SECONDS_IN_A_WEEK < now )
776                    gtr_core_blocklist_update( cbdata->core );
777            }
778        }
779
780        /* if there's no magnet link handler registered, register us */
781        register_magnet_link_handler( );
782
783        gtk_main( );
784    }
785    else if( err )
786    {
787        const char * primary_text = _( "Transmission cannot be started." );
788        GtkWidget * w = gtk_message_dialog_new( NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, primary_text, NULL );
789        gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ), "%s", err );
790        g_signal_connect( w, "response", G_CALLBACK(gtk_main_quit), NULL );
791        gtk_widget_show( w );
792        g_free( err );
793        gtk_main( );
794    }
795
796    return 0;
797}
798
799static void
800on_core_busy( TrCore * core UNUSED, gboolean busy, struct cbdata * c )
801{
802    gtr_window_set_busy( c->wind, busy );
803}
804
805static void
806app_setup( TrWindow      * wind,
807           GSList        * files,
808           struct cbdata * cbdata,
809           gboolean        pause_all,
810           gboolean        is_iconified )
811{
812    const gboolean do_start = gtr_pref_flag_get( TR_PREFS_KEY_START ) && !pause_all;
813    const gboolean do_prompt = gtr_pref_flag_get( PREF_KEY_OPTIONS_PROMPT );
814    const gboolean do_notify = TRUE;
815
816    cbdata->is_iconified = is_iconified;
817
818    if( is_iconified )
819        gtr_pref_flag_set( PREF_KEY_SHOW_TRAY_ICON, TRUE );
820
821    gtr_actions_set_core( cbdata->core );
822
823    /* set up core handlers */
824    g_signal_connect( cbdata->core, "busy", G_CALLBACK( on_core_busy ), cbdata );
825    g_signal_connect( cbdata->core, "add-error", G_CALLBACK( on_core_error ), cbdata );
826    g_signal_connect( cbdata->core, "add-prompt", G_CALLBACK( on_add_torrent ), cbdata );
827    g_signal_connect( cbdata->core, "prefs-changed", G_CALLBACK( on_prefs_changed ), cbdata );
828
829    /* add torrents from command-line and saved state */
830    gtr_core_load( cbdata->core, pause_all );
831    gtr_core_add_list( cbdata->core, files, do_start, do_prompt, do_notify );
832    files = NULL;
833    gtr_core_torrents_added( cbdata->core );
834
835    /* set up main window */
836    main_window_setup( cbdata, wind );
837
838    /* set up the icon */
839    on_prefs_changed( cbdata->core, PREF_KEY_SHOW_TRAY_ICON, cbdata );
840
841    /* start model update timer */
842    cbdata->timer = gdk_threads_add_timeout_seconds( MAIN_WINDOW_REFRESH_INTERVAL_SECONDS, update_model_loop, cbdata );
843    update_model_once( cbdata );
844
845    /* either show the window or iconify it */
846    if( !is_iconified )
847        gtk_widget_show( GTK_WIDGET( wind ) );
848    else
849    {
850        gtk_window_set_skip_taskbar_hint( cbdata->wind,
851                                          cbdata->icon != NULL );
852        cbdata->is_iconified = FALSE; // ensure that the next toggle iconifies
853        gtr_action_set_toggled( "toggle-main-window", FALSE );
854    }
855
856    if( !gtr_pref_flag_get( PREF_KEY_USER_HAS_GIVEN_INFORMED_CONSENT ) )
857    {
858        GtkWidget * w = gtk_message_dialog_new( GTK_WINDOW( wind ),
859                                                GTK_DIALOG_DESTROY_WITH_PARENT,
860                                                GTK_MESSAGE_INFO,
861                                                GTK_BUTTONS_NONE,
862                                                "%s",
863             _( "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." ) );
864        gtk_dialog_add_button( GTK_DIALOG( w ), GTK_STOCK_QUIT, GTK_RESPONSE_REJECT );
865        gtk_dialog_add_button( GTK_DIALOG( w ), _( "I _Accept" ), GTK_RESPONSE_ACCEPT );
866        gtk_dialog_set_default_response( GTK_DIALOG( w ), GTK_RESPONSE_ACCEPT );
867        switch( gtk_dialog_run( GTK_DIALOG( w ) ) ) {
868            case GTK_RESPONSE_ACCEPT:
869                /* only show it once */
870                gtr_pref_flag_set( PREF_KEY_USER_HAS_GIVEN_INFORMED_CONSENT, TRUE );
871                gtk_widget_destroy( w );
872                break;
873            default:
874                exit( 0 );
875        }
876    }
877}
878
879static void
880presentMainWindow( struct cbdata * cbdata )
881{
882    GtkWindow * window = cbdata->wind;
883
884    if( cbdata->is_iconified )
885    {
886        cbdata->is_iconified = false;
887
888        gtk_window_set_skip_taskbar_hint( window, FALSE );
889    }
890
891    if( !gtk_widget_get_visible( GTK_WIDGET( window ) ) )
892    {
893        gtk_window_resize( window, gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_WIDTH ),
894                                   gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_HEIGHT ) );
895        gtk_window_move( window, gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_X ),
896                                 gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_Y ) );
897        gtr_widget_set_visible( GTK_WIDGET( window ), TRUE );
898    }
899    gtr_window_present( window );
900}
901
902static void
903hideMainWindow( struct cbdata * cbdata )
904{
905    GtkWindow * window = cbdata->wind;
906    gtk_window_set_skip_taskbar_hint( window, TRUE );
907    gtr_widget_set_visible( GTK_WIDGET( window ), FALSE );
908    cbdata->is_iconified = true;
909}
910
911static void
912toggleMainWindow( struct cbdata * cbdata )
913{
914    if( cbdata->is_iconified )
915        presentMainWindow( cbdata );
916    else
917        hideMainWindow( cbdata );
918}
919
920static gboolean
921winclose( GtkWidget * w    UNUSED,
922          GdkEvent * event UNUSED,
923          gpointer         gdata )
924{
925    struct cbdata * cbdata = gdata;
926
927    if( cbdata->icon != NULL )
928        gtr_action_activate ( "toggle-main-window" );
929    else
930        on_app_exit( cbdata );
931
932    return TRUE; /* don't propagate event further */
933}
934
935static void
936rowChangedCB( GtkTreeModel  * model UNUSED,
937              GtkTreePath   * path,
938              GtkTreeIter   * iter  UNUSED,
939              gpointer        gdata )
940{
941    struct cbdata * data = gdata;
942
943    if( gtk_tree_selection_path_is_selected ( data->sel, path ) )
944        refresh_actions_soon( data );
945}
946
947static void
948on_drag_data_received( GtkWidget         * widget          UNUSED,
949                       GdkDragContext    * drag_context,
950                       gint                x               UNUSED,
951                       gint                y               UNUSED,
952                       GtkSelectionData  * selection_data,
953                       guint               info            UNUSED,
954                       guint               time_,
955                       gpointer            gdata )
956{
957    int i;
958    gboolean success = FALSE;
959    GSList * files = NULL;
960    struct cbdata * data = gdata;
961    char ** uris = gtk_selection_data_get_uris( selection_data );
962
963    /* try to add the filename URIs... */
964    for( i=0; uris && uris[i]; ++i )
965    {
966        const char * uri = uris[i];
967        char * filename = g_filename_from_uri( uri, NULL, NULL );
968
969        if( filename && g_file_test( filename, G_FILE_TEST_EXISTS ) )
970        {
971            files = g_slist_append( files, g_strdup( filename ) );
972            success = TRUE;
973        }
974        else if( tr_urlIsValid( uri, -1 ) || gtr_is_magnet_link( uri ) )
975        {
976            gtr_core_add_from_url( data->core, uri );
977            success = TRUE;
978        }
979
980        g_free( filename );
981    }
982
983    if( files )
984        gtr_core_add_list_defaults( data->core, g_slist_reverse( files ), TRUE );
985
986    gtr_core_torrents_added( data->core );
987    gtk_drag_finish( drag_context, success, FALSE, time_ );
988
989    /* cleanup */
990    g_strfreev( uris );
991}
992
993static void
994main_window_setup( struct cbdata * cbdata, TrWindow * wind )
995{
996    GtkWidget * w;
997    GtkTreeModel * model;
998    GtkTreeSelection * sel;
999
1000    g_assert( NULL == cbdata->wind );
1001    cbdata->wind = GTK_WINDOW( wind );
1002    cbdata->sel = sel = GTK_TREE_SELECTION( gtr_window_get_selection( cbdata->wind ) );
1003
1004    g_signal_connect( sel, "changed", G_CALLBACK( on_selection_changed ), cbdata );
1005    on_selection_changed( sel, cbdata );
1006    model = gtr_core_model( cbdata->core );
1007    g_signal_connect( model, "row-changed", G_CALLBACK( rowChangedCB ), cbdata );
1008    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
1009    refresh_actions( cbdata );
1010
1011    /* register to handle URIs that get dragged onto our main window */
1012    w = GTK_WIDGET( wind );
1013    gtk_drag_dest_set( w, GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY );
1014    gtk_drag_dest_add_uri_targets( w );
1015    g_signal_connect( w, "drag-data-received", G_CALLBACK(on_drag_data_received), cbdata );
1016}
1017
1018static gboolean
1019on_session_closed( gpointer gdata )
1020{
1021    struct cbdata * cbdata = gdata;
1022
1023    /* shutdown the gui */
1024    while( cbdata->details != NULL ) {
1025        struct DetailsDialogHandle * h = cbdata->details->data;
1026        gtk_widget_destroy( h->dialog );
1027    }
1028
1029    if( cbdata->prefs )
1030        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
1031    if( cbdata->wind )
1032        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
1033    g_object_unref( cbdata->core );
1034    if( cbdata->icon )
1035        g_object_unref( cbdata->icon );
1036    g_slist_foreach( cbdata->error_list, (GFunc)g_free, NULL );
1037    g_slist_free( cbdata->error_list );
1038    g_slist_foreach( cbdata->duplicates_list, (GFunc)g_free, NULL );
1039    g_slist_free( cbdata->duplicates_list );
1040    g_free( cbdata );
1041
1042    gtk_main_quit( );
1043
1044    return FALSE;
1045}
1046
1047static gpointer
1048session_close_threadfunc( gpointer gdata )
1049{
1050    /* since tr_sessionClose() is a blocking function,
1051     * call it from another thread... when it's done,
1052     * punt the GUI teardown back to the GTK+ thread */
1053    struct cbdata * cbdata = gdata;
1054    gdk_threads_enter( );
1055    gtr_core_close( cbdata->core );
1056    gdk_threads_add_idle( on_session_closed, gdata );
1057    gdk_threads_leave( );
1058    return NULL;
1059}
1060
1061static void
1062exit_now_cb( GtkWidget *w UNUSED, gpointer data UNUSED )
1063{
1064    exit( 0 );
1065}
1066
1067static void
1068on_app_exit( gpointer vdata )
1069{
1070    GtkWidget *r, *p, *b, *w, *c;
1071    struct cbdata *cbdata = vdata;
1072
1073    /* stop the update timer */
1074    if( cbdata->timer )
1075    {
1076        g_source_remove( cbdata->timer );
1077        cbdata->timer = 0;
1078    }
1079
1080    c = GTK_WIDGET( cbdata->wind );
1081    gtk_container_remove( GTK_CONTAINER( c ), gtk_bin_get_child( GTK_BIN( c ) ) );
1082
1083    r = gtk_alignment_new( 0.5, 0.5, 0.01, 0.01 );
1084    gtk_container_add( GTK_CONTAINER( c ), r );
1085
1086    p = gtk_table_new( 3, 2, FALSE );
1087    gtk_table_set_col_spacings( GTK_TABLE( p ), GUI_PAD_BIG );
1088    gtk_container_add( GTK_CONTAINER( r ), p );
1089
1090    w = gtk_image_new_from_stock( GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG );
1091    gtk_table_attach_defaults( GTK_TABLE( p ), w, 0, 1, 0, 2 );
1092
1093    w = gtk_label_new( NULL );
1094    gtk_label_set_markup( GTK_LABEL( w ), _( "<b>Closing Connections</b>" ) );
1095    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
1096    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 0, 1 );
1097
1098    w = gtk_label_new( _( "Sending upload/download totals to tracker..." ) );
1099    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
1100    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 1, 2 );
1101
1102    b = gtk_alignment_new( 0.0, 1.0, 0.01, 0.01 );
1103    w = gtk_button_new_with_mnemonic( _( "_Quit Now" ) );
1104    g_signal_connect( w, "clicked", G_CALLBACK( exit_now_cb ), NULL );
1105    gtk_container_add( GTK_CONTAINER( b ), w );
1106    gtk_table_attach( GTK_TABLE( p ), b, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 10 );
1107
1108    gtk_widget_show_all( r );
1109    gtk_widget_grab_focus( w );
1110
1111    /* clear the UI */
1112    gtr_core_clear( cbdata->core );
1113
1114    /* ensure the window is in its previous position & size.
1115     * this seems to be necessary because changing the main window's
1116     * child seems to unset the size */
1117    gtk_window_resize( cbdata->wind, gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_WIDTH ),
1118                                     gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_HEIGHT ) );
1119    gtk_window_move( cbdata->wind, gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_X ),
1120                                   gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_Y ) );
1121
1122    /* shut down libT */
1123    g_thread_create( session_close_threadfunc, vdata, TRUE, NULL );
1124}
1125
1126static void
1127show_torrent_errors( GtkWindow * window, const char * primary, GSList ** files )
1128{
1129    GSList * l;
1130    GtkWidget * w;
1131    GString * s = g_string_new( NULL );
1132    const char * leader = g_slist_length( *files ) > 1
1133                        ? gtr_get_unicode_string( GTR_UNICODE_BULLET )
1134                        : "";
1135
1136    for( l=*files; l!=NULL; l=l->next )
1137        g_string_append_printf( s, "%s %s\n", leader, (const char*)l->data );
1138
1139    w = gtk_message_dialog_new( window,
1140                                GTK_DIALOG_DESTROY_WITH_PARENT,
1141                                GTK_MESSAGE_ERROR,
1142                                GTK_BUTTONS_CLOSE,
1143                                "%s", primary );
1144    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
1145                                              "%s", s->str );
1146    g_signal_connect_swapped( w, "response",
1147                              G_CALLBACK( gtk_widget_destroy ), w );
1148    gtk_widget_show( w );
1149    g_string_free( s, TRUE );
1150
1151    g_slist_foreach( *files, (GFunc)g_free, NULL );
1152    g_slist_free( *files );
1153    *files = NULL;
1154}
1155
1156static void
1157flush_torrent_errors( struct cbdata * cbdata )
1158{
1159    if( cbdata->error_list )
1160        show_torrent_errors( cbdata->wind,
1161                              ngettext( "Couldn't add corrupt torrent",
1162                                        "Couldn't add corrupt torrents",
1163                                        g_slist_length( cbdata->error_list ) ),
1164                              &cbdata->error_list );
1165
1166    if( cbdata->duplicates_list )
1167        show_torrent_errors( cbdata->wind,
1168                              ngettext( "Couldn't add duplicate torrent",
1169                                        "Couldn't add duplicate torrents",
1170                                        g_slist_length( cbdata->duplicates_list ) ),
1171                              &cbdata->duplicates_list );
1172}
1173
1174static void
1175on_core_error( TrCore * core UNUSED, guint code, const char * msg, struct cbdata * c )
1176{
1177    switch( code )
1178    {
1179        case TR_PARSE_ERR:
1180            c->error_list =
1181                g_slist_append( c->error_list, g_path_get_basename( msg ) );
1182            break;
1183
1184        case TR_PARSE_DUPLICATE:
1185            c->duplicates_list = g_slist_append( c->duplicates_list, g_strdup( msg ) );
1186            break;
1187
1188        case TR_CORE_ERR_NO_MORE_TORRENTS:
1189            flush_torrent_errors( c );
1190            break;
1191
1192        default:
1193            g_assert_not_reached( );
1194            break;
1195    }
1196}
1197
1198static gboolean
1199on_main_window_focus_in( GtkWidget      * widget UNUSED,
1200                         GdkEventFocus  * event  UNUSED,
1201                         gpointer                gdata )
1202{
1203    struct cbdata * cbdata = gdata;
1204
1205    if( cbdata->wind )
1206        gtk_window_set_urgency_hint( cbdata->wind, FALSE );
1207    return FALSE;
1208}
1209
1210static void
1211on_add_torrent( TrCore * core, tr_ctor * ctor, gpointer gdata )
1212{
1213    struct cbdata * cbdata = gdata;
1214    GtkWidget * w = gtr_torrent_options_dialog_new( cbdata->wind, core, ctor );
1215
1216    g_signal_connect( w, "focus-in-event",
1217                      G_CALLBACK( on_main_window_focus_in ),  cbdata );
1218    if( cbdata->wind )
1219        gtk_window_set_urgency_hint( cbdata->wind, TRUE );
1220
1221    gtk_widget_show( w );
1222}
1223
1224static void
1225on_prefs_changed( TrCore * core UNUSED, const char * key, gpointer data )
1226{
1227    struct cbdata * cbdata = data;
1228    tr_session * tr = gtr_core_session( cbdata->core );
1229
1230    if( !strcmp( key, TR_PREFS_KEY_ENCRYPTION ) )
1231    {
1232        tr_sessionSetEncryption( tr, gtr_pref_int_get( key ) );
1233    }
1234    else if( !strcmp( key, TR_PREFS_KEY_DOWNLOAD_DIR ) )
1235    {
1236        tr_sessionSetDownloadDir( tr, gtr_pref_string_get( key ) );
1237    }
1238    else if( !strcmp( key, TR_PREFS_KEY_MSGLEVEL ) )
1239    {
1240        tr_setMessageLevel( gtr_pref_int_get( key ) );
1241    }
1242    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT ) )
1243    {
1244        tr_sessionSetPeerPort( tr, gtr_pref_int_get( key ) );
1245    }
1246    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_ENABLED ) )
1247    {
1248        tr_blocklistSetEnabled( tr, gtr_pref_flag_get( key ) );
1249    }
1250    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_URL ) )
1251    {
1252        tr_blocklistSetURL( tr, gtr_pref_string_get( key ) );
1253    }
1254    else if( !strcmp( key, PREF_KEY_SHOW_TRAY_ICON ) )
1255    {
1256        const int show = gtr_pref_flag_get( key );
1257        if( show && !cbdata->icon )
1258            cbdata->icon = gtr_icon_new( cbdata->core );
1259        else if( !show && cbdata->icon ) {
1260            g_object_unref( cbdata->icon );
1261            cbdata->icon = NULL;
1262        }
1263    }
1264    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_ENABLED ) )
1265    {
1266        tr_sessionLimitSpeed( tr, TR_DOWN, gtr_pref_flag_get( key ) );
1267    }
1268    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_KBps ) )
1269    {
1270        tr_sessionSetSpeedLimit_KBps( tr, TR_DOWN, gtr_pref_int_get( key ) );
1271    }
1272    else if( !strcmp( key, TR_PREFS_KEY_USPEED_ENABLED ) )
1273    {
1274        tr_sessionLimitSpeed( tr, TR_UP, gtr_pref_flag_get( key ) );
1275    }
1276    else if( !strcmp( key, TR_PREFS_KEY_USPEED_KBps ) )
1277    {
1278        tr_sessionSetSpeedLimit_KBps( tr, TR_UP, gtr_pref_int_get( key ) );
1279    }
1280    else if( !strcmp( key, TR_PREFS_KEY_RATIO_ENABLED ) )
1281    {
1282        tr_sessionSetRatioLimited( tr, gtr_pref_flag_get( key ) );
1283    }
1284    else if( !strcmp( key, TR_PREFS_KEY_RATIO ) )
1285    {
1286        tr_sessionSetRatioLimit( tr, gtr_pref_double_get( key ) );
1287    }
1288    else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT ) )
1289    {
1290        tr_sessionSetIdleLimit( tr, gtr_pref_int_get( key ) );
1291    }
1292    else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT_ENABLED ) )
1293    {
1294        tr_sessionSetIdleLimited( tr, gtr_pref_flag_get( key ) );
1295    }
1296    else if( !strcmp( key, TR_PREFS_KEY_PORT_FORWARDING ) )
1297    {
1298        tr_sessionSetPortForwardingEnabled( tr, gtr_pref_flag_get( key ) );
1299    }
1300    else if( !strcmp( key, TR_PREFS_KEY_PEX_ENABLED ) )
1301    {
1302        tr_sessionSetPexEnabled( tr, gtr_pref_flag_get( key ) );
1303    }
1304    else if( !strcmp( key, TR_PREFS_KEY_RENAME_PARTIAL_FILES ) )
1305    {
1306        tr_sessionSetIncompleteFileNamingEnabled( tr, gtr_pref_flag_get( key ) );
1307    }
1308    else if( !strcmp( key, TR_PREFS_KEY_DOWNLOAD_QUEUE_SIZE ) )
1309    {
1310        tr_sessionSetQueueSize( tr, TR_DOWN, gtr_pref_int_get( key ) );
1311    }
1312    else if( !strcmp( key, TR_PREFS_KEY_QUEUE_STALLED_MINUTES ) )
1313    {
1314        tr_sessionSetQueueStalledMinutes( tr, gtr_pref_int_get( key ) );
1315    }
1316    else if( !strcmp( key, TR_PREFS_KEY_DHT_ENABLED ) )
1317    {
1318        tr_sessionSetDHTEnabled( tr, gtr_pref_flag_get( key ) );
1319    }
1320    else if( !strcmp( key, TR_PREFS_KEY_UTP_ENABLED ) )
1321    {
1322        tr_sessionSetUTPEnabled( tr, gtr_pref_flag_get( key ) );
1323    }
1324    else if( !strcmp( key, TR_PREFS_KEY_LPD_ENABLED ) )
1325    {
1326        tr_sessionSetLPDEnabled( tr, gtr_pref_flag_get( key ) );
1327    }
1328    else if( !strcmp( key, TR_PREFS_KEY_RPC_PORT ) )
1329    {
1330        tr_sessionSetRPCPort( tr, gtr_pref_int_get( key ) );
1331    }
1332    else if( !strcmp( key, TR_PREFS_KEY_RPC_ENABLED ) )
1333    {
1334        tr_sessionSetRPCEnabled( tr, gtr_pref_flag_get( key ) );
1335    }
1336    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST ) )
1337    {
1338        tr_sessionSetRPCWhitelist( tr, gtr_pref_string_get( key ) );
1339    }
1340    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST_ENABLED ) )
1341    {
1342        tr_sessionSetRPCWhitelistEnabled( tr, gtr_pref_flag_get( key ) );
1343    }
1344    else if( !strcmp( key, TR_PREFS_KEY_RPC_USERNAME ) )
1345    {
1346        tr_sessionSetRPCUsername( tr, gtr_pref_string_get( key ) );
1347    }
1348    else if( !strcmp( key, TR_PREFS_KEY_RPC_PASSWORD ) )
1349    {
1350        tr_sessionSetRPCPassword( tr, gtr_pref_string_get( key ) );
1351    }
1352    else if( !strcmp( key, TR_PREFS_KEY_RPC_AUTH_REQUIRED ) )
1353    {
1354        tr_sessionSetRPCPasswordEnabled( tr, gtr_pref_flag_get( key ) );
1355    }
1356    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_UP_KBps ) )
1357    {
1358        tr_sessionSetAltSpeed_KBps( tr, TR_UP, gtr_pref_int_get( key ) );
1359    }
1360    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps ) )
1361    {
1362        tr_sessionSetAltSpeed_KBps( tr, TR_DOWN, gtr_pref_int_get( key ) );
1363    }
1364    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_ENABLED ) )
1365    {
1366        const gboolean b = gtr_pref_flag_get( key );
1367        tr_sessionUseAltSpeed( tr, b );
1368        gtr_action_set_toggled( key, b );
1369    }
1370    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN ) )
1371    {
1372        tr_sessionSetAltSpeedBegin( tr, gtr_pref_int_get( key ) );
1373    }
1374    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_END ) )
1375    {
1376        tr_sessionSetAltSpeedEnd( tr, gtr_pref_int_get( key ) );
1377    }
1378    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED ) )
1379    {
1380        tr_sessionUseAltSpeedTime( tr, gtr_pref_flag_get( key ) );
1381    }
1382    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_DAY ) )
1383    {
1384        tr_sessionSetAltSpeedDay( tr, gtr_pref_int_get( key ) );
1385    }
1386    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START ) )
1387    {
1388        tr_sessionSetPeerPortRandomOnStart( tr, gtr_pref_flag_get( key ) );
1389    }
1390    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR ) )
1391    {
1392        tr_sessionSetIncompleteDir( tr, gtr_pref_string_get( key ) );
1393    }
1394    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED ) )
1395    {
1396        tr_sessionSetIncompleteDirEnabled( tr, gtr_pref_flag_get( key ) );
1397    }
1398    else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED ) )
1399    {
1400        tr_sessionSetTorrentDoneScriptEnabled( tr, gtr_pref_flag_get( key ) );
1401    }
1402    else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME ) )
1403    {
1404        tr_sessionSetTorrentDoneScript( tr, gtr_pref_string_get( key ) );
1405    }
1406    else if( !strcmp( key, TR_PREFS_KEY_START) )
1407    {
1408        tr_sessionSetPaused( tr, !gtr_pref_flag_get( key ) );
1409    }
1410    else if( !strcmp( key, TR_PREFS_KEY_TRASH_ORIGINAL ) )
1411    {
1412        tr_sessionSetDeleteSource( tr, gtr_pref_flag_get( key ) );
1413    }
1414}
1415
1416static gboolean
1417update_model_once( gpointer gdata )
1418{
1419    struct cbdata *data = gdata;
1420
1421    /* update the torrent data in the model */
1422    gtr_core_update( data->core );
1423
1424    /* refresh the main window's statusbar and toolbar buttons */
1425    if( data->wind != NULL )
1426        gtr_window_refresh( data->wind );
1427
1428    /* update the actions */
1429        refresh_actions( data );
1430
1431    /* update the status tray icon */
1432    if( data->icon != NULL )
1433        gtr_icon_refresh( data->icon );
1434
1435    data->update_model_soon_tag = 0;
1436    return FALSE;
1437}
1438
1439static void
1440update_model_soon( gpointer gdata )
1441{
1442    struct cbdata *data = gdata;
1443
1444    if( data->update_model_soon_tag == 0 )
1445        data->update_model_soon_tag = gdk_threads_add_idle( update_model_once, data );
1446}
1447
1448static gboolean
1449update_model_loop( gpointer gdata )
1450{
1451    const gboolean done = global_sigcount;
1452
1453    if( !done )
1454        update_model_once( gdata );
1455
1456    return !done;
1457}
1458
1459static void
1460show_about_dialog( GtkWindow * parent )
1461{
1462    GtkWidget * d;
1463    const char * website_uri = "http://www.transmissionbt.com/";
1464    const char * authors[] = {
1465        "Jordan Lee (Backend; GTK+)",
1466        "Mitchell Livingston (Backend; OS X)",
1467        NULL
1468    };
1469
1470    d = g_object_new( GTK_TYPE_ABOUT_DIALOG,
1471                      "authors", authors,
1472                      "comments", _( "A fast and easy BitTorrent client" ),
1473                      "copyright", _( "Copyright (c) The Transmission Project" ),
1474                      "logo-icon-name", MY_CONFIG_NAME,
1475                      "name", g_get_application_name( ),
1476                      /* Translators: translate "translator-credits" as your name
1477                         to have it appear in the credits in the "About"
1478                         dialog */
1479                      "translator-credits", _( "translator-credits" ),
1480                      "version", LONG_VERSION_STRING,
1481                      "website", website_uri,
1482                      "website-label", website_uri,
1483#ifdef SHOW_LICENSE
1484                      "license", LICENSE,
1485                      "wrap-license", TRUE,
1486#endif
1487                      NULL );
1488    gtk_window_set_transient_for( GTK_WINDOW( d ), parent );
1489    g_signal_connect_swapped( d, "response", G_CALLBACK (gtk_widget_destroy), d );
1490    gtk_widget_show( d );
1491}
1492
1493static void
1494append_id_to_benc_list( GtkTreeModel * m, GtkTreePath * path UNUSED,
1495                        GtkTreeIter * iter, gpointer list )
1496{
1497    tr_torrent * tor = NULL;
1498    gtk_tree_model_get( m, iter, MC_TORRENT, &tor, -1 );
1499    tr_bencListAddInt( list, tr_torrentId( tor ) );
1500}
1501
1502static gboolean
1503call_rpc_for_selected_torrents( struct cbdata * data, const char * method )
1504{
1505    tr_benc top, *args, *ids;
1506    gboolean invoked = FALSE;
1507    GtkTreeSelection * s = data->sel;
1508    tr_session * session = gtr_core_session( data->core );
1509
1510    tr_bencInitDict( &top, 2 );
1511    tr_bencDictAddStr( &top, "method", method );
1512    args = tr_bencDictAddDict( &top, "arguments", 1 );
1513    ids = tr_bencDictAddList( args, "ids", 0 );
1514    gtk_tree_selection_selected_foreach( s, append_id_to_benc_list, ids );
1515
1516    if( tr_bencListSize( ids ) != 0 )
1517    {
1518        int json_len;
1519        char * json = tr_bencToStr( &top, TR_FMT_JSON_LEAN, &json_len );
1520        tr_rpc_request_exec_json( session, json, json_len, NULL, NULL );
1521        g_free( json );
1522        invoked = TRUE;
1523    }
1524
1525    tr_bencFree( &top );
1526    return invoked;
1527}
1528
1529static void
1530open_folder_foreach( GtkTreeModel * model, GtkTreePath * path UNUSED,
1531                     GtkTreeIter * iter, gpointer core )
1532{
1533    int id;
1534    gtk_tree_model_get( model, iter, MC_TORRENT_ID, &id, -1 );
1535    gtr_core_open_folder( core, id );
1536}
1537
1538static gboolean
1539on_message_window_closed( void )
1540{
1541    gtr_action_set_toggled( "toggle-message-log", FALSE );
1542    return FALSE;
1543}
1544
1545static void
1546accumulate_selected_torrents( GtkTreeModel  * model, GtkTreePath   * path UNUSED,
1547                              GtkTreeIter   * iter, gpointer        gdata )
1548{
1549    int id;
1550    GSList ** data = gdata;
1551
1552    gtk_tree_model_get( model, iter, MC_TORRENT_ID, &id, -1 );
1553    *data = g_slist_append( *data, GINT_TO_POINTER( id ) );
1554}
1555
1556static void
1557remove_selected( struct cbdata * data, gboolean delete_files )
1558{
1559    GSList * l = NULL;
1560
1561    gtk_tree_selection_selected_foreach( data->sel, accumulate_selected_torrents, &l );
1562
1563    if( l != NULL )
1564        gtr_confirm_remove( data->wind, data->core, l, delete_files );
1565}
1566
1567static void
1568start_all_torrents( struct cbdata * data )
1569{
1570    tr_session * session = gtr_core_session( data->core );
1571    const char * cmd = "{ \"method\": \"torrent-start\" }";
1572    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1573}
1574
1575static void
1576pause_all_torrents( struct cbdata * data )
1577{
1578    tr_session * session = gtr_core_session( data->core );
1579    const char * cmd = "{ \"method\": \"torrent-stop\" }";
1580    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1581}
1582
1583static tr_torrent*
1584get_first_selected_torrent( struct cbdata * data )
1585{
1586    tr_torrent * tor = NULL;
1587    GtkTreeModel * m;
1588    GList * l = gtk_tree_selection_get_selected_rows( data->sel, &m );
1589    if( l != NULL ) {
1590        GtkTreePath * p = l->data;
1591        GtkTreeIter i;
1592        if( gtk_tree_model_get_iter( m, &i, p ) )
1593            gtk_tree_model_get( m, &i, MC_TORRENT, &tor, -1 );
1594    }
1595    g_list_foreach( l, (GFunc)gtk_tree_path_free, NULL );
1596    g_list_free( l );
1597    return tor;
1598}
1599
1600static void
1601copy_magnet_link_to_clipboard( GtkWidget * w, tr_torrent * tor )
1602{
1603    char * magnet = tr_torrentGetMagnetLink( tor );
1604    GdkDisplay * display = gtk_widget_get_display( w );
1605    GdkAtom selection;
1606    GtkClipboard * clipboard;
1607
1608    /* this is The Right Thing for copy/paste... */
1609    selection = GDK_SELECTION_CLIPBOARD;
1610    clipboard = gtk_clipboard_get_for_display( display, selection );
1611    gtk_clipboard_set_text( clipboard, magnet, -1 );
1612
1613    /* ...but people using plain ol' X need this instead */
1614    selection = GDK_SELECTION_PRIMARY;
1615    clipboard = gtk_clipboard_get_for_display( display, selection );
1616    gtk_clipboard_set_text( clipboard, magnet, -1 );
1617
1618    /* cleanup */
1619    tr_free( magnet );
1620}
1621
1622void
1623gtr_actions_handler( const char * action_name, gpointer user_data )
1624{
1625    struct cbdata * data = user_data;
1626    gboolean        changed = FALSE;
1627
1628    if( !strcmp( action_name, "open-torrent-from-url" ) )
1629    {
1630        GtkWidget * w = gtr_torrent_open_from_url_dialog_new( data->wind, data->core );
1631        gtk_widget_show( w );
1632    }
1633    else if(  !strcmp( action_name, "open-torrent-menu" )
1634      || !strcmp( action_name, "open-torrent-toolbar" ) )
1635    {
1636        GtkWidget * w = gtr_torrent_open_from_file_dialog_new( data->wind, data->core );
1637        gtk_widget_show( w );
1638    }
1639    else if( !strcmp( action_name, "show-stats" ) )
1640    {
1641        GtkWidget * dialog = gtr_stats_dialog_new( data->wind, data->core );
1642        gtk_widget_show( dialog );
1643    }
1644    else if( !strcmp( action_name, "donate" ) )
1645    {
1646        gtr_open_uri( "http://www.transmissionbt.com/donate.php" );
1647    }
1648    else if( !strcmp( action_name, "pause-all-torrents" ) )
1649    {
1650        pause_all_torrents( data );
1651    }
1652    else if( !strcmp( action_name, "start-all-torrents" ) )
1653    {
1654        start_all_torrents( data );
1655    }
1656    else if( !strcmp( action_name, "copy-magnet-link-to-clipboard" ) )
1657    {
1658        tr_torrent * tor = get_first_selected_torrent( data );
1659        if( tor != NULL )
1660        {
1661            copy_magnet_link_to_clipboard( GTK_WIDGET( data->wind ), tor );
1662        }
1663    }
1664    else if( !strcmp( action_name, "relocate-torrent" ) )
1665    {
1666        GSList * ids = getSelectedTorrentIds( data );
1667        if( ids != NULL )
1668        {
1669            GtkWindow * parent = data->wind;
1670            GtkWidget * w = gtr_relocate_dialog_new( parent, data->core, ids );
1671            gtk_widget_show( w );
1672        }
1673    }
1674    else if( !strcmp( action_name, "torrent-start" )
1675          || !strcmp( action_name, "torrent-start-now" )
1676          || !strcmp( action_name, "torrent-stop" )
1677          || !strcmp( action_name, "torrent-reannounce" )
1678          || !strcmp( action_name, "torrent-verify" )
1679          || !strcmp( action_name, "queue-move-top" )
1680          || !strcmp( action_name, "queue-move-up" )
1681          || !strcmp( action_name, "queue-move-down" )
1682          || !strcmp( action_name, "queue-move-bottom" ) )
1683    {
1684        changed |= call_rpc_for_selected_torrents( data, action_name );
1685    }
1686    else if( !strcmp( action_name, "open-torrent-folder" ) )
1687    {
1688        gtk_tree_selection_selected_foreach( data->sel, open_folder_foreach, data->core );
1689    }
1690    else if( !strcmp( action_name, "show-torrent-properties" ) )
1691    {
1692        show_details_dialog_for_selected_torrents( data );
1693    }
1694    else if( !strcmp( action_name, "new-torrent" ) )
1695    {
1696        GtkWidget * w = gtr_torrent_creation_dialog_new( data->wind, data->core );
1697        gtk_widget_show( w );
1698    }
1699    else if( !strcmp( action_name, "remove-torrent" ) )
1700    {
1701        remove_selected( data, FALSE );
1702    }
1703    else if( !strcmp( action_name, "delete-torrent" ) )
1704    {
1705        remove_selected( data, TRUE );
1706    }
1707    else if( !strcmp( action_name, "quit" ) )
1708    {
1709        on_app_exit( data );
1710    }
1711    else if( !strcmp( action_name, "select-all" ) )
1712    {
1713        gtk_tree_selection_select_all( data->sel );
1714    }
1715    else if( !strcmp( action_name, "deselect-all" ) )
1716    {
1717        gtk_tree_selection_unselect_all( data->sel );
1718    }
1719    else if( !strcmp( action_name, "edit-preferences" ) )
1720    {
1721        if( NULL == data->prefs )
1722        {
1723            data->prefs = gtr_prefs_dialog_new( data->wind, G_OBJECT( data->core ) );
1724            g_signal_connect( data->prefs, "destroy",
1725                              G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
1726        }
1727        gtr_window_present( GTK_WINDOW( data->prefs ) );
1728    }
1729    else if( !strcmp( action_name, "toggle-message-log" ) )
1730    {
1731        if( !data->msgwin )
1732        {
1733            GtkWidget * win = gtr_message_log_window_new( data->wind, data->core );
1734            g_signal_connect( win, "destroy", G_CALLBACK( on_message_window_closed ), NULL );
1735            data->msgwin = win;
1736        }
1737        else
1738        {
1739            gtr_action_set_toggled( "toggle-message-log", FALSE );
1740            gtk_widget_destroy( data->msgwin );
1741            data->msgwin = NULL;
1742        }
1743    }
1744    else if( !strcmp( action_name, "show-about-dialog" ) )
1745    {
1746        show_about_dialog( data->wind );
1747    }
1748    else if( !strcmp ( action_name, "help" ) )
1749    {
1750        gtr_open_uri( gtr_get_help_uri( ) );
1751    }
1752    else if( !strcmp( action_name, "toggle-main-window" ) )
1753    {
1754        toggleMainWindow( data );
1755    }
1756    else if( !strcmp( action_name, "present-main-window" ) )
1757    {
1758        presentMainWindow( data );
1759    }
1760    else g_error ( "Unhandled action: %s", action_name );
1761
1762    if( changed )
1763        update_model_soon( data );
1764}
Note: See TracBrowser for help on using the repository browser.