source: trunk/gtk/main.c @ 12511

Last change on this file since 12511 was 12511, checked in by jordan, 10 years ago

(trunk gtk) #4308 "Open with Transmission from Firefox doesn't work since Transmission 2.31" -- added support for uri-formmatted filenames being passed in via the command line

  • Property svn:keywords set to Date Rev Author Id
File size: 55.0 KB
Line 
1/******************************************************************************
2 * $Id: main.c 12511 2011-06-24 18:16:12Z 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        const char * arg = argv[i];
588
589        if( gtr_is_supported_url( arg ) || gtr_is_magnet_link( arg ) )
590        {
591            ret = g_slist_prepend( ret, g_strdup( arg ) );
592        }
593        else /* local file */
594        {
595            char * filename;
596
597            if( g_path_is_absolute( arg ) )
598                filename = g_strdup( arg );
599            else {
600                filename = g_filename_from_uri( arg, NULL, NULL );
601
602                if( filename == NULL )
603                    filename = g_build_filename( pwd, arg, NULL );
604            }
605
606            if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
607                ret = g_slist_prepend( ret, filename );
608            else {
609                if( gtr_is_hex_hashcode( argv[i] ) )
610                    ret = g_slist_prepend( ret, g_strdup_printf( "magnet:?xt=urn:btih:%s", argv[i] ) );
611                g_free( filename );
612            }
613        }
614    }
615
616    g_free( pwd );
617    return g_slist_reverse( ret );
618}
619
620/****
621*****
622*****
623****/
624
625int
626main( int argc, char ** argv )
627{
628    char * err = NULL;
629    GSList * argfiles;
630    GError * gerr;
631    gboolean didinit = FALSE;
632    gboolean didlock = FALSE;
633    gboolean showversion = FALSE;
634    gboolean startpaused = FALSE;
635    gboolean startminimized = FALSE;
636    const char * domain = MY_READABLE_NAME;
637    char * configDir = NULL;
638    gtr_lockfile_state_t tr_state;
639
640    GOptionEntry entries[] = {
641        { "paused",     'p', 0, G_OPTION_ARG_NONE,
642          &startpaused, _( "Start with all torrents paused" ), NULL },
643        { "version",    '\0', 0, G_OPTION_ARG_NONE,
644          &showversion, _( "Show version number and exit" ), NULL },
645#ifdef STATUS_ICON_SUPPORTED
646        { "minimized",  'm', 0, G_OPTION_ARG_NONE,
647          &startminimized,
648          _( "Start minimized in notification area" ), NULL },
649#endif
650        { "config-dir", 'g', 0, G_OPTION_ARG_FILENAME, &configDir,
651          _( "Where to look for configuration files" ), NULL },
652        { NULL, 0,   0, 0, NULL, NULL, NULL }
653    };
654
655    /* bind the gettext domain */
656    setlocale( LC_ALL, "" );
657    bindtextdomain( domain, TRANSMISSIONLOCALEDIR );
658    bind_textdomain_codeset( domain, "UTF-8" );
659    textdomain( domain );
660    g_set_application_name( _( "Transmission" ) );
661    tr_formatter_mem_init( mem_K, _(mem_K_str), _(mem_M_str), _(mem_G_str), _(mem_T_str) );
662    tr_formatter_size_init( disk_K, _(disk_K_str), _(disk_M_str), _(disk_G_str), _(disk_T_str) );
663    tr_formatter_speed_init( speed_K, _(speed_K_str), _(speed_M_str), _(speed_G_str), _(speed_T_str) );
664
665    /* initialize gtk */
666    if( !g_thread_supported( ) )
667        g_thread_init( NULL );
668
669    gerr = NULL;
670    if( !gtk_init_with_args( &argc, &argv, (char*)_( "[torrent files or urls]" ), entries,
671                             (char*)domain, &gerr ) )
672    {
673        fprintf( stderr, "%s\n", gerr->message );
674        g_clear_error( &gerr );
675        return 0;
676    }
677
678    if( showversion )
679    {
680        fprintf( stderr, "%s %s\n", MY_READABLE_NAME, LONG_VERSION_STRING );
681        return 0;
682    }
683
684    if( configDir == NULL )
685        configDir = (char*) tr_getDefaultConfigDir( MY_CONFIG_NAME );
686
687    gtr_notify_init( );
688    didinit = cf_init( configDir, NULL ); /* must come before actions_init */
689
690    setupsighandlers( ); /* set up handlers for fatal signals */
691
692    didlock = cf_lock( &tr_state, &err );
693    argfiles = checkfilenames( argc - 1, argv + 1 );
694
695    if( !didlock && argfiles )
696    {
697        /* We have torrents to add but there's another copy of Transmsision
698         * running... chances are we've been invoked from a browser, etc.
699         * So send the files over to the "real" copy of Transmission, and
700         * if that goes well, then our work is done. */
701        GSList * l;
702        gboolean delegated = FALSE;
703        const gboolean trash_originals = gtr_pref_flag_get( TR_PREFS_KEY_TRASH_ORIGINAL );
704
705        for( l=argfiles; l!=NULL; l=l->next )
706        {
707            const char * filename = l->data;
708            const gboolean added = gtr_dbus_add_torrent( filename );
709
710            if( added && trash_originals )
711                gtr_file_trash_or_remove( filename );
712
713            delegated |= added;
714        }
715
716        if( delegated ) {
717            g_slist_foreach( argfiles, (GFunc)g_free, NULL );
718            g_slist_free( argfiles );
719            argfiles = NULL;
720
721            if( err ) {
722                g_free( err );
723                err = NULL;
724            }
725        }
726    }
727    else if( ( !didlock ) && ( tr_state == GTR_LOCKFILE_ELOCK ) )
728    {
729        /* There's already another copy of Transmission running,
730         * so tell it to present its window to the user */
731        err = NULL;
732        if( !gtr_dbus_present_window( ) )
733            err = g_strdup( _( "Transmission is already running, but is not responding. To start a new session, you must first close the existing Transmission process." ) );
734    }
735
736    if( didlock && ( didinit || cf_init( configDir, &err ) ) )
737    {
738        /* No other copy of Transmission running...
739         * so we're going to be the primary. */
740
741        const char * str;
742        GtkWindow * win;
743        GtkUIManager * myUIManager;
744        tr_session * session;
745        struct cbdata * cbdata = g_new0( struct cbdata, 1 );
746
747        sighandler_cbdata = cbdata;
748
749        /* ensure the directories are created */
750        if(( str = gtr_pref_string_get( TR_PREFS_KEY_DOWNLOAD_DIR )))
751            gtr_mkdir_with_parents( str, 0777 );
752        if(( str = gtr_pref_string_get( TR_PREFS_KEY_INCOMPLETE_DIR )))
753            gtr_mkdir_with_parents( str, 0777 );
754
755        /* initialize the libtransmission session */
756        session = tr_sessionInit( "gtk", configDir, TRUE, gtr_pref_get_all( ) );
757
758        gtr_pref_flag_set( TR_PREFS_KEY_ALT_SPEED_ENABLED, tr_sessionUsesAltSpeed( session ) );
759        gtr_pref_int_set( TR_PREFS_KEY_PEER_PORT, tr_sessionGetPeerPort( session ) );
760        cbdata->core = gtr_core_new( session );
761
762        /* init the ui manager */
763        myUIManager = gtk_ui_manager_new ( );
764        gtr_actions_init ( myUIManager, cbdata );
765        gtk_ui_manager_add_ui_from_string ( myUIManager, fallback_ui_file, -1, NULL );
766        gtk_ui_manager_ensure_update ( myUIManager );
767        gtk_window_set_default_icon_name ( MY_CONFIG_NAME );
768
769        /* create main window now to be a parent to any error dialogs */
770        win = GTK_WINDOW( gtr_window_new( myUIManager, cbdata->core ) );
771        g_signal_connect( win, "size-allocate", G_CALLBACK( on_main_window_size_allocated ), cbdata );
772
773        app_setup( win, argfiles, cbdata, startpaused, startminimized );
774        tr_sessionSetRPCCallback( session, on_rpc_changed, cbdata );
775
776        /* on startup, check & see if it's time to update the blocklist */
777        if( gtr_pref_flag_get( TR_PREFS_KEY_BLOCKLIST_ENABLED ) ) {
778            if( gtr_pref_flag_get( PREF_KEY_BLOCKLIST_UPDATES_ENABLED ) ) {
779                const int64_t last_time = gtr_pref_int_get( "blocklist-date" );
780                const int SECONDS_IN_A_WEEK = 7 * 24 * 60 * 60;
781                const time_t now = time( NULL );
782                if( last_time + SECONDS_IN_A_WEEK < now )
783                    gtr_core_blocklist_update( cbdata->core );
784            }
785        }
786
787        /* if there's no magnet link handler registered, register us */
788        register_magnet_link_handler( );
789
790        gtk_main( );
791    }
792    else if( err )
793    {
794        const char * primary_text = _( "Transmission cannot be started." );
795        GtkWidget * w = gtk_message_dialog_new( NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, primary_text, NULL );
796        gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ), "%s", err );
797        g_signal_connect( w, "response", G_CALLBACK(gtk_main_quit), NULL );
798        gtk_widget_show( w );
799        g_free( err );
800        gtk_main( );
801    }
802
803    return 0;
804}
805
806static void
807on_core_busy( TrCore * core UNUSED, gboolean busy, struct cbdata * c )
808{
809    gtr_window_set_busy( c->wind, busy );
810}
811
812static void
813app_setup( TrWindow      * wind,
814           GSList        * files,
815           struct cbdata * cbdata,
816           gboolean        pause_all,
817           gboolean        is_iconified )
818{
819    const gboolean do_start = gtr_pref_flag_get( TR_PREFS_KEY_START ) && !pause_all;
820    const gboolean do_prompt = gtr_pref_flag_get( PREF_KEY_OPTIONS_PROMPT );
821    const gboolean do_notify = TRUE;
822
823    cbdata->is_iconified = is_iconified;
824
825    if( is_iconified )
826        gtr_pref_flag_set( PREF_KEY_SHOW_TRAY_ICON, TRUE );
827
828    gtr_actions_set_core( cbdata->core );
829
830    /* set up core handlers */
831    g_signal_connect( cbdata->core, "busy", G_CALLBACK( on_core_busy ), cbdata );
832    g_signal_connect( cbdata->core, "add-error", G_CALLBACK( on_core_error ), cbdata );
833    g_signal_connect( cbdata->core, "add-prompt", G_CALLBACK( on_add_torrent ), cbdata );
834    g_signal_connect( cbdata->core, "prefs-changed", G_CALLBACK( on_prefs_changed ), cbdata );
835
836    /* add torrents from command-line and saved state */
837    gtr_core_load( cbdata->core, pause_all );
838    gtr_core_add_list( cbdata->core, files, do_start, do_prompt, do_notify );
839    files = NULL;
840    gtr_core_torrents_added( cbdata->core );
841
842    /* set up main window */
843    main_window_setup( cbdata, wind );
844
845    /* set up the icon */
846    on_prefs_changed( cbdata->core, PREF_KEY_SHOW_TRAY_ICON, cbdata );
847
848    /* start model update timer */
849    cbdata->timer = gtr_timeout_add_seconds( MAIN_WINDOW_REFRESH_INTERVAL_SECONDS, update_model, cbdata );
850    update_model( cbdata );
851
852    /* either show the window or iconify it */
853    if( !is_iconified )
854        gtk_widget_show( GTK_WIDGET( wind ) );
855    else
856    {
857        gtk_window_set_skip_taskbar_hint( cbdata->wind,
858                                          cbdata->icon != NULL );
859        cbdata->is_iconified = FALSE; // ensure that the next toggle iconifies
860        gtr_action_set_toggled( "toggle-main-window", FALSE );
861    }
862
863    if( !gtr_pref_flag_get( PREF_KEY_USER_HAS_GIVEN_INFORMED_CONSENT ) )
864    {
865        GtkWidget * w = gtk_message_dialog_new( GTK_WINDOW( wind ),
866                                                GTK_DIALOG_DESTROY_WITH_PARENT,
867                                                GTK_MESSAGE_INFO,
868                                                GTK_BUTTONS_NONE,
869                                                "%s",
870             _( "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." ) );
871        gtk_dialog_add_button( GTK_DIALOG( w ), GTK_STOCK_QUIT, GTK_RESPONSE_REJECT );
872        gtk_dialog_add_button( GTK_DIALOG( w ), _( "I _Accept" ), GTK_RESPONSE_ACCEPT );
873        gtk_dialog_set_default_response( GTK_DIALOG( w ), GTK_RESPONSE_ACCEPT );
874        switch( gtk_dialog_run( GTK_DIALOG( w ) ) ) {
875            case GTK_RESPONSE_ACCEPT:
876                /* only show it once */
877                gtr_pref_flag_set( PREF_KEY_USER_HAS_GIVEN_INFORMED_CONSENT, TRUE );
878                gtk_widget_destroy( w );
879                break;
880            default:
881                exit( 0 );
882        }
883    }
884}
885
886static void
887toggleMainWindow( struct cbdata * cbdata )
888{
889    GtkWindow * window = cbdata->wind;
890    const int   doShow = cbdata->is_iconified;
891    static int  x = 0;
892    static int  y = 0;
893
894    if( doShow )
895    {
896        cbdata->is_iconified = 0;
897        gtk_window_set_skip_taskbar_hint( window, FALSE );
898        gtk_window_move( window, x, y );
899        gtr_widget_set_visible( GTK_WIDGET( window ), TRUE );
900        gtr_window_present( window );
901    }
902    else
903    {
904        gtk_window_get_position( window, &x, &y );
905        gtk_window_set_skip_taskbar_hint( window, TRUE );
906        gtr_widget_set_visible( GTK_WIDGET( window ), FALSE );
907        cbdata->is_iconified = 1;
908    }
909}
910
911static gboolean
912winclose( GtkWidget * w    UNUSED,
913          GdkEvent * event UNUSED,
914          gpointer         gdata )
915{
916    struct cbdata * cbdata = gdata;
917
918    if( cbdata->icon != NULL )
919        gtr_action_activate ( "toggle-main-window" );
920    else
921        on_app_exit( cbdata );
922
923    return TRUE; /* don't propagate event further */
924}
925
926static void
927rowChangedCB( GtkTreeModel  * model UNUSED,
928              GtkTreePath   * path,
929              GtkTreeIter   * iter  UNUSED,
930              gpointer        gdata )
931{
932    struct cbdata * data = gdata;
933    if( gtk_tree_selection_path_is_selected ( data->sel, path ) )
934        refresh_actions( gdata );
935}
936
937static void
938on_drag_data_received( GtkWidget         * widget          UNUSED,
939                       GdkDragContext    * drag_context,
940                       gint                x               UNUSED,
941                       gint                y               UNUSED,
942                       GtkSelectionData  * selection_data,
943                       guint               info            UNUSED,
944                       guint               time_,
945                       gpointer            gdata )
946{
947    int i;
948    gboolean success = FALSE;
949    GSList * files = NULL;
950    struct cbdata * data = gdata;
951    char ** uris = gtk_selection_data_get_uris( selection_data );
952
953    /* try to add the filename URIs... */
954    for( i=0; uris && uris[i]; ++i )
955    {
956        const char * uri = uris[i];
957        char * filename = g_filename_from_uri( uri, NULL, NULL );
958
959        if( filename && g_file_test( filename, G_FILE_TEST_EXISTS ) )
960        {
961            files = g_slist_append( files, g_strdup( filename ) );
962            success = TRUE;
963        }
964        else if( tr_urlIsValid( uri, -1 ) || gtr_is_magnet_link( uri ) )
965        {
966            gtr_core_add_from_url( data->core, uri );
967            success = TRUE;
968        }
969
970        g_free( filename );
971    }
972
973    if( files )
974        gtr_core_add_list_defaults( data->core, g_slist_reverse( files ), TRUE );
975
976    gtr_core_torrents_added( data->core );
977    gtk_drag_finish( drag_context, success, FALSE, time_ );
978
979    /* cleanup */
980    g_strfreev( uris );
981}
982
983static void
984main_window_setup( struct cbdata * cbdata, TrWindow * wind )
985{
986    GtkWidget * w;
987    GtkTreeModel * model;
988    GtkTreeSelection * sel;
989
990    g_assert( NULL == cbdata->wind );
991    cbdata->wind = GTK_WINDOW( wind );
992    cbdata->sel = sel = GTK_TREE_SELECTION( gtr_window_get_selection( cbdata->wind ) );
993
994    g_signal_connect( sel, "changed", G_CALLBACK( on_selection_changed ), cbdata );
995    on_selection_changed( sel, cbdata );
996    model = gtr_core_model( cbdata->core );
997    g_signal_connect( model, "row-changed", G_CALLBACK( rowChangedCB ), cbdata );
998    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
999    refresh_actions( cbdata );
1000
1001    /* register to handle URIs that get dragged onto our main window */
1002    w = GTK_WIDGET( wind );
1003    gtk_drag_dest_set( w, GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY );
1004    gtk_drag_dest_add_uri_targets( w );
1005    g_signal_connect( w, "drag-data-received", G_CALLBACK(on_drag_data_received), cbdata );
1006}
1007
1008static gboolean
1009on_session_closed( gpointer gdata )
1010{
1011    struct cbdata * cbdata = gdata;
1012
1013    /* shutdown the gui */
1014    while( cbdata->details != NULL ) {
1015        struct DetailsDialogHandle * h = cbdata->details->data;
1016        gtk_widget_destroy( h->dialog );
1017    }
1018
1019    if( cbdata->prefs )
1020        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
1021    if( cbdata->wind )
1022        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
1023    g_object_unref( cbdata->core );
1024    if( cbdata->icon )
1025        g_object_unref( cbdata->icon );
1026    g_slist_foreach( cbdata->error_list, (GFunc)g_free, NULL );
1027    g_slist_free( cbdata->error_list );
1028    g_slist_foreach( cbdata->duplicates_list, (GFunc)g_free, NULL );
1029    g_slist_free( cbdata->duplicates_list );
1030    g_free( cbdata );
1031
1032    gtk_main_quit( );
1033
1034    return FALSE;
1035}
1036
1037static gpointer
1038session_close_threadfunc( gpointer gdata )
1039{
1040    /* since tr_sessionClose() is a blocking function,
1041     * call it from another thread... when it's done,
1042     * punt the GUI teardown back to the GTK+ thread */
1043    struct cbdata * cbdata = gdata;
1044    gdk_threads_enter( );
1045    gtr_core_close( cbdata->core );
1046    gtr_idle_add( on_session_closed, gdata );
1047    gdk_threads_leave( );
1048    return NULL;
1049}
1050
1051static void
1052exit_now_cb( GtkWidget *w UNUSED, gpointer data UNUSED )
1053{
1054    exit( 0 );
1055}
1056
1057static void
1058on_app_exit( gpointer vdata )
1059{
1060    GtkWidget *r, *p, *b, *w, *c;
1061    struct cbdata *cbdata = vdata;
1062
1063    /* stop the update timer */
1064    if( cbdata->timer )
1065    {
1066        g_source_remove( cbdata->timer );
1067        cbdata->timer = 0;
1068    }
1069
1070    c = GTK_WIDGET( cbdata->wind );
1071    gtk_container_remove( GTK_CONTAINER( c ), gtk_bin_get_child( GTK_BIN( c ) ) );
1072
1073    r = gtk_alignment_new( 0.5, 0.5, 0.01, 0.01 );
1074    gtk_container_add( GTK_CONTAINER( c ), r );
1075
1076    p = gtk_table_new( 3, 2, FALSE );
1077    gtk_table_set_col_spacings( GTK_TABLE( p ), GUI_PAD_BIG );
1078    gtk_container_add( GTK_CONTAINER( r ), p );
1079
1080    w = gtk_image_new_from_stock( GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG );
1081    gtk_table_attach_defaults( GTK_TABLE( p ), w, 0, 1, 0, 2 );
1082
1083    w = gtk_label_new( NULL );
1084    gtk_label_set_markup( GTK_LABEL( w ), _( "<b>Closing Connections</b>" ) );
1085    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
1086    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 0, 1 );
1087
1088    w = gtk_label_new( _( "Sending upload/download totals to tracker..." ) );
1089    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
1090    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 1, 2 );
1091
1092    b = gtk_alignment_new( 0.0, 1.0, 0.01, 0.01 );
1093    w = gtk_button_new_with_mnemonic( _( "_Quit Now" ) );
1094    g_signal_connect( w, "clicked", G_CALLBACK( exit_now_cb ), NULL );
1095    gtk_container_add( GTK_CONTAINER( b ), w );
1096    gtk_table_attach( GTK_TABLE( p ), b, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 10 );
1097
1098    gtk_widget_show_all( r );
1099    gtk_widget_grab_focus( w );
1100
1101    /* clear the UI */
1102    gtr_core_clear( cbdata->core );
1103
1104    /* ensure the window is in its previous position & size.
1105     * this seems to be necessary because changing the main window's
1106     * child seems to unset the size */
1107    gtk_window_resize( cbdata->wind, gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_WIDTH ),
1108                                     gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_HEIGHT ) );
1109    gtk_window_move( cbdata->wind, gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_X ),
1110                                   gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_Y ) );
1111
1112    /* shut down libT */
1113    g_thread_create( session_close_threadfunc, vdata, TRUE, NULL );
1114}
1115
1116static void
1117show_torrent_errors( GtkWindow * window, const char * primary, GSList ** files )
1118{
1119    GSList * l;
1120    GtkWidget * w;
1121    GString * s = g_string_new( NULL );
1122    const char * leader = g_slist_length( *files ) > 1
1123                        ? gtr_get_unicode_string( GTR_UNICODE_BULLET )
1124                        : "";
1125
1126    for( l=*files; l!=NULL; l=l->next )
1127        g_string_append_printf( s, "%s %s\n", leader, (const char*)l->data );
1128
1129    w = gtk_message_dialog_new( window,
1130                                GTK_DIALOG_DESTROY_WITH_PARENT,
1131                                GTK_MESSAGE_ERROR,
1132                                GTK_BUTTONS_CLOSE,
1133                                "%s", primary );
1134    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
1135                                              "%s", s->str );
1136    g_signal_connect_swapped( w, "response",
1137                              G_CALLBACK( gtk_widget_destroy ), w );
1138    gtk_widget_show( w );
1139    g_string_free( s, TRUE );
1140
1141    g_slist_foreach( *files, (GFunc)g_free, NULL );
1142    g_slist_free( *files );
1143    *files = NULL;
1144}
1145
1146static void
1147flush_torrent_errors( struct cbdata * cbdata )
1148{
1149    if( cbdata->error_list )
1150        show_torrent_errors( cbdata->wind,
1151                              ngettext( "Couldn't add corrupt torrent",
1152                                        "Couldn't add corrupt torrents",
1153                                        g_slist_length( cbdata->error_list ) ),
1154                              &cbdata->error_list );
1155
1156    if( cbdata->duplicates_list )
1157        show_torrent_errors( cbdata->wind,
1158                              ngettext( "Couldn't add duplicate torrent",
1159                                        "Couldn't add duplicate torrents",
1160                                        g_slist_length( cbdata->duplicates_list ) ),
1161                              &cbdata->duplicates_list );
1162}
1163
1164static void
1165on_core_error( TrCore * core UNUSED, guint code, const char * msg, struct cbdata * c )
1166{
1167    switch( code )
1168    {
1169        case TR_PARSE_ERR:
1170            c->error_list =
1171                g_slist_append( c->error_list, g_path_get_basename( msg ) );
1172            break;
1173
1174        case TR_PARSE_DUPLICATE:
1175            c->duplicates_list = g_slist_append( c->duplicates_list, g_strdup( msg ) );
1176            break;
1177
1178        case TR_CORE_ERR_NO_MORE_TORRENTS:
1179            flush_torrent_errors( c );
1180            break;
1181
1182        default:
1183            g_assert_not_reached( );
1184            break;
1185    }
1186}
1187
1188static gboolean
1189on_main_window_focus_in( GtkWidget      * widget UNUSED,
1190                         GdkEventFocus  * event  UNUSED,
1191                         gpointer                gdata )
1192{
1193    struct cbdata * cbdata = gdata;
1194
1195    if( cbdata->wind )
1196        gtk_window_set_urgency_hint( cbdata->wind, FALSE );
1197    return FALSE;
1198}
1199
1200static void
1201on_add_torrent( TrCore * core, tr_ctor * ctor, gpointer gdata )
1202{
1203    struct cbdata * cbdata = gdata;
1204    GtkWidget * w = gtr_torrent_options_dialog_new( cbdata->wind, core, ctor );
1205
1206    g_signal_connect( w, "focus-in-event",
1207                      G_CALLBACK( on_main_window_focus_in ),  cbdata );
1208    if( cbdata->wind )
1209        gtk_window_set_urgency_hint( cbdata->wind, TRUE );
1210
1211    gtk_widget_show( w );
1212}
1213
1214static void
1215on_prefs_changed( TrCore * core UNUSED, const char * key, gpointer data )
1216{
1217    struct cbdata * cbdata = data;
1218    tr_session * tr = gtr_core_session( cbdata->core );
1219
1220    if( !strcmp( key, TR_PREFS_KEY_ENCRYPTION ) )
1221    {
1222        tr_sessionSetEncryption( tr, gtr_pref_int_get( key ) );
1223    }
1224    else if( !strcmp( key, TR_PREFS_KEY_DOWNLOAD_DIR ) )
1225    {
1226        tr_sessionSetDownloadDir( tr, gtr_pref_string_get( key ) );
1227    }
1228    else if( !strcmp( key, TR_PREFS_KEY_MSGLEVEL ) )
1229    {
1230        tr_setMessageLevel( gtr_pref_int_get( key ) );
1231    }
1232    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT ) )
1233    {
1234        tr_sessionSetPeerPort( tr, gtr_pref_int_get( key ) );
1235    }
1236    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_ENABLED ) )
1237    {
1238        tr_blocklistSetEnabled( tr, gtr_pref_flag_get( key ) );
1239    }
1240    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_URL ) )
1241    {
1242        tr_blocklistSetURL( tr, gtr_pref_string_get( key ) );
1243    }
1244    else if( !strcmp( key, PREF_KEY_SHOW_TRAY_ICON ) )
1245    {
1246        const int show = gtr_pref_flag_get( key );
1247        if( show && !cbdata->icon )
1248            cbdata->icon = gtr_icon_new( cbdata->core );
1249        else if( !show && cbdata->icon ) {
1250            g_object_unref( cbdata->icon );
1251            cbdata->icon = NULL;
1252        }
1253    }
1254    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_ENABLED ) )
1255    {
1256        tr_sessionLimitSpeed( tr, TR_DOWN, gtr_pref_flag_get( key ) );
1257    }
1258    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_KBps ) )
1259    {
1260        tr_sessionSetSpeedLimit_KBps( tr, TR_DOWN, gtr_pref_int_get( key ) );
1261    }
1262    else if( !strcmp( key, TR_PREFS_KEY_USPEED_ENABLED ) )
1263    {
1264        tr_sessionLimitSpeed( tr, TR_UP, gtr_pref_flag_get( key ) );
1265    }
1266    else if( !strcmp( key, TR_PREFS_KEY_USPEED_KBps ) )
1267    {
1268        tr_sessionSetSpeedLimit_KBps( tr, TR_UP, gtr_pref_int_get( key ) );
1269    }
1270    else if( !strcmp( key, TR_PREFS_KEY_RATIO_ENABLED ) )
1271    {
1272        tr_sessionSetRatioLimited( tr, gtr_pref_flag_get( key ) );
1273    }
1274    else if( !strcmp( key, TR_PREFS_KEY_RATIO ) )
1275    {
1276        tr_sessionSetRatioLimit( tr, gtr_pref_double_get( key ) );
1277    }
1278    else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT ) )
1279    {
1280        tr_sessionSetIdleLimit( tr, gtr_pref_int_get( key ) );
1281    }
1282    else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT_ENABLED ) )
1283    {
1284        tr_sessionSetIdleLimited( tr, gtr_pref_flag_get( key ) );
1285    }
1286    else if( !strcmp( key, TR_PREFS_KEY_PORT_FORWARDING ) )
1287    {
1288        tr_sessionSetPortForwardingEnabled( tr, gtr_pref_flag_get( key ) );
1289    }
1290    else if( !strcmp( key, TR_PREFS_KEY_PEX_ENABLED ) )
1291    {
1292        tr_sessionSetPexEnabled( tr, gtr_pref_flag_get( key ) );
1293    }
1294    else if( !strcmp( key, TR_PREFS_KEY_RENAME_PARTIAL_FILES ) )
1295    {
1296        tr_sessionSetIncompleteFileNamingEnabled( tr, gtr_pref_flag_get( key ) );
1297    }
1298    else if( !strcmp( key, TR_PREFS_KEY_DHT_ENABLED ) )
1299    {
1300        tr_sessionSetDHTEnabled( tr, gtr_pref_flag_get( key ) );
1301    }
1302    else if( !strcmp( key, TR_PREFS_KEY_UTP_ENABLED ) )
1303    {
1304        tr_sessionSetUTPEnabled( tr, gtr_pref_flag_get( key ) );
1305    }
1306    else if( !strcmp( key, TR_PREFS_KEY_LPD_ENABLED ) )
1307    {
1308        tr_sessionSetLPDEnabled( tr, gtr_pref_flag_get( key ) );
1309    }
1310    else if( !strcmp( key, TR_PREFS_KEY_RPC_PORT ) )
1311    {
1312        tr_sessionSetRPCPort( tr, gtr_pref_int_get( key ) );
1313    }
1314    else if( !strcmp( key, TR_PREFS_KEY_RPC_ENABLED ) )
1315    {
1316        tr_sessionSetRPCEnabled( tr, gtr_pref_flag_get( key ) );
1317    }
1318    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST ) )
1319    {
1320        tr_sessionSetRPCWhitelist( tr, gtr_pref_string_get( key ) );
1321    }
1322    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST_ENABLED ) )
1323    {
1324        tr_sessionSetRPCWhitelistEnabled( tr, gtr_pref_flag_get( key ) );
1325    }
1326    else if( !strcmp( key, TR_PREFS_KEY_RPC_USERNAME ) )
1327    {
1328        tr_sessionSetRPCUsername( tr, gtr_pref_string_get( key ) );
1329    }
1330    else if( !strcmp( key, TR_PREFS_KEY_RPC_PASSWORD ) )
1331    {
1332        tr_sessionSetRPCPassword( tr, gtr_pref_string_get( key ) );
1333    }
1334    else if( !strcmp( key, TR_PREFS_KEY_RPC_AUTH_REQUIRED ) )
1335    {
1336        tr_sessionSetRPCPasswordEnabled( tr, gtr_pref_flag_get( key ) );
1337    }
1338    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_UP_KBps ) )
1339    {
1340        tr_sessionSetAltSpeed_KBps( tr, TR_UP, gtr_pref_int_get( key ) );
1341    }
1342    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps ) )
1343    {
1344        tr_sessionSetAltSpeed_KBps( tr, TR_DOWN, gtr_pref_int_get( key ) );
1345    }
1346    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_ENABLED ) )
1347    {
1348        const gboolean b = gtr_pref_flag_get( key );
1349        tr_sessionUseAltSpeed( tr, b );
1350        gtr_action_set_toggled( key, b );
1351    }
1352    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN ) )
1353    {
1354        tr_sessionSetAltSpeedBegin( tr, gtr_pref_int_get( key ) );
1355    }
1356    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_END ) )
1357    {
1358        tr_sessionSetAltSpeedEnd( tr, gtr_pref_int_get( key ) );
1359    }
1360    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED ) )
1361    {
1362        tr_sessionUseAltSpeedTime( tr, gtr_pref_flag_get( key ) );
1363    }
1364    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_DAY ) )
1365    {
1366        tr_sessionSetAltSpeedDay( tr, gtr_pref_int_get( key ) );
1367    }
1368    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START ) )
1369    {
1370        tr_sessionSetPeerPortRandomOnStart( tr, gtr_pref_flag_get( key ) );
1371    }
1372    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR ) )
1373    {
1374        tr_sessionSetIncompleteDir( tr, gtr_pref_string_get( key ) );
1375    }
1376    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED ) )
1377    {
1378        tr_sessionSetIncompleteDirEnabled( tr, gtr_pref_flag_get( key ) );
1379    }
1380    else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED ) )
1381    {
1382        tr_sessionSetTorrentDoneScriptEnabled( tr, gtr_pref_flag_get( key ) );
1383    }
1384    else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME ) )
1385    {
1386        tr_sessionSetTorrentDoneScript( tr, gtr_pref_string_get( key ) );
1387    }
1388    else if( !strcmp( key, TR_PREFS_KEY_START) )
1389    {
1390        tr_sessionSetPaused( tr, !gtr_pref_flag_get( key ) );
1391    }
1392    else if( !strcmp( key, TR_PREFS_KEY_TRASH_ORIGINAL ) )
1393    {
1394        tr_sessionSetDeleteSource( tr, gtr_pref_flag_get( key ) );
1395    }
1396}
1397
1398static gboolean
1399update_model( gpointer gdata )
1400{
1401    struct cbdata *data = gdata;
1402    const gboolean done = global_sigcount;
1403
1404    if( !done )
1405    {
1406        /* update the torrent data in the model */
1407        gtr_core_update( data->core );
1408
1409        /* refresh the main window's statusbar and toolbar buttons */
1410        if( data->wind != NULL )
1411            gtr_window_refresh( data->wind );
1412
1413        /* update the actions */
1414        refresh_actions( data );
1415
1416        /* update the status tray icon */
1417        if( data->icon != NULL )
1418            gtr_icon_refresh( data->icon );
1419    }
1420
1421    return !done;
1422}
1423
1424/* GTK+ versions before 2.18.0 don't have a default URI hook... */
1425#if !GTK_CHECK_VERSION(2,18,0)
1426 #define NEED_URL_HOOK
1427#endif
1428
1429#ifdef NEED_URL_HOOK
1430static void
1431on_uri_clicked( GtkAboutDialog * u UNUSED, const gchar * uri, gpointer u2 UNUSED )
1432{
1433    gtr_open_uri( uri );
1434}
1435#endif
1436
1437static void
1438show_about_dialog( GtkWindow * parent )
1439{
1440    GtkWidget * d;
1441    const char * website_uri = "http://www.transmissionbt.com/";
1442    const char * authors[] = {
1443        "Jordan Lee (Backend; GTK+)",
1444        "Mitchell Livingston (Backend; OS X)",
1445        NULL
1446    };
1447
1448#ifdef NEED_URL_HOOK
1449    gtk_about_dialog_set_url_hook( on_uri_clicked, NULL, NULL );
1450#endif
1451
1452    d = g_object_new( GTK_TYPE_ABOUT_DIALOG,
1453                      "authors", authors,
1454                      "comments", _( "A fast and easy BitTorrent client" ),
1455                      "copyright", _( "Copyright (c) The Transmission Project" ),
1456                      "logo-icon-name", MY_CONFIG_NAME,
1457                      "name", g_get_application_name( ),
1458                      /* Translators: translate "translator-credits" as your name
1459                         to have it appear in the credits in the "About"
1460                         dialog */
1461                      "translator-credits", _( "translator-credits" ),
1462                      "version", LONG_VERSION_STRING,
1463                      "website", website_uri,
1464                      "website-label", website_uri,
1465#ifdef SHOW_LICENSE
1466                      "license", LICENSE,
1467                      "wrap-license", TRUE,
1468#endif
1469                      NULL );
1470    gtk_window_set_transient_for( GTK_WINDOW( d ), parent );
1471    g_signal_connect_swapped( d, "response", G_CALLBACK (gtk_widget_destroy), d );
1472    gtk_widget_show( d );
1473}
1474
1475static void
1476append_id_to_benc_list( GtkTreeModel * m, GtkTreePath * path UNUSED,
1477                        GtkTreeIter * iter, gpointer list )
1478{
1479    tr_torrent * tor = NULL;
1480    gtk_tree_model_get( m, iter, MC_TORRENT, &tor, -1 );
1481    tr_bencListAddInt( list, tr_torrentId( tor ) );
1482}
1483
1484static gboolean
1485call_rpc_for_selected_torrents( struct cbdata * data, const char * method )
1486{
1487    tr_benc top, *args, *ids;
1488    gboolean invoked = FALSE;
1489    GtkTreeSelection * s = data->sel;
1490    tr_session * session = gtr_core_session( data->core );
1491
1492    tr_bencInitDict( &top, 2 );
1493    tr_bencDictAddStr( &top, "method", method );
1494    args = tr_bencDictAddDict( &top, "arguments", 1 );
1495    ids = tr_bencDictAddList( args, "ids", 0 );
1496    gtk_tree_selection_selected_foreach( s, append_id_to_benc_list, ids );
1497
1498    if( tr_bencListSize( ids ) != 0 )
1499    {
1500        int json_len;
1501        char * json = tr_bencToStr( &top, TR_FMT_JSON_LEAN, &json_len );
1502        tr_rpc_request_exec_json( session, json, json_len, NULL, NULL );
1503        g_free( json );
1504        invoked = TRUE;
1505    }
1506
1507    tr_bencFree( &top );
1508    return invoked;
1509}
1510
1511static void
1512open_folder_foreach( GtkTreeModel * model, GtkTreePath * path UNUSED,
1513                     GtkTreeIter * iter, gpointer core )
1514{
1515    int id;
1516    gtk_tree_model_get( model, iter, MC_TORRENT_ID, &id, -1 );
1517    gtr_core_open_folder( core, id );
1518}
1519
1520static gboolean
1521on_message_window_closed( void )
1522{
1523    gtr_action_set_toggled( "toggle-message-log", FALSE );
1524    return FALSE;
1525}
1526
1527static void
1528accumulate_selected_torrents( GtkTreeModel  * model, GtkTreePath   * path UNUSED,
1529                              GtkTreeIter   * iter, gpointer        gdata )
1530{
1531    int id;
1532    GSList ** data = gdata;
1533
1534    gtk_tree_model_get( model, iter, MC_TORRENT_ID, &id, -1 );
1535    *data = g_slist_append( *data, GINT_TO_POINTER( id ) );
1536}
1537
1538static void
1539remove_selected( struct cbdata * data, gboolean delete_files )
1540{
1541    GSList * l = NULL;
1542
1543    gtk_tree_selection_selected_foreach( data->sel, accumulate_selected_torrents, &l );
1544
1545    if( l != NULL )
1546        gtr_confirm_remove( data->wind, data->core, l, delete_files );
1547}
1548
1549static void
1550start_all_torrents( struct cbdata * data )
1551{
1552    tr_session * session = gtr_core_session( data->core );
1553    const char * cmd = "{ \"method\": \"torrent-start\" }";
1554    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1555}
1556
1557static void
1558pause_all_torrents( struct cbdata * data )
1559{
1560    tr_session * session = gtr_core_session( data->core );
1561    const char * cmd = "{ \"method\": \"torrent-stop\" }";
1562    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1563}
1564
1565static tr_torrent*
1566get_first_selected_torrent( struct cbdata * data )
1567{
1568    tr_torrent * tor = NULL;
1569    GtkTreeModel * m;
1570    GList * l = gtk_tree_selection_get_selected_rows( data->sel, &m );
1571    if( l != NULL ) {
1572        GtkTreePath * p = l->data;
1573        GtkTreeIter i;
1574        if( gtk_tree_model_get_iter( m, &i, p ) )
1575            gtk_tree_model_get( m, &i, MC_TORRENT, &tor, -1 );
1576    }
1577    g_list_foreach( l, (GFunc)gtk_tree_path_free, NULL );
1578    g_list_free( l );
1579    return tor;
1580}
1581
1582static void
1583copy_magnet_link_to_clipboard( GtkWidget * w, tr_torrent * tor )
1584{
1585    char * magnet = tr_torrentGetMagnetLink( tor );
1586    GdkDisplay * display = gtk_widget_get_display( w );
1587    GdkAtom selection;
1588    GtkClipboard * clipboard;
1589
1590    /* this is The Right Thing for copy/paste... */
1591    selection = GDK_SELECTION_CLIPBOARD;
1592    clipboard = gtk_clipboard_get_for_display( display, selection );
1593    gtk_clipboard_set_text( clipboard, magnet, -1 );
1594
1595    /* ...but people using plain ol' X need this instead */
1596    selection = GDK_SELECTION_PRIMARY;
1597    clipboard = gtk_clipboard_get_for_display( display, selection );
1598    gtk_clipboard_set_text( clipboard, magnet, -1 );
1599
1600    /* cleanup */
1601    tr_free( magnet );
1602}
1603
1604void
1605gtr_actions_handler( const char * action_name, gpointer user_data )
1606{
1607    struct cbdata * data = user_data;
1608    gboolean        changed = FALSE;
1609
1610    if( !strcmp( action_name, "open-torrent-from-url" ) )
1611    {
1612        GtkWidget * w = gtr_torrent_open_from_url_dialog_new( data->wind, data->core );
1613        gtk_widget_show( w );
1614    }
1615    else if(  !strcmp( action_name, "open-torrent-menu" )
1616      || !strcmp( action_name, "open-torrent-toolbar" ) )
1617    {
1618        GtkWidget * w = gtr_torrent_open_from_file_dialog_new( data->wind, data->core );
1619        gtk_widget_show( w );
1620    }
1621    else if( !strcmp( action_name, "show-stats" ) )
1622    {
1623        GtkWidget * dialog = gtr_stats_dialog_new( data->wind, data->core );
1624        gtk_widget_show( dialog );
1625    }
1626    else if( !strcmp( action_name, "donate" ) )
1627    {
1628        gtr_open_uri( "http://www.transmissionbt.com/donate.php" );
1629    }
1630    else if( !strcmp( action_name, "pause-all-torrents" ) )
1631    {
1632        pause_all_torrents( data );
1633    }
1634    else if( !strcmp( action_name, "start-all-torrents" ) )
1635    {
1636        start_all_torrents( data );
1637    }
1638    else if( !strcmp( action_name, "copy-magnet-link-to-clipboard" ) )
1639    {
1640        tr_torrent * tor = get_first_selected_torrent( data );
1641        if( tor != NULL )
1642        {
1643            copy_magnet_link_to_clipboard( GTK_WIDGET( data->wind ), tor );
1644        }
1645    }
1646    else if( !strcmp( action_name, "relocate-torrent" ) )
1647    {
1648        GSList * ids = getSelectedTorrentIds( data );
1649        if( ids != NULL )
1650        {
1651            GtkWindow * parent = data->wind;
1652            GtkWidget * w = gtr_relocate_dialog_new( parent, data->core, ids );
1653            gtk_widget_show( w );
1654        }
1655    }
1656    else if( !strcmp( action_name, "start-torrent" ) )
1657    {
1658        changed |= call_rpc_for_selected_torrents( data, "torrent-start" );
1659    }
1660    else if( !strcmp( action_name, "pause-torrent" ) )
1661    {
1662        changed |= call_rpc_for_selected_torrents( data, "torrent-stop" );
1663    }
1664    else if( !strcmp( action_name, "verify-torrent" ) )
1665    {
1666        changed |= call_rpc_for_selected_torrents( data, "torrent-verify" );
1667    }
1668    else if( !strcmp( action_name, "update-tracker" ) )
1669    {
1670        changed |= call_rpc_for_selected_torrents( data, "torrent-reannounce" );
1671    }
1672    else if( !strcmp( action_name, "open-torrent-folder" ) )
1673    {
1674        gtk_tree_selection_selected_foreach( data->sel, open_folder_foreach, data->core );
1675    }
1676    else if( !strcmp( action_name, "show-torrent-properties" ) )
1677    {
1678        show_details_dialog_for_selected_torrents( data );
1679    }
1680    else if( !strcmp( action_name, "new-torrent" ) )
1681    {
1682        GtkWidget * w = gtr_torrent_creation_dialog_new( data->wind, data->core );
1683        gtk_widget_show( w );
1684    }
1685    else if( !strcmp( action_name, "remove-torrent" ) )
1686    {
1687        remove_selected( data, FALSE );
1688    }
1689    else if( !strcmp( action_name, "delete-torrent" ) )
1690    {
1691        remove_selected( data, TRUE );
1692    }
1693    else if( !strcmp( action_name, "quit" ) )
1694    {
1695        on_app_exit( data );
1696    }
1697    else if( !strcmp( action_name, "select-all" ) )
1698    {
1699        gtk_tree_selection_select_all( data->sel );
1700    }
1701    else if( !strcmp( action_name, "deselect-all" ) )
1702    {
1703        gtk_tree_selection_unselect_all( data->sel );
1704    }
1705    else if( !strcmp( action_name, "edit-preferences" ) )
1706    {
1707        if( NULL == data->prefs )
1708        {
1709            data->prefs = gtr_prefs_dialog_new( data->wind, G_OBJECT( data->core ) );
1710            g_signal_connect( data->prefs, "destroy",
1711                              G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
1712        }
1713        gtr_window_present( GTK_WINDOW( data->prefs ) );
1714    }
1715    else if( !strcmp( action_name, "toggle-message-log" ) )
1716    {
1717        if( !data->msgwin )
1718        {
1719            GtkWidget * win = gtr_message_log_window_new( data->wind, data->core );
1720            g_signal_connect( win, "destroy", G_CALLBACK( on_message_window_closed ), NULL );
1721            data->msgwin = win;
1722        }
1723        else
1724        {
1725            gtr_action_set_toggled( "toggle-message-log", FALSE );
1726            gtk_widget_destroy( data->msgwin );
1727            data->msgwin = NULL;
1728        }
1729    }
1730    else if( !strcmp( action_name, "show-about-dialog" ) )
1731    {
1732        show_about_dialog( data->wind );
1733    }
1734    else if( !strcmp ( action_name, "help" ) )
1735    {
1736        gtr_open_uri( gtr_get_help_uri( ) );
1737    }
1738    else if( !strcmp( action_name, "toggle-main-window" ) )
1739    {
1740        toggleMainWindow( data );
1741    }
1742    else g_error ( "Unhandled action: %s", action_name );
1743
1744    if( changed )
1745        update_model( data );
1746}
Note: See TracBrowser for help on using the repository browser.