source: trunk/gtk/main.c @ 10991

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

(trunk gtk) #3415 "crash in trunk when removing multiple torrents" -- experimental fix for Rolcol to confirm

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