source: trunk/gtk/main.c @ 9688

Last change on this file since 9688 was 9688, checked in by charles, 13 years ago

(trunk gtk) #2625 "ability to create a magnet link" -- fix "Copy Magnet Link to Clipboard" for X users who aren't using KDE or GNOME. Reported by jch

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