source: trunk/gtk/main.c @ 10855

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

(trunk gtk) #3345 "Remove button deselects selected torrents" -- fixed

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