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

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

(2.0x gtk) #3415 "crash in trunk when removing multiple torrents" -- fixed.

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