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

Last change on this file since 11552 was 11552, checked in by charles, 11 years ago

backport #3837, #3838, #3834, #3719, #3826 to the 2.1x branch

  • Property svn:keywords set to Date Rev Author Id
File size: 54.7 KB
Line 
1/******************************************************************************
2 * $Id: main.c 11552 2010-12-20 12:26:18Z 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    gpointer            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 size_t total = tr_core_get_torrent_count( data->core );
340        const size_t 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    return pref_flag_get( PREF_KEY_ASKQUIT )
840        && tr_core_get_active_torrent_count( data->core );
841}
842
843static void
844maybeaskquit( struct cbdata * cbdata )
845{
846    if( !shouldConfirmBeforeExiting( cbdata ) )
847        wannaquit( cbdata );
848    else {
849        if( cbdata->quit_dialog == NULL ) {
850            cbdata->quit_dialog = askquit( cbdata->core, cbdata->wind, wannaquit, cbdata );
851            g_object_add_weak_pointer( G_OBJECT( cbdata->quit_dialog ), &cbdata->quit_dialog );
852        }
853        gtk_window_present( GTK_WINDOW( cbdata->quit_dialog ) );
854    }
855}
856
857static gboolean
858winclose( GtkWidget * w    UNUSED,
859          GdkEvent * event UNUSED,
860          gpointer         gdata )
861{
862    struct cbdata * cbdata = gdata;
863
864    if( cbdata->icon != NULL )
865        action_activate ( "toggle-main-window" );
866    else
867        maybeaskquit( cbdata );
868
869    return TRUE; /* don't propagate event further */
870}
871
872static void
873rowChangedCB( GtkTreeModel  * model UNUSED,
874              GtkTreePath   * path,
875              GtkTreeIter   * iter  UNUSED,
876              gpointer        gdata )
877{
878    struct cbdata * data = gdata;
879    if( gtk_tree_selection_path_is_selected ( data->sel, path ) )
880        refreshActions( gdata );
881}
882
883static void
884on_drag_data_received( GtkWidget         * widget          UNUSED,
885                       GdkDragContext    * drag_context,
886                       gint                x               UNUSED,
887                       gint                y               UNUSED,
888                       GtkSelectionData  * selection_data,
889                       guint               info            UNUSED,
890                       guint               time_,
891                       gpointer            gdata )
892{
893    int i;
894    gboolean success = FALSE;
895    GSList * filenames = NULL;
896    struct cbdata * data = gdata;
897    char ** uris = gtk_selection_data_get_uris( selection_data );
898
899    /* try to add the filename URIs... */
900    for( i=0; uris && uris[i]; ++i )
901    {
902        const char * uri = uris[i];
903        char * filename = g_filename_from_uri( uri, NULL, NULL );
904
905        if( filename && g_file_test( filename, G_FILE_TEST_EXISTS ) )
906        {
907            filenames = g_slist_append( filenames, g_strdup( filename ) );
908            success = TRUE;
909        }
910        else if( tr_urlIsValid( uri, -1 ) || gtr_is_magnet_link( uri ) )
911        {
912            tr_core_add_from_url( data->core, uri );
913            success = TRUE;
914        }
915    }
916
917    if( filenames )
918        tr_core_add_list_defaults( data->core, g_slist_reverse( filenames ), TRUE );
919
920    tr_core_torrents_added( data->core );
921    gtk_drag_finish( drag_context, success, FALSE, time_ );
922
923    /* cleanup */
924    g_strfreev( uris );
925}
926
927static void
928winsetup( struct cbdata * cbdata, TrWindow * wind )
929{
930    GtkWidget * w;
931    GtkTreeModel * model;
932    GtkTreeSelection * sel;
933
934    g_assert( NULL == cbdata->wind );
935    cbdata->wind = GTK_WINDOW( wind );
936    cbdata->sel = sel = GTK_TREE_SELECTION( tr_window_get_selection( cbdata->wind ) );
937
938    g_signal_connect( sel, "changed", G_CALLBACK( selectionChangedCB ), cbdata );
939    selectionChangedCB( sel, cbdata );
940    model = tr_core_model( cbdata->core );
941    g_signal_connect( model, "row-changed", G_CALLBACK( rowChangedCB ), cbdata );
942    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
943    refreshActions( cbdata );
944
945    /* register to handle URIs that get dragged onto our main window */
946    w = GTK_WIDGET( wind );
947    gtk_drag_dest_set( w, GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY );
948    gtk_drag_dest_add_uri_targets( w );
949    g_signal_connect( w, "drag-data-received", G_CALLBACK(on_drag_data_received), cbdata );
950}
951
952static gboolean
953onSessionClosed( gpointer gdata )
954{
955    struct cbdata * cbdata = gdata;
956
957    /* shutdown the gui */
958    while( cbdata->details != NULL ) {
959        struct DetailsDialogHandle * h = cbdata->details->data;
960        gtk_widget_destroy( h->dialog );
961    }
962
963    if( cbdata->prefs )
964        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
965    if( cbdata->wind )
966        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
967    g_object_unref( cbdata->core );
968    if( cbdata->icon )
969        g_object_unref( cbdata->icon );
970    if( cbdata->errqueue ) {
971        g_slist_foreach( cbdata->errqueue, (GFunc)g_free, NULL );
972        g_slist_free( cbdata->errqueue );
973    }
974    if( cbdata->dupqueue ) {
975        g_slist_foreach( cbdata->dupqueue, (GFunc)g_free, NULL );
976        g_slist_free( cbdata->dupqueue );
977    }
978    g_free( cbdata );
979
980    gtk_main_quit( );
981
982    return FALSE;
983}
984
985static gpointer
986sessionCloseThreadFunc( gpointer gdata )
987{
988    /* since tr_sessionClose() is a blocking function,
989     * call it from another thread... when it's done,
990     * punt the GUI teardown back to the GTK+ thread */
991    struct cbdata * cbdata = gdata;
992    gdk_threads_enter( );
993    tr_core_close( cbdata->core );
994    gtr_idle_add( onSessionClosed, gdata );
995    gdk_threads_leave( );
996    return NULL;
997}
998
999static void
1000do_exit_cb( GtkWidget *w  UNUSED,
1001            gpointer data UNUSED )
1002{
1003    exit( 0 );
1004}
1005
1006static void
1007wannaquit( gpointer vdata )
1008{
1009    GtkWidget *r, *p, *b, *w, *c;
1010    struct cbdata *cbdata = vdata;
1011
1012    /* stop the update timer */
1013    if( cbdata->timer )
1014    {
1015        g_source_remove( cbdata->timer );
1016        cbdata->timer = 0;
1017    }
1018
1019    c = GTK_WIDGET( cbdata->wind );
1020    gtk_container_remove( GTK_CONTAINER( c ), gtk_bin_get_child( GTK_BIN( c ) ) );
1021
1022    r = gtk_alignment_new( 0.5, 0.5, 0.01, 0.01 );
1023    gtk_container_add( GTK_CONTAINER( c ), r );
1024
1025    p = gtk_table_new( 3, 2, FALSE );
1026    gtk_table_set_col_spacings( GTK_TABLE( p ), GUI_PAD_BIG );
1027    gtk_container_add( GTK_CONTAINER( r ), p );
1028
1029    w = gtk_image_new_from_stock( GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG );
1030    gtk_table_attach_defaults( GTK_TABLE( p ), w, 0, 1, 0, 2 );
1031
1032    w = gtk_label_new( NULL );
1033    gtk_label_set_markup( GTK_LABEL( w ), _( "<b>Closing Connections</b>" ) );
1034    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
1035    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 0, 1 );
1036
1037    w = gtk_label_new( _( "Sending upload/download totals to tracker..." ) );
1038    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
1039    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 1, 2 );
1040
1041    b = gtk_alignment_new( 0.0, 1.0, 0.01, 0.01 );
1042    w = gtk_button_new_with_mnemonic( _( "_Quit Now" ) );
1043    g_signal_connect( w, "clicked", G_CALLBACK( do_exit_cb ), NULL );
1044    gtk_container_add( GTK_CONTAINER( b ), w );
1045    gtk_table_attach( GTK_TABLE( p ), b, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 10 );
1046
1047    gtk_widget_show_all( r );
1048    gtk_widget_grab_focus( w );
1049
1050    /* clear the UI */
1051    gtk_list_store_clear( GTK_LIST_STORE( tr_core_model( cbdata->core ) ) );
1052
1053    /* ensure the window is in its previous position & size.
1054     * this seems to be necessary because changing the main window's
1055     * child seems to unset the size */
1056    gtk_window_resize( cbdata->wind, pref_int_get( PREF_KEY_MAIN_WINDOW_WIDTH ),
1057                                     pref_int_get( PREF_KEY_MAIN_WINDOW_HEIGHT ) );
1058    gtk_window_move( cbdata->wind, pref_int_get( PREF_KEY_MAIN_WINDOW_X ),
1059                                   pref_int_get( PREF_KEY_MAIN_WINDOW_Y ) );
1060
1061    /* shut down libT */
1062    g_thread_create( sessionCloseThreadFunc, vdata, TRUE, NULL );
1063}
1064
1065static void
1066flushAddTorrentErrors( GtkWindow *  window,
1067                       const char * primary,
1068                       GSList **    files )
1069{
1070    GString *   s = g_string_new( NULL );
1071    GSList *    l;
1072    GtkWidget * w;
1073
1074    if( g_slist_length( *files ) > 1 ) {
1075        for( l=*files; l!=NULL; l=l->next )
1076            g_string_append_printf( s, "\xE2\x88\x99 %s\n", (const char*)l->data );
1077    } else {
1078        for( l=*files; l!=NULL; l=l->next )
1079            g_string_append_printf( s, "%s\n", (const char*)l->data );
1080    }
1081    w = gtk_message_dialog_new( window,
1082                                GTK_DIALOG_DESTROY_WITH_PARENT,
1083                                GTK_MESSAGE_ERROR,
1084                                GTK_BUTTONS_CLOSE,
1085                                "%s", primary );
1086    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
1087                                              "%s", s->str );
1088    g_signal_connect_swapped( w, "response",
1089                              G_CALLBACK( gtk_widget_destroy ), w );
1090    gtk_widget_show_all( w );
1091    g_string_free( s, TRUE );
1092
1093    g_slist_foreach( *files, (GFunc)g_free, NULL );
1094    g_slist_free( *files );
1095    *files = NULL;
1096}
1097
1098static void
1099showTorrentErrors( struct cbdata * cbdata )
1100{
1101    if( cbdata->errqueue )
1102        flushAddTorrentErrors( GTK_WINDOW( cbdata->wind ),
1103                               gtr_ngettext( "Couldn't add corrupt torrent",
1104                                             "Couldn't add corrupt torrents",
1105                                             g_slist_length( cbdata->errqueue ) ),
1106                               &cbdata->errqueue );
1107
1108    if( cbdata->dupqueue )
1109        flushAddTorrentErrors( GTK_WINDOW( cbdata->wind ),
1110                               gtr_ngettext( "Couldn't add duplicate torrent",
1111                                             "Couldn't add duplicate torrents",
1112                                             g_slist_length( cbdata->dupqueue ) ),
1113                               &cbdata->dupqueue );
1114}
1115
1116static void
1117coreerr( TrCore * core UNUSED, guint code, const char * msg, struct cbdata * c )
1118{
1119    switch( code )
1120    {
1121        case TR_PARSE_ERR:
1122            c->errqueue =
1123                g_slist_append( c->errqueue, g_path_get_basename( msg ) );
1124            break;
1125
1126        case TR_PARSE_DUPLICATE:
1127            c->dupqueue = g_slist_append( c->dupqueue, g_strdup( msg ) );
1128            break;
1129
1130        case TR_CORE_ERR_NO_MORE_TORRENTS:
1131            showTorrentErrors( c );
1132            break;
1133
1134        default:
1135            g_assert_not_reached( );
1136            break;
1137    }
1138}
1139
1140#if GTK_CHECK_VERSION( 2, 8, 0 )
1141static void
1142on_main_window_focus_in( GtkWidget      * widget UNUSED,
1143                         GdkEventFocus  * event  UNUSED,
1144                         gpointer                gdata )
1145{
1146    struct cbdata * cbdata = gdata;
1147
1148    if( cbdata->wind )
1149        gtk_window_set_urgency_hint( GTK_WINDOW( cbdata->wind ), FALSE );
1150}
1151
1152#endif
1153
1154static void
1155onAddTorrent( TrCore *  core,
1156              tr_ctor * ctor,
1157              gpointer  gdata )
1158{
1159    struct cbdata * cbdata = gdata;
1160    GtkWidget *     w = addSingleTorrentDialog( cbdata->wind, core, ctor );
1161
1162#if GTK_CHECK_VERSION( 2, 8, 0 )
1163    g_signal_connect( w, "focus-in-event",
1164                      G_CALLBACK( on_main_window_focus_in ),  cbdata );
1165    if( cbdata->wind )
1166        gtk_window_set_urgency_hint( cbdata->wind, TRUE );
1167#endif
1168}
1169
1170static void
1171prefschanged( TrCore * core UNUSED, const char * key, gpointer data )
1172{
1173    struct cbdata * cbdata = data;
1174    tr_session * tr = tr_core_session( cbdata->core );
1175
1176    if( !strcmp( key, TR_PREFS_KEY_ENCRYPTION ) )
1177    {
1178        tr_sessionSetEncryption( tr, pref_int_get( key ) );
1179    }
1180    else if( !strcmp( key, TR_PREFS_KEY_DOWNLOAD_DIR ) )
1181    {
1182        tr_sessionSetDownloadDir( tr, pref_string_get( key ) );
1183    }
1184    else if( !strcmp( key, TR_PREFS_KEY_MSGLEVEL ) )
1185    {
1186        tr_setMessageLevel( pref_int_get( key ) );
1187    }
1188    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT ) )
1189    {
1190        tr_sessionSetPeerPort( tr, pref_int_get( key ) );
1191    }
1192    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_ENABLED ) )
1193    {
1194        tr_blocklistSetEnabled( tr, pref_flag_get( key ) );
1195    }
1196    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_URL ) )
1197    {
1198        tr_blocklistSetURL( tr, pref_string_get( key ) );
1199    }
1200    else if( !strcmp( key, PREF_KEY_SHOW_TRAY_ICON ) )
1201    {
1202        const int show = pref_flag_get( key );
1203        if( show && !cbdata->icon )
1204            cbdata->icon = tr_icon_new( cbdata->core );
1205        else if( !show && cbdata->icon ) {
1206            g_object_unref( cbdata->icon );
1207            cbdata->icon = NULL;
1208        }
1209    }
1210    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_ENABLED ) )
1211    {
1212        tr_sessionLimitSpeed( tr, TR_DOWN, pref_flag_get( key ) );
1213    }
1214    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_KBps ) )
1215    {
1216        tr_sessionSetSpeedLimit_KBps( tr, TR_DOWN, pref_int_get( key ) );
1217    }
1218    else if( !strcmp( key, TR_PREFS_KEY_USPEED_ENABLED ) )
1219    {
1220        tr_sessionLimitSpeed( tr, TR_UP, pref_flag_get( key ) );
1221    }
1222    else if( !strcmp( key, TR_PREFS_KEY_USPEED_KBps ) )
1223    {
1224        tr_sessionSetSpeedLimit_KBps( tr, TR_UP, pref_int_get( key ) );
1225    }
1226    else if( !strcmp( key, TR_PREFS_KEY_RATIO_ENABLED ) )
1227    {
1228        tr_sessionSetRatioLimited( tr, pref_flag_get( key ) );
1229    }
1230    else if( !strcmp( key, TR_PREFS_KEY_RATIO ) )
1231    {
1232        tr_sessionSetRatioLimit( tr, pref_double_get( key ) );
1233    }
1234    else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT ) )
1235    {
1236        tr_sessionSetIdleLimit( tr, pref_int_get( key ) );
1237    }
1238    else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT_ENABLED ) )
1239    {
1240        tr_sessionSetIdleLimited( tr, pref_flag_get( key ) );
1241    }
1242    else if( !strcmp( key, TR_PREFS_KEY_PORT_FORWARDING ) )
1243    {
1244        tr_sessionSetPortForwardingEnabled( tr, pref_flag_get( key ) );
1245    }
1246    else if( !strcmp( key, TR_PREFS_KEY_PEX_ENABLED ) )
1247    {
1248        tr_sessionSetPexEnabled( tr, pref_flag_get( key ) );
1249    }
1250    else if( !strcmp( key, TR_PREFS_KEY_RENAME_PARTIAL_FILES ) )
1251    {
1252        tr_sessionSetIncompleteFileNamingEnabled( tr, pref_flag_get( key ) );
1253    }
1254    else if( !strcmp( key, TR_PREFS_KEY_DHT_ENABLED ) )
1255    {
1256        tr_sessionSetDHTEnabled( tr, pref_flag_get( key ) );
1257    }
1258    else if( !strcmp( key, TR_PREFS_KEY_LPD_ENABLED ) )
1259    {
1260        tr_sessionSetLPDEnabled( tr, pref_flag_get( key ) );
1261    }
1262    else if( !strcmp( key, TR_PREFS_KEY_RPC_PORT ) )
1263    {
1264        tr_sessionSetRPCPort( tr, pref_int_get( key ) );
1265    }
1266    else if( !strcmp( key, TR_PREFS_KEY_RPC_ENABLED ) )
1267    {
1268        tr_sessionSetRPCEnabled( tr, pref_flag_get( key ) );
1269    }
1270    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST ) )
1271    {
1272        tr_sessionSetRPCWhitelist( tr, pref_string_get( key ) );
1273    }
1274    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST_ENABLED ) )
1275    {
1276        tr_sessionSetRPCWhitelistEnabled( tr, pref_flag_get( key ) );
1277    }
1278    else if( !strcmp( key, TR_PREFS_KEY_RPC_USERNAME ) )
1279    {
1280        tr_sessionSetRPCUsername( tr, pref_string_get( key ) );
1281    }
1282    else if( !strcmp( key, TR_PREFS_KEY_RPC_PASSWORD ) )
1283    {
1284        tr_sessionSetRPCPassword( tr, pref_string_get( key ) );
1285    }
1286    else if( !strcmp( key, TR_PREFS_KEY_RPC_AUTH_REQUIRED ) )
1287    {
1288        tr_sessionSetRPCPasswordEnabled( tr, pref_flag_get( key ) );
1289    }
1290    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_UP_KBps ) )
1291    {
1292        tr_sessionSetAltSpeed_KBps( tr, TR_UP, pref_int_get( key ) );
1293    }
1294    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps ) )
1295    {
1296        tr_sessionSetAltSpeed_KBps( tr, TR_DOWN, pref_int_get( key ) );
1297    }
1298    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_ENABLED ) )
1299    {
1300        const gboolean b = pref_flag_get( key );
1301        tr_sessionUseAltSpeed( tr, b );
1302        action_toggle( key, b );
1303    }
1304    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN ) )
1305    {
1306        tr_sessionSetAltSpeedBegin( tr, pref_int_get( key ) );
1307    }
1308    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_END ) )
1309    {
1310        tr_sessionSetAltSpeedEnd( tr, pref_int_get( key ) );
1311    }
1312    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED ) )
1313    {
1314        tr_sessionUseAltSpeedTime( tr, pref_flag_get( key ) );
1315    }
1316    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_DAY ) )
1317    {
1318        tr_sessionSetAltSpeedDay( tr, pref_int_get( key ) );
1319    }
1320    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START ) )
1321    {
1322        tr_sessionSetPeerPortRandomOnStart( tr, pref_flag_get( key ) );
1323    }
1324    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR ) )
1325    {
1326        tr_sessionSetIncompleteDir( tr, pref_string_get( key ) );
1327    }
1328    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED ) )
1329    {
1330        tr_sessionSetIncompleteDirEnabled( tr, pref_flag_get( key ) );
1331    }
1332    else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED ) )
1333    {
1334        tr_sessionSetTorrentDoneScriptEnabled( tr, pref_flag_get( key ) );
1335    }
1336    else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME ) )
1337    {
1338        tr_sessionSetTorrentDoneScript( tr, pref_string_get( key ) );
1339    }
1340    else if( !strcmp( key, TR_PREFS_KEY_START) )
1341    {
1342        tr_sessionSetPaused( tr, !pref_flag_get( key ) );
1343    }
1344    else if( !strcmp( key, TR_PREFS_KEY_TRASH_ORIGINAL ) )
1345    {
1346        tr_sessionSetDeleteSource( tr, pref_flag_get( key ) );
1347    }
1348}
1349
1350static gboolean
1351updatemodel( gpointer gdata )
1352{
1353    struct cbdata *data = gdata;
1354    const gboolean done = data->isClosing || global_sigcount;
1355
1356    if( !done )
1357    {
1358        /* update the torrent data in the model */
1359        tr_core_update( data->core );
1360
1361        /* update the main window's statusbar and toolbar buttons */
1362        if( data->wind != NULL )
1363            tr_window_update( data->wind );
1364
1365        /* update the actions */
1366        refreshActions( data );
1367
1368        /* update the status tray icon */
1369        if( data->icon != NULL )
1370            tr_icon_refresh( data->icon );
1371    }
1372
1373    return !done;
1374}
1375
1376static void
1377onUriClicked( GtkAboutDialog * u UNUSED, const gchar * uri, gpointer u2 UNUSED )
1378{
1379    gtr_open_uri( uri );
1380}
1381
1382static void
1383about( GtkWindow * parent )
1384{
1385    GtkWidget * d;
1386    const char * website_uri = "http://www.transmissionbt.com/";
1387    const char * authors[] = {
1388        "Charles Kerr (Backend; GTK+)",
1389        "Mitchell Livingston (Backend; OS X)",
1390        NULL
1391    };
1392
1393    gtk_about_dialog_set_url_hook( onUriClicked, NULL, NULL );
1394
1395    d = g_object_new( GTK_TYPE_ABOUT_DIALOG,
1396                      "authors", authors,
1397                      "comments", _( "A fast and easy BitTorrent client" ),
1398                      "copyright", _( "Copyright (c) The Transmission Project" ),
1399                      "logo-icon-name", MY_CONFIG_NAME,
1400                      "name", g_get_application_name( ),
1401                      /* Translators: translate "translator-credits" as your name
1402                         to have it appear in the credits in the "About"
1403                         dialog */
1404                      "translator-credits", _( "translator-credits" ),
1405                      "version", LONG_VERSION_STRING,
1406                      "website", website_uri,
1407                      "website-label", website_uri,
1408#ifdef SHOW_LICENSE
1409                      "license", LICENSE,
1410                      "wrap-license", TRUE,
1411#endif
1412                      NULL );
1413    gtk_window_set_transient_for( GTK_WINDOW( d ), parent );
1414    g_signal_connect_swapped( d, "response", G_CALLBACK (gtk_widget_destroy), d );
1415    gtk_widget_show_all( d );
1416}
1417
1418static void
1419appendIdToBencList( GtkTreeModel * m, GtkTreePath * path UNUSED,
1420                    GtkTreeIter * iter, gpointer list )
1421{
1422    tr_torrent * tor = NULL;
1423    gtk_tree_model_get( m, iter, MC_TORRENT_RAW, &tor, -1 );
1424    tr_bencListAddInt( list, tr_torrentId( tor ) );
1425}
1426
1427static gboolean
1428rpcOnSelectedTorrents( struct cbdata * data, const char * method )
1429{
1430    tr_benc top, *args, *ids;
1431    gboolean invoked = FALSE;
1432    tr_session * session = tr_core_session( data->core );
1433    GtkTreeSelection * s = tr_window_get_selection( data->wind );
1434
1435    tr_bencInitDict( &top, 2 );
1436    tr_bencDictAddStr( &top, "method", method );
1437    args = tr_bencDictAddDict( &top, "arguments", 1 );
1438    ids = tr_bencDictAddList( args, "ids", 0 );
1439    gtk_tree_selection_selected_foreach( s, appendIdToBencList, ids );
1440
1441    if( tr_bencListSize( ids ) != 0 )
1442    {
1443        int json_len;
1444        char * json = tr_bencToStr( &top, TR_FMT_JSON_LEAN, &json_len );
1445        tr_rpc_request_exec_json( session, json, json_len, NULL, NULL );
1446        g_free( json );
1447        invoked = TRUE;
1448    }
1449
1450    tr_bencFree( &top );
1451    return invoked;
1452}
1453
1454static void
1455openFolderForeach( GtkTreeModel *           model,
1456                   GtkTreePath  * path      UNUSED,
1457                   GtkTreeIter *            iter,
1458                   gpointer       user_data UNUSED )
1459{
1460    TrTorrent * gtor = NULL;
1461
1462    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1463    tr_torrent_open_folder( gtor );
1464    g_object_unref( G_OBJECT( gtor ) );
1465}
1466
1467static gboolean
1468msgwinclosed( void )
1469{
1470    action_toggle( "toggle-message-log", FALSE );
1471    return FALSE;
1472}
1473
1474static void
1475accumulateSelectedTorrents( GtkTreeModel * model,
1476                            GtkTreePath  * path UNUSED,
1477                            GtkTreeIter  * iter,
1478                            gpointer       gdata )
1479{
1480    GSList ** data = ( GSList** ) gdata;
1481    TrTorrent * gtor = NULL;
1482
1483    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1484    *data = g_slist_prepend( *data, gtor );
1485    g_object_unref( G_OBJECT( gtor ) );
1486}
1487
1488static void
1489removeSelected( struct cbdata * data, gboolean delete_files )
1490{
1491    GSList * l = NULL;
1492    GtkTreeSelection * s = tr_window_get_selection( data->wind );
1493
1494    gtk_tree_selection_selected_foreach( s, accumulateSelectedTorrents, &l );
1495
1496    if( l != NULL ) {
1497        l = g_slist_reverse( l );
1498        confirmRemove( data->wind, data->core, l, delete_files );
1499    }
1500}
1501
1502static void
1503startAllTorrents( struct cbdata * data )
1504{
1505    tr_session * session = tr_core_session( data->core );
1506    const char * cmd = "{ \"method\": \"torrent-start\" }";
1507    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1508}
1509
1510static void
1511pauseAllTorrents( struct cbdata * data )
1512{
1513    tr_session * session = tr_core_session( data->core );
1514    const char * cmd = "{ \"method\": \"torrent-stop\" }";
1515    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1516}
1517
1518static tr_torrent*
1519getFirstSelectedTorrent( struct cbdata * data )
1520{
1521    tr_torrent * tor = NULL;
1522    GtkTreeSelection * s = tr_window_get_selection( data->wind );
1523    GtkTreeModel * m;
1524    GList * l = gtk_tree_selection_get_selected_rows( s, &m );
1525    if( l != NULL ) {
1526        GtkTreePath * p = l->data;
1527        GtkTreeIter i;
1528        if( gtk_tree_model_get_iter( m, &i, p ) )
1529            gtk_tree_model_get( m, &i, MC_TORRENT_RAW, &tor, -1 );
1530    }
1531    g_list_foreach( l, (GFunc)gtk_tree_path_free, NULL );
1532    g_list_free( l );
1533    return tor;
1534}
1535
1536static void
1537detailsClosed( gpointer gdata, GObject * dead )
1538{
1539    struct cbdata * data = gdata;
1540    struct DetailsDialogHandle * h = findDetailsDialogFromWidget( data, dead );
1541
1542    if( h != NULL )
1543    {
1544        data->details = g_slist_remove( data->details, h );
1545        g_free( h->key );
1546        g_free( h );
1547    }
1548}
1549
1550static void
1551copyMagnetLinkToClipboard( GtkWidget * w, tr_torrent * tor )
1552{
1553    char * magnet = tr_torrentGetMagnetLink( tor );
1554    GdkDisplay * display = gtk_widget_get_display( w );
1555    GdkAtom selection;
1556    GtkClipboard * clipboard;
1557
1558    /* this is The Right Thing for copy/paste... */
1559    selection = GDK_SELECTION_CLIPBOARD;
1560    clipboard = gtk_clipboard_get_for_display( display, selection );
1561    gtk_clipboard_set_text( clipboard, magnet, -1 );
1562
1563    /* ...but people using plain ol' X need this instead */
1564    selection = GDK_SELECTION_PRIMARY;
1565    clipboard = gtk_clipboard_get_for_display( display, selection );
1566    gtk_clipboard_set_text( clipboard, magnet, -1 );
1567
1568    /* cleanup */
1569    tr_free( magnet );
1570}
1571
1572void
1573doAction( const char * action_name, gpointer user_data )
1574{
1575    struct cbdata * data = user_data;
1576    gboolean        changed = FALSE;
1577
1578    if( !strcmp( action_name, "add-torrent-from-url" ) )
1579    {
1580        addURLDialog( data->wind, data->core );
1581    }
1582    else if(  !strcmp( action_name, "add-torrent-menu" )
1583      || !strcmp( action_name, "add-torrent-toolbar" ) )
1584    {
1585        addDialog( data->wind, data->core );
1586    }
1587    else if( !strcmp( action_name, "show-stats" ) )
1588    {
1589        GtkWidget * dialog = stats_dialog_create( data->wind, data->core );
1590        gtk_widget_show( dialog );
1591    }
1592    else if( !strcmp( action_name, "donate" ) )
1593    {
1594        gtr_open_uri( "http://www.transmissionbt.com/donate.php" );
1595    }
1596    else if( !strcmp( action_name, "pause-all-torrents" ) )
1597    {
1598        pauseAllTorrents( data );
1599    }
1600    else if( !strcmp( action_name, "start-all-torrents" ) )
1601    {
1602        startAllTorrents( data );
1603    }
1604    else if( !strcmp( action_name, "copy-magnet-link-to-clipboard" ) )
1605    {
1606        tr_torrent * tor = getFirstSelectedTorrent( data );
1607        if( tor != NULL )
1608        {
1609            copyMagnetLinkToClipboard( GTK_WIDGET( data->wind ), tor );
1610        }
1611    }
1612    else if( !strcmp( action_name, "relocate-torrent" ) )
1613    {
1614        GSList * ids = getSelectedTorrentIds( data );
1615        if( ids != NULL )
1616        {
1617            GtkWindow * parent = GTK_WINDOW( data->wind );
1618            GtkWidget * w = gtr_relocate_dialog_new( parent, data->core, ids );
1619            gtk_widget_show( w );
1620        }
1621    }
1622    else if( !strcmp( action_name, "start-torrent" ) )
1623    {
1624        changed |= rpcOnSelectedTorrents( data, "torrent-start" );
1625    }
1626    else if( !strcmp( action_name, "pause-torrent" ) )
1627    {
1628        changed |= rpcOnSelectedTorrents( data, "torrent-stop" );
1629    }
1630    else if( !strcmp( action_name, "verify-torrent" ) )
1631    {
1632        changed |= rpcOnSelectedTorrents( data, "torrent-verify" );
1633    }
1634    else if( !strcmp( action_name, "update-tracker" ) )
1635    {
1636        changed |= rpcOnSelectedTorrents( data, "torrent-reannounce" );
1637    }
1638    else if( !strcmp( action_name, "open-torrent-folder" ) )
1639    {
1640        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1641        gtk_tree_selection_selected_foreach( s, openFolderForeach, data );
1642    }
1643    else if( !strcmp( action_name, "show-torrent-properties" ) )
1644    {
1645        GtkWidget * w;
1646        GSList * ids = getSelectedTorrentIds( data );
1647        struct DetailsDialogHandle * h = findDetailsDialogFromIds( data, ids );
1648        if( h != NULL )
1649            w = h->dialog;
1650        else {
1651            h = g_new( struct DetailsDialogHandle, 1 );
1652            h->key = getDetailsDialogKey( ids );
1653            h->dialog = w = torrent_inspector_new( GTK_WINDOW( data->wind ), data->core );
1654            torrent_inspector_set_torrents( w, ids );
1655            data->details = g_slist_append( data->details, h );
1656            g_object_weak_ref( G_OBJECT( w ), detailsClosed, data );
1657        }
1658        gtk_window_present( GTK_WINDOW( w ) );
1659        g_slist_free( ids );
1660    }
1661    else if( !strcmp( action_name, "new-torrent" ) )
1662    {
1663        GtkWidget * w = make_meta_ui( GTK_WINDOW( data->wind ), data->core );
1664        gtk_widget_show_all( w );
1665    }
1666    else if( !strcmp( action_name, "remove-torrent" ) )
1667    {
1668        removeSelected( data, FALSE );
1669    }
1670    else if( !strcmp( action_name, "delete-torrent" ) )
1671    {
1672        removeSelected( data, TRUE );
1673    }
1674    else if( !strcmp( action_name, "quit" ) )
1675    {
1676        maybeaskquit( data );
1677    }
1678    else if( !strcmp( action_name, "select-all" ) )
1679    {
1680        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1681        gtk_tree_selection_select_all( s );
1682    }
1683    else if( !strcmp( action_name, "deselect-all" ) )
1684    {
1685        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1686        gtk_tree_selection_unselect_all( s );
1687    }
1688    else if( !strcmp( action_name, "edit-preferences" ) )
1689    {
1690        if( NULL == data->prefs )
1691        {
1692            data->prefs = tr_prefs_dialog_new( G_OBJECT( data->core ),
1693                                               data->wind );
1694            g_signal_connect( data->prefs, "destroy",
1695                              G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
1696            gtk_widget_show( GTK_WIDGET( data->prefs ) );
1697        }
1698    }
1699    else if( !strcmp( action_name, "toggle-message-log" ) )
1700    {
1701        if( !data->msgwin )
1702        {
1703            GtkWidget * win = msgwin_new( data->core, data->wind );
1704            g_signal_connect( win, "destroy", G_CALLBACK( msgwinclosed ), NULL );
1705            data->msgwin = win;
1706        }
1707        else
1708        {
1709            action_toggle( "toggle-message-log", FALSE );
1710            gtk_widget_destroy( data->msgwin );
1711            data->msgwin = NULL;
1712        }
1713    }
1714    else if( !strcmp( action_name, "show-about-dialog" ) )
1715    {
1716        about( data->wind );
1717    }
1718    else if( !strcmp ( action_name, "help" ) )
1719    {
1720        gtr_open_uri( gtr_get_help_uri( ) );
1721    }
1722    else if( !strcmp( action_name, "toggle-main-window" ) )
1723    {
1724        toggleMainWindow( data );
1725    }
1726    else g_error ( "Unhandled action: %s", action_name );
1727
1728    if( changed )
1729        updatemodel( data );
1730}
Note: See TracBrowser for help on using the repository browser.