source: trunk/gtk/main.c @ 10066

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

(trunk gtk) make it easier to change the periodic refresh intervals at the code level

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