source: trunk/gtk/main.c @ 11368

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

(trunk) #3697 "make blocklist URL configurable" -- implemented in GTK+, Qt, and RPC

  • Property svn:keywords set to Date Rev Author Id
File size: 54.7 KB
Line 
1/******************************************************************************
2 * $Id: main.c 11368 2010-10-31 17:16:12Z 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        gtr_widget_set_visible( GTK_WIDGET( window ), TRUE );
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        gtr_widget_set_visible( GTK_WIDGET( window ), FALSE );
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, TR_PREFS_KEY_BLOCKLIST_URL ) )
1200    {
1201        tr_blocklistSetURL( tr, pref_string_get( key ) );
1202    }
1203    else if( !strcmp( key, PREF_KEY_SHOW_TRAY_ICON ) )
1204    {
1205        const int show = pref_flag_get( key );
1206        if( show && !cbdata->icon )
1207            cbdata->icon = tr_icon_new( cbdata->core );
1208        else if( !show && cbdata->icon ) {
1209            g_object_unref( cbdata->icon );
1210            cbdata->icon = NULL;
1211        }
1212    }
1213    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_ENABLED ) )
1214    {
1215        tr_sessionLimitSpeed( tr, TR_DOWN, pref_flag_get( key ) );
1216    }
1217    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_KBps ) )
1218    {
1219        tr_sessionSetSpeedLimit_KBps( tr, TR_DOWN, pref_int_get( key ) );
1220    }
1221    else if( !strcmp( key, TR_PREFS_KEY_USPEED_ENABLED ) )
1222    {
1223        tr_sessionLimitSpeed( tr, TR_UP, pref_flag_get( key ) );
1224    }
1225    else if( !strcmp( key, TR_PREFS_KEY_USPEED_KBps ) )
1226    {
1227        tr_sessionSetSpeedLimit_KBps( tr, TR_UP, pref_int_get( key ) );
1228    }
1229    else if( !strcmp( key, TR_PREFS_KEY_RATIO_ENABLED ) )
1230    {
1231        tr_sessionSetRatioLimited( tr, pref_flag_get( key ) );
1232    }
1233    else if( !strcmp( key, TR_PREFS_KEY_RATIO ) )
1234    {
1235        tr_sessionSetRatioLimit( tr, pref_double_get( key ) );
1236    }
1237    else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT ) )
1238    {
1239        tr_sessionSetIdleLimit( tr, pref_int_get( key ) );
1240    }
1241    else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT_ENABLED ) )
1242    {
1243        tr_sessionSetIdleLimited( tr, pref_flag_get( key ) );
1244    }
1245    else if( !strcmp( key, TR_PREFS_KEY_PORT_FORWARDING ) )
1246    {
1247        tr_sessionSetPortForwardingEnabled( tr, pref_flag_get( key ) );
1248    }
1249    else if( !strcmp( key, TR_PREFS_KEY_PEX_ENABLED ) )
1250    {
1251        tr_sessionSetPexEnabled( tr, pref_flag_get( key ) );
1252    }
1253    else if( !strcmp( key, TR_PREFS_KEY_RENAME_PARTIAL_FILES ) )
1254    {
1255        tr_sessionSetIncompleteFileNamingEnabled( tr, pref_flag_get( key ) );
1256    }
1257    else if( !strcmp( key, TR_PREFS_KEY_DHT_ENABLED ) )
1258    {
1259        tr_sessionSetDHTEnabled( tr, pref_flag_get( key ) );
1260    }
1261    else if( !strcmp( key, TR_PREFS_KEY_LPD_ENABLED ) )
1262    {
1263        tr_sessionSetLPDEnabled( tr, pref_flag_get( key ) );
1264    }
1265    else if( !strcmp( key, TR_PREFS_KEY_RPC_PORT ) )
1266    {
1267        tr_sessionSetRPCPort( tr, pref_int_get( key ) );
1268    }
1269    else if( !strcmp( key, TR_PREFS_KEY_RPC_ENABLED ) )
1270    {
1271        tr_sessionSetRPCEnabled( tr, pref_flag_get( key ) );
1272    }
1273    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST ) )
1274    {
1275        tr_sessionSetRPCWhitelist( tr, pref_string_get( key ) );
1276    }
1277    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST_ENABLED ) )
1278    {
1279        tr_sessionSetRPCWhitelistEnabled( tr, pref_flag_get( key ) );
1280    }
1281    else if( !strcmp( key, TR_PREFS_KEY_RPC_USERNAME ) )
1282    {
1283        tr_sessionSetRPCUsername( tr, pref_string_get( key ) );
1284    }
1285    else if( !strcmp( key, TR_PREFS_KEY_RPC_PASSWORD ) )
1286    {
1287        tr_sessionSetRPCPassword( tr, pref_string_get( key ) );
1288    }
1289    else if( !strcmp( key, TR_PREFS_KEY_RPC_AUTH_REQUIRED ) )
1290    {
1291        tr_sessionSetRPCPasswordEnabled( tr, pref_flag_get( key ) );
1292    }
1293    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_UP_KBps ) )
1294    {
1295        tr_sessionSetAltSpeed_KBps( tr, TR_UP, pref_int_get( key ) );
1296    }
1297    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps ) )
1298    {
1299        tr_sessionSetAltSpeed_KBps( tr, TR_DOWN, pref_int_get( key ) );
1300    }
1301    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_ENABLED ) )
1302    {
1303        const gboolean b = pref_flag_get( key );
1304        tr_sessionUseAltSpeed( tr, b );
1305        action_toggle( key, b );
1306    }
1307    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN ) )
1308    {
1309        tr_sessionSetAltSpeedBegin( tr, pref_int_get( key ) );
1310    }
1311    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_END ) )
1312    {
1313        tr_sessionSetAltSpeedEnd( tr, pref_int_get( key ) );
1314    }
1315    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED ) )
1316    {
1317        tr_sessionUseAltSpeedTime( tr, pref_flag_get( key ) );
1318    }
1319    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_DAY ) )
1320    {
1321        tr_sessionSetAltSpeedDay( tr, pref_int_get( key ) );
1322    }
1323    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START ) )
1324    {
1325        tr_sessionSetPeerPortRandomOnStart( tr, pref_flag_get( key ) );
1326    }
1327    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR ) )
1328    {
1329        tr_sessionSetIncompleteDir( tr, pref_string_get( key ) );
1330    }
1331    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED ) )
1332    {
1333        tr_sessionSetIncompleteDirEnabled( tr, pref_flag_get( key ) );
1334    }
1335    else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED ) )
1336    {
1337        tr_sessionSetTorrentDoneScriptEnabled( tr, pref_flag_get( key ) );
1338    }
1339    else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME ) )
1340    {
1341        tr_sessionSetTorrentDoneScript( tr, pref_string_get( key ) );
1342    }
1343    else if( !strcmp( key, TR_PREFS_KEY_START) )
1344    {
1345        tr_sessionSetPaused( tr, !pref_flag_get( key ) );
1346    }
1347    else if( !strcmp( key, TR_PREFS_KEY_TRASH_ORIGINAL ) )
1348    {
1349        tr_sessionSetDeleteSource( tr, pref_flag_get( key ) );
1350    }
1351}
1352
1353static gboolean
1354updatemodel( gpointer gdata )
1355{
1356    struct cbdata *data = gdata;
1357    const gboolean done = data->isClosing || global_sigcount;
1358
1359    if( !done )
1360    {
1361        /* update the torrent data in the model */
1362        tr_core_update( data->core );
1363
1364        /* update the main window's statusbar and toolbar buttons */
1365        if( data->wind != NULL )
1366            tr_window_update( data->wind );
1367
1368        /* update the actions */
1369        refreshActions( data );
1370
1371        /* update the status tray icon */
1372        if( data->icon != NULL )
1373            tr_icon_refresh( data->icon );
1374    }
1375
1376    return !done;
1377}
1378
1379static void
1380onUriClicked( GtkAboutDialog * u UNUSED, const gchar * uri, gpointer u2 UNUSED )
1381{
1382    gtr_open_uri( uri );
1383}
1384
1385static void
1386about( GtkWindow * parent )
1387{
1388    GtkWidget * d;
1389    const char * website_uri = "http://www.transmissionbt.com/";
1390    const char * authors[] = {
1391        "Charles Kerr (Backend; GTK+)",
1392        "Mitchell Livingston (Backend; OS X)",
1393        NULL
1394    };
1395
1396    gtk_about_dialog_set_url_hook( onUriClicked, NULL, NULL );
1397
1398    d = g_object_new( GTK_TYPE_ABOUT_DIALOG,
1399                      "authors", authors,
1400                      "comments", _( "A fast and easy BitTorrent client" ),
1401                      "copyright", _( "Copyright (c) The Transmission Project" ),
1402                      "logo-icon-name", MY_CONFIG_NAME,
1403                      "name", g_get_application_name( ),
1404                      /* Translators: translate "translator-credits" as your name
1405                         to have it appear in the credits in the "About"
1406                         dialog */
1407                      "translator-credits", _( "translator-credits" ),
1408                      "version", LONG_VERSION_STRING,
1409                      "website", website_uri,
1410                      "website-label", website_uri,
1411#ifdef SHOW_LICENSE
1412                      "license", LICENSE,
1413                      "wrap-license", TRUE,
1414#endif
1415                      NULL );
1416    gtk_window_set_transient_for( GTK_WINDOW( d ), parent );
1417    g_signal_connect_swapped( d, "response", G_CALLBACK (gtk_widget_destroy), d );
1418    gtk_widget_show_all( d );
1419}
1420
1421static void
1422appendIdToBencList( GtkTreeModel * m, GtkTreePath * path UNUSED,
1423                    GtkTreeIter * iter, gpointer list )
1424{
1425    tr_torrent * tor = NULL;
1426    gtk_tree_model_get( m, iter, MC_TORRENT_RAW, &tor, -1 );
1427    tr_bencListAddInt( list, tr_torrentId( tor ) );
1428}
1429
1430static gboolean
1431rpcOnSelectedTorrents( struct cbdata * data, const char * method )
1432{
1433    tr_benc top, *args, *ids;
1434    gboolean invoked = FALSE;
1435    tr_session * session = tr_core_session( data->core );
1436    GtkTreeSelection * s = tr_window_get_selection( data->wind );
1437
1438    tr_bencInitDict( &top, 2 );
1439    tr_bencDictAddStr( &top, "method", method );
1440    args = tr_bencDictAddDict( &top, "arguments", 1 );
1441    ids = tr_bencDictAddList( args, "ids", 0 );
1442    gtk_tree_selection_selected_foreach( s, appendIdToBencList, ids );
1443
1444    if( tr_bencListSize( ids ) != 0 )
1445    {
1446        int json_len;
1447        char * json = tr_bencToStr( &top, TR_FMT_JSON_LEAN, &json_len );
1448        tr_rpc_request_exec_json( session, json, json_len, NULL, NULL );
1449        g_free( json );
1450        invoked = TRUE;
1451    }
1452
1453    tr_bencFree( &top );
1454    return invoked;
1455}
1456
1457static void
1458openFolderForeach( GtkTreeModel *           model,
1459                   GtkTreePath  * path      UNUSED,
1460                   GtkTreeIter *            iter,
1461                   gpointer       user_data UNUSED )
1462{
1463    TrTorrent * gtor = NULL;
1464
1465    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1466    tr_torrent_open_folder( gtor );
1467    g_object_unref( G_OBJECT( gtor ) );
1468}
1469
1470static gboolean
1471msgwinclosed( void )
1472{
1473    action_toggle( "toggle-message-log", FALSE );
1474    return FALSE;
1475}
1476
1477static void
1478accumulateSelectedTorrents( GtkTreeModel * model,
1479                            GtkTreePath  * path UNUSED,
1480                            GtkTreeIter  * iter,
1481                            gpointer       gdata )
1482{
1483    GSList ** data = ( GSList** ) gdata;
1484    TrTorrent * gtor = NULL;
1485
1486    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1487    *data = g_slist_prepend( *data, gtor );
1488    g_object_unref( G_OBJECT( gtor ) );
1489}
1490
1491static void
1492removeSelected( struct cbdata * data, gboolean delete_files )
1493{
1494    GSList * l = NULL;
1495    GtkTreeSelection * s = tr_window_get_selection( data->wind );
1496
1497    gtk_tree_selection_selected_foreach( s, accumulateSelectedTorrents, &l );
1498
1499    if( l != NULL ) {
1500        l = g_slist_reverse( l );
1501        confirmRemove( data->wind, data->core, l, delete_files );
1502    }
1503}
1504
1505static void
1506startAllTorrents( struct cbdata * data )
1507{
1508    tr_session * session = tr_core_session( data->core );
1509    const char * cmd = "{ \"method\": \"torrent-start\" }";
1510    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1511}
1512
1513static void
1514pauseAllTorrents( struct cbdata * data )
1515{
1516    tr_session * session = tr_core_session( data->core );
1517    const char * cmd = "{ \"method\": \"torrent-stop\" }";
1518    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1519}
1520
1521static tr_torrent*
1522getFirstSelectedTorrent( struct cbdata * data )
1523{
1524    tr_torrent * tor = NULL;
1525    GtkTreeSelection * s = tr_window_get_selection( data->wind );
1526    GtkTreeModel * m;
1527    GList * l = gtk_tree_selection_get_selected_rows( s, &m );
1528    if( l != NULL ) {
1529        GtkTreePath * p = l->data;
1530        GtkTreeIter i;
1531        if( gtk_tree_model_get_iter( m, &i, p ) )
1532            gtk_tree_model_get( m, &i, MC_TORRENT_RAW, &tor, -1 );
1533    }
1534    g_list_foreach( l, (GFunc)gtk_tree_path_free, NULL );
1535    g_list_free( l );
1536    return tor;
1537}
1538
1539static void
1540detailsClosed( gpointer gdata, GObject * dead )
1541{
1542    struct cbdata * data = gdata;
1543    struct DetailsDialogHandle * h = findDetailsDialogFromWidget( data, dead );
1544
1545    if( h != NULL )
1546    {
1547        data->details = g_slist_remove( data->details, h );
1548        g_free( h->key );
1549        g_free( h );
1550    }
1551}
1552
1553static void
1554copyMagnetLinkToClipboard( GtkWidget * w, tr_torrent * tor )
1555{
1556    char * magnet = tr_torrentGetMagnetLink( tor );
1557    GdkDisplay * display = gtk_widget_get_display( w );
1558    GdkAtom selection;
1559    GtkClipboard * clipboard;
1560
1561    /* this is The Right Thing for copy/paste... */
1562    selection = GDK_SELECTION_CLIPBOARD;
1563    clipboard = gtk_clipboard_get_for_display( display, selection );
1564    gtk_clipboard_set_text( clipboard, magnet, -1 );
1565
1566    /* ...but people using plain ol' X need this instead */
1567    selection = GDK_SELECTION_PRIMARY;
1568    clipboard = gtk_clipboard_get_for_display( display, selection );
1569    gtk_clipboard_set_text( clipboard, magnet, -1 );
1570
1571    /* cleanup */
1572    tr_free( magnet );
1573}
1574
1575void
1576doAction( const char * action_name, gpointer user_data )
1577{
1578    struct cbdata * data = user_data;
1579    gboolean        changed = FALSE;
1580
1581    if( !strcmp( action_name, "add-torrent-from-url" ) )
1582    {
1583        addURLDialog( data->wind, data->core );
1584    }
1585    else if(  !strcmp( action_name, "add-torrent-menu" )
1586      || !strcmp( action_name, "add-torrent-toolbar" ) )
1587    {
1588        addDialog( data->wind, data->core );
1589    }
1590    else if( !strcmp( action_name, "show-stats" ) )
1591    {
1592        GtkWidget * dialog = stats_dialog_create( data->wind, data->core );
1593        gtk_widget_show( dialog );
1594    }
1595    else if( !strcmp( action_name, "donate" ) )
1596    {
1597        gtr_open_uri( "http://www.transmissionbt.com/donate.php" );
1598    }
1599    else if( !strcmp( action_name, "pause-all-torrents" ) )
1600    {
1601        pauseAllTorrents( data );
1602    }
1603    else if( !strcmp( action_name, "start-all-torrents" ) )
1604    {
1605        startAllTorrents( data );
1606    }
1607    else if( !strcmp( action_name, "copy-magnet-link-to-clipboard" ) )
1608    {
1609        tr_torrent * tor = getFirstSelectedTorrent( data );
1610        if( tor != NULL )
1611        {
1612            copyMagnetLinkToClipboard( GTK_WIDGET( data->wind ), tor );
1613        }
1614    }
1615    else if( !strcmp( action_name, "relocate-torrent" ) )
1616    {
1617        GSList * ids = getSelectedTorrentIds( data );
1618        if( ids != NULL )
1619        {
1620            GtkWindow * parent = GTK_WINDOW( data->wind );
1621            GtkWidget * w = gtr_relocate_dialog_new( parent, data->core, ids );
1622            gtk_widget_show( w );
1623        }
1624    }
1625    else if( !strcmp( action_name, "start-torrent" ) )
1626    {
1627        changed |= rpcOnSelectedTorrents( data, "torrent-start" );
1628    }
1629    else if( !strcmp( action_name, "pause-torrent" ) )
1630    {
1631        changed |= rpcOnSelectedTorrents( data, "torrent-stop" );
1632    }
1633    else if( !strcmp( action_name, "verify-torrent" ) )
1634    {
1635        changed |= rpcOnSelectedTorrents( data, "torrent-verify" );
1636    }
1637    else if( !strcmp( action_name, "update-tracker" ) )
1638    {
1639        changed |= rpcOnSelectedTorrents( data, "torrent-reannounce" );
1640    }
1641    else if( !strcmp( action_name, "open-torrent-folder" ) )
1642    {
1643        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1644        gtk_tree_selection_selected_foreach( s, openFolderForeach, data );
1645    }
1646    else if( !strcmp( action_name, "show-torrent-properties" ) )
1647    {
1648        GtkWidget * w;
1649        GSList * ids = getSelectedTorrentIds( data );
1650        struct DetailsDialogHandle * h = findDetailsDialogFromIds( data, ids );
1651        if( h != NULL )
1652            w = h->dialog;
1653        else {
1654            h = g_new( struct DetailsDialogHandle, 1 );
1655            h->key = getDetailsDialogKey( ids );
1656            h->dialog = w = torrent_inspector_new( GTK_WINDOW( data->wind ), data->core );
1657            torrent_inspector_set_torrents( w, ids );
1658            data->details = g_slist_append( data->details, h );
1659            g_object_weak_ref( G_OBJECT( w ), detailsClosed, data );
1660        }
1661        gtk_window_present( GTK_WINDOW( w ) );
1662        g_slist_free( ids );
1663    }
1664    else if( !strcmp( action_name, "new-torrent" ) )
1665    {
1666        GtkWidget * w = make_meta_ui( GTK_WINDOW( data->wind ), data->core );
1667        gtk_widget_show_all( w );
1668    }
1669    else if( !strcmp( action_name, "remove-torrent" ) )
1670    {
1671        removeSelected( data, FALSE );
1672    }
1673    else if( !strcmp( action_name, "delete-torrent" ) )
1674    {
1675        removeSelected( data, TRUE );
1676    }
1677    else if( !strcmp( action_name, "quit" ) )
1678    {
1679        maybeaskquit( data );
1680    }
1681    else if( !strcmp( action_name, "select-all" ) )
1682    {
1683        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1684        gtk_tree_selection_select_all( s );
1685    }
1686    else if( !strcmp( action_name, "deselect-all" ) )
1687    {
1688        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1689        gtk_tree_selection_unselect_all( s );
1690    }
1691    else if( !strcmp( action_name, "edit-preferences" ) )
1692    {
1693        if( NULL == data->prefs )
1694        {
1695            data->prefs = tr_prefs_dialog_new( G_OBJECT( data->core ),
1696                                               data->wind );
1697            g_signal_connect( data->prefs, "destroy",
1698                              G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
1699            gtk_widget_show( GTK_WIDGET( data->prefs ) );
1700        }
1701    }
1702    else if( !strcmp( action_name, "toggle-message-log" ) )
1703    {
1704        if( !data->msgwin )
1705        {
1706            GtkWidget * win = msgwin_new( data->core, data->wind );
1707            g_signal_connect( win, "destroy", G_CALLBACK( msgwinclosed ), NULL );
1708            data->msgwin = win;
1709        }
1710        else
1711        {
1712            action_toggle( "toggle-message-log", FALSE );
1713            gtk_widget_destroy( data->msgwin );
1714            data->msgwin = NULL;
1715        }
1716    }
1717    else if( !strcmp( action_name, "show-about-dialog" ) )
1718    {
1719        about( data->wind );
1720    }
1721    else if( !strcmp ( action_name, "help" ) )
1722    {
1723        gtr_open_uri( gtr_get_help_uri( ) );
1724    }
1725    else if( !strcmp( action_name, "toggle-main-window" ) )
1726    {
1727        toggleMainWindow( data );
1728    }
1729    else g_error ( "Unhandled action: %s", action_name );
1730
1731    if( changed )
1732        updatemodel( data );
1733}
Note: See TracBrowser for help on using the repository browser.