source: branches/2.0x/gtk/main.c @ 10994

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

(2.0x gtk) #3416 "clicking close on the transmission window causes new "Quit Transmission?" dialog even if one already exists" -- fixed

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