source: trunk/gtk/main.c @ 12461

Last change on this file since 12461 was 12461, checked in by jordan, 11 years ago

(trunk) #3817 remove OS proxy integration from the GTK+ client

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