source: trunk/gtk/main.c @ 10065

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

(trunk, libT) #2844 "add able to disable '.part' suffix for incomplete files" -- added to GTK+ and Qt clients and to RPC spec for 1.90

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