source: branches/2.1x/gtk/main.c @ 11282

Last change on this file since 11282 was 11282, checked in by charles, 12 years ago

(2.1x gtk) #3589 "drag-and-dropping a magnet link onto the GTK+ client doesn't work" -- fixed.

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