source: trunk/gtk/main.c @ 11099

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

(trunk gtk) rewrite the main window's drag-and-drop handler s.t. it's GTK+ 3 compliant

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