source: trunk/gtk/main.c @ 11095

Last change on this file since 11095 was 11095, checked in by charles, 12 years ago

(trunk) sync some more GTK+ and Qt strings

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