source: trunk/gtk/main.c @ 12563

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

(trunk gtk) #4366 -- wrap register_magnet_link_handler()'s body in #if HAVE_GIO ... #endif

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