source: trunk/gtk/main.c @ 10700

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

(trunk gtk) #3239 "move .torrent files to trash" not working when .torrent added via dbus

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