source: trunk/gtk/main.c @ 9196

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

(trunk gtk) #2454: gtk client handles SIGINT (ctrl-c) oddly

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