source: trunk/gtk/main.c @ 11512

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

(trunk) #3817 "use the OS' proxy support" -- implemented for libtransmission, transmission-gtk

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