source: trunk/gtk/main.c @ 12562

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

(trunk gtk) #4366 "Use GAppInfo, rather than GConf2, for mime-type registration" -- done.

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