source: trunk/gtk/main.c @ 11599

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

(trunk) Join the 21st century and use only 1 space at the end sentences. This commit is nearly as important as the semi-annual ones that remove trailing spaces from the ends of lines of code... :)

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