source: trunk/gtk/main.c @ 10759

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

(trunk gtk) #3295 "transmission updates the blocklist even when the blocklist is disabled" -- fixed in trunk for 2.00

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