source: trunk/gtk/main.c @ 9390

Last change on this file since 9390 was 9390, checked in by charles, 9 years ago

(trunk) add SIGKILL handling to daemon, gtk clients. This is revision of r9387 for #2119

  • Property svn:keywords set to Date Rev Author Id
File size: 48.6 KB
Line 
1/******************************************************************************
2 * $Id: main.c 9390 2009-10-24 16:18:40Z 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        case SIGKILL:
276            g_message( _( "Got signal %d; trying to shut down cleanly.  Do it again if it gets stuck." ), sig );
277            doAction( "quit", sighandler_cbdata );
278            break;
279
280        default:
281            g_message( "unhandled signal" );
282    }
283}
284
285static void
286setupsighandlers( void )
287{
288    signal( SIGINT, signal_handler );
289    signal( SIGKILL, signal_handler );
290}
291
292static tr_rpc_callback_status
293onRPCChanged( tr_session            * session UNUSED,
294              tr_rpc_callback_type    type,
295              struct tr_torrent     * tor,
296              void                  * gdata )
297{
298    struct cbdata * cbdata = gdata;
299    gdk_threads_enter( );
300
301    switch( type )
302    {
303        case TR_RPC_TORRENT_ADDED:
304            tr_core_add_torrent( cbdata->core, tr_torrent_new_preexisting( tor ), TRUE );
305            break;
306
307        case TR_RPC_TORRENT_REMOVING:
308            tr_core_torrent_destroyed( cbdata->core, tr_torrentId( tor ) );
309            break;
310
311        case TR_RPC_SESSION_CHANGED:
312        case TR_RPC_TORRENT_CHANGED:
313        case TR_RPC_TORRENT_MOVED:
314        case TR_RPC_TORRENT_STARTED:
315        case TR_RPC_TORRENT_STOPPED:
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 notification area" ), 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        if(( str = pref_string_get( TR_PREFS_KEY_INCOMPLETE_DIR )))
442            gtr_mkdir_with_parents( str, 0777 );
443
444        /* initialize the libtransmission session */
445        session = tr_sessionInit( "gtk", configDir, TRUE, pref_get_all( ) );
446        pref_flag_set( TR_PREFS_KEY_ALT_SPEED_ENABLED, tr_sessionUsesAltSpeed( session ) );
447        pref_int_set( TR_PREFS_KEY_PEER_PORT, tr_sessionGetPeerPort( session ) );
448        cbdata->core = tr_core_new( session );
449
450        /* init the ui manager */
451        myUIManager = gtk_ui_manager_new ( );
452        actions_init ( myUIManager, cbdata );
453        gtk_ui_manager_add_ui_from_string ( myUIManager, fallback_ui_file, -1, NULL );
454        gtk_ui_manager_ensure_update ( myUIManager );
455        gtk_window_set_default_icon_name ( MY_NAME );
456
457        /* create main window now to be a parent to any error dialogs */
458        win = GTK_WINDOW( tr_window_new( myUIManager, cbdata->core ) );
459        g_signal_connect( win, "size-allocate", G_CALLBACK( onMainWindowSizeAllocated ), cbdata );
460
461        appsetup( win, argfiles, cbdata, startpaused, startminimized );
462        tr_sessionSetRPCCallback( session, onRPCChanged, cbdata );
463
464        /* on startup, check & see if it's time to update the blocklist */
465        if( pref_flag_get( PREF_KEY_BLOCKLIST_UPDATES_ENABLED )
466            && ( time( NULL ) - pref_int_get( "blocklist-date" ) > ( 60 * 60 * 24 * 7 ) ) )
467                tr_core_blocklist_update( cbdata->core );
468
469        gtk_main( );
470    }
471    else if( err )
472    {
473        const char * primary_text = _( "Transmission cannot be started." );
474        GtkWidget * w = gtk_message_dialog_new( NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, primary_text, NULL );
475        gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ), "%s", err );
476        g_signal_connect( w, "response", G_CALLBACK(gtk_main_quit), NULL );
477        gtk_widget_show( w );
478        g_free( err );
479        gtk_main( );
480    }
481
482    return 0;
483}
484
485static void
486appsetup( TrWindow *      wind,
487          GSList *        torrentFiles,
488          struct cbdata * cbdata,
489          gboolean        forcepause,
490          gboolean        isIconified )
491{
492    const pref_flag_t start =
493        forcepause ? PREF_FLAG_FALSE : PREF_FLAG_DEFAULT;
494    const pref_flag_t prompt = PREF_FLAG_DEFAULT;
495
496    /* fill out cbdata */
497    cbdata->wind         = NULL;
498    cbdata->icon         = NULL;
499    cbdata->msgwin       = NULL;
500    cbdata->prefs        = NULL;
501    cbdata->timer        = 0;
502    cbdata->isClosing    = 0;
503    cbdata->errqueue     = NULL;
504    cbdata->dupqueue     = NULL;
505    cbdata->isIconified  = isIconified;
506
507    if( isIconified )
508        pref_flag_set( PREF_KEY_SHOW_TRAY_ICON, TRUE );
509
510    actions_set_core( cbdata->core );
511
512    /* set up core handlers */
513    g_signal_connect( cbdata->core, "error", G_CALLBACK( coreerr ), cbdata );
514    g_signal_connect( cbdata->core, "add-torrent-prompt",
515                      G_CALLBACK( onAddTorrent ), cbdata );
516    g_signal_connect_swapped( cbdata->core, "quit",
517                              G_CALLBACK( wannaquit ), cbdata );
518    g_signal_connect( cbdata->core, "prefs-changed",
519                      G_CALLBACK( prefschanged ), cbdata );
520
521    /* add torrents from command-line and saved state */
522    tr_core_load( cbdata->core, forcepause );
523    tr_core_add_list( cbdata->core, torrentFiles, start, prompt, TRUE );
524    torrentFiles = NULL;
525    tr_core_torrents_added( cbdata->core );
526
527    /* set up main window */
528    winsetup( cbdata, wind );
529
530    /* set up the icon */
531    prefschanged( cbdata->core, PREF_KEY_SHOW_TRAY_ICON, cbdata );
532
533    /* start model update timer */
534    cbdata->timer = gtr_timeout_add_seconds( REFRESH_INTERVAL_SECONDS, updatemodel, cbdata );
535    updatemodel( cbdata );
536
537    /* either show the window or iconify it */
538    if( !isIconified )
539        gtk_widget_show( GTK_WIDGET( wind ) );
540    else
541    {
542        gtk_window_set_skip_taskbar_hint( cbdata->wind,
543                                          cbdata->icon != NULL );
544        cbdata->isIconified = FALSE; // ensure that the next toggle iconifies
545        action_toggle( "toggle-main-window", FALSE );
546    }
547
548    if( !pref_flag_get( PREF_KEY_USER_HAS_GIVEN_INFORMED_CONSENT ) )
549    {
550        GtkWidget * w = gtk_message_dialog_new( GTK_WINDOW( wind ),
551                                                GTK_DIALOG_DESTROY_WITH_PARENT,
552                                                GTK_MESSAGE_INFO,
553                                                GTK_BUTTONS_NONE,
554                                                "%s",
555            _( "Transmission is a file sharing program.  When you run a torrent, its data will be made available to others by means of upload.  And of course, any content you share is your sole responsibility.\n\nYou probably knew this, so we won't tell you again." ) );
556        gtk_dialog_add_button( GTK_DIALOG( w ), GTK_STOCK_QUIT, GTK_RESPONSE_REJECT );
557        gtk_dialog_add_button( GTK_DIALOG( w ), _( "I _Accept" ), GTK_RESPONSE_ACCEPT );
558        gtk_dialog_set_default_response( GTK_DIALOG( w ), GTK_RESPONSE_ACCEPT );
559        switch( gtk_dialog_run( GTK_DIALOG( w ) ) ) {
560            case GTK_RESPONSE_ACCEPT:
561                /* only show it once */
562                pref_flag_set( PREF_KEY_USER_HAS_GIVEN_INFORMED_CONSENT, TRUE );
563                gtk_widget_destroy( w );
564                break;
565            default:
566                exit( 0 );
567        }
568    }
569}
570
571static void
572tr_window_present( GtkWindow * window )
573{
574#if GTK_CHECK_VERSION( 2, 8, 0 )
575    gtk_window_present_with_time( window, gtk_get_current_event_time( ) );
576#else
577    gtk_window_present( window );
578#endif
579}
580
581static void
582toggleMainWindow( struct cbdata * cbdata,
583                  gboolean        doPresent )
584{
585    GtkWindow * window = GTK_WINDOW( cbdata->wind );
586    const int   doShow = cbdata->isIconified;
587    static int  x = 0;
588    static int  y = 0;
589
590    if( doShow || doPresent )
591    {
592        cbdata->isIconified = 0;
593        gtk_window_set_skip_taskbar_hint( window, FALSE );
594        gtk_window_move( window, x, y );
595        gtk_widget_show( GTK_WIDGET( window ) );
596        tr_window_present( window );
597    }
598    else
599    {
600        gtk_window_get_position( window, &x, &y );
601        gtk_window_set_skip_taskbar_hint( window, TRUE );
602        gtk_widget_hide( GTK_WIDGET( window ) );
603        cbdata->isIconified = 1;
604    }
605}
606
607static gboolean
608winclose( GtkWidget * w    UNUSED,
609          GdkEvent * event UNUSED,
610          gpointer         gdata )
611{
612    struct cbdata * cbdata = gdata;
613
614    if( cbdata->icon != NULL )
615        action_activate ( "toggle-main-window" );
616    else
617        askquit( cbdata->core, cbdata->wind, wannaquit, cbdata );
618
619    return TRUE; /* don't propagate event further */
620}
621
622static void
623rowChangedCB( GtkTreeModel  * model UNUSED,
624              GtkTreePath   * path,
625              GtkTreeIter   * iter  UNUSED,
626              gpointer        gdata )
627{
628    struct cbdata * data = gdata;
629    if( gtk_tree_selection_path_is_selected ( data->sel, path ) )
630        refreshActions( gdata );
631}
632
633static void
634winsetup( struct cbdata * cbdata,
635          TrWindow *      wind )
636{
637    GtkTreeModel *     model;
638    GtkTreeSelection * sel;
639
640    g_assert( NULL == cbdata->wind );
641    cbdata->wind = GTK_WINDOW( wind );
642    cbdata->sel = sel = GTK_TREE_SELECTION( tr_window_get_selection( cbdata->wind ) );
643
644    g_signal_connect( sel, "changed", G_CALLBACK( selectionChangedCB ), cbdata );
645    selectionChangedCB( sel, cbdata );
646    model = tr_core_model( cbdata->core );
647    g_signal_connect( model, "row-changed", G_CALLBACK( rowChangedCB ), cbdata );
648    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
649    refreshActions( cbdata );
650
651    setupdrag( GTK_WIDGET( wind ), cbdata );
652}
653
654static gpointer
655quitThreadFunc( gpointer gdata )
656{
657    struct cbdata * cbdata = gdata;
658    gdk_threads_enter( );
659
660    tr_core_close( cbdata->core );
661
662    /* shutdown the gui */
663    if( cbdata->details ) {
664        g_slist_foreach( cbdata->details, (GFunc)gtk_widget_destroy, NULL );
665        g_slist_free( cbdata->details );
666    }
667    if( cbdata->prefs )
668        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
669    if( cbdata->wind )
670        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
671    g_object_unref( cbdata->core );
672    if( cbdata->icon )
673        g_object_unref( cbdata->icon );
674    if( cbdata->errqueue ) {
675        g_slist_foreach( cbdata->errqueue, (GFunc)g_free, NULL );
676        g_slist_free( cbdata->errqueue );
677    }
678    if( cbdata->dupqueue ) {
679        g_slist_foreach( cbdata->dupqueue, (GFunc)g_free, NULL );
680        g_slist_free( cbdata->dupqueue );
681    }
682    g_free( cbdata );
683
684    gtk_main_quit( );
685    gdk_threads_leave( );
686
687    return NULL;
688}
689
690static void
691do_exit_cb( GtkWidget *w  UNUSED,
692            gpointer data UNUSED )
693{
694    exit( 0 );
695}
696
697static void
698wannaquit( gpointer vdata )
699{
700    GtkWidget *r, *p, *b, *w, *c;
701    struct cbdata *cbdata = vdata;
702
703    /* stop the update timer */
704    if( cbdata->timer )
705    {
706        g_source_remove( cbdata->timer );
707        cbdata->timer = 0;
708    }
709
710    c = GTK_WIDGET( cbdata->wind );
711    gtk_container_remove( GTK_CONTAINER( c ), gtk_bin_get_child( GTK_BIN( c ) ) );
712
713    r = gtk_alignment_new( 0.5, 0.5, 0.01, 0.01 );
714    gtk_container_add( GTK_CONTAINER( c ), r );
715
716    p = gtk_table_new( 3, 2, FALSE );
717    gtk_table_set_col_spacings( GTK_TABLE( p ), GUI_PAD_BIG );
718    gtk_container_add( GTK_CONTAINER( r ), p );
719
720    w = gtk_image_new_from_stock( GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG );
721    gtk_table_attach_defaults( GTK_TABLE( p ), w, 0, 1, 0, 2 );
722
723    w = gtk_label_new( NULL );
724    gtk_label_set_markup( GTK_LABEL( w ), _( "<b>Closing Connections</b>" ) );
725    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
726    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 0, 1 );
727
728    w = gtk_label_new( _( "Sending upload/download totals to tracker..." ) );
729    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
730    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 1, 2 );
731
732    b = gtk_alignment_new( 0.0, 1.0, 0.01, 0.01 );
733    w = gtr_button_new_from_stock( GTK_STOCK_QUIT, _( "_Quit Now" ) );
734    g_signal_connect( w, "clicked", G_CALLBACK( do_exit_cb ), NULL );
735    gtk_container_add( GTK_CONTAINER( b ), w );
736    gtk_table_attach( GTK_TABLE( p ), b, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 10 );
737
738    gtk_widget_show_all( r );
739    gtk_widget_grab_focus( w );
740
741    /* clear the UI */
742    gtk_list_store_clear( GTK_LIST_STORE( tr_core_model( cbdata->core ) ) );
743
744    /* ensure the window is in its previous position & size.
745     * this seems to be necessary because changing the main window's
746     * child seems to unset the size */
747    gtk_window_resize( cbdata->wind, pref_int_get( PREF_KEY_MAIN_WINDOW_WIDTH ),
748                                     pref_int_get( PREF_KEY_MAIN_WINDOW_HEIGHT ) );
749    gtk_window_move( cbdata->wind, pref_int_get( PREF_KEY_MAIN_WINDOW_X ),
750                                   pref_int_get( PREF_KEY_MAIN_WINDOW_Y ) );
751
752    /* shut down libT */
753    g_thread_create( quitThreadFunc, vdata, TRUE, NULL );
754}
755
756static void
757gotdrag( GtkWidget         * widget UNUSED,
758         GdkDragContext *           dc,
759         gint                x      UNUSED,
760         gint                y      UNUSED,
761         GtkSelectionData *         sel,
762         guint               info   UNUSED,
763         guint                      time,
764         gpointer                   gdata )
765{
766    struct cbdata * data = gdata;
767    GSList *        paths = NULL;
768    GSList *        freeme = NULL;
769
770#if 0
771    int             i;
772    char *          sele = gdk_atom_name( sel->selection );
773    char *          targ = gdk_atom_name( sel->target );
774    char *          type = gdk_atom_name( sel->type );
775
776    g_message( "dropped file: sel=%s targ=%s type=%s fmt=%i len=%i",
777               sele, targ, type, sel->format, sel->length );
778    g_free( sele );
779    g_free( targ );
780    g_free( type );
781    if( sel->format == 8 )
782    {
783        for( i = 0; i < sel->length; ++i )
784            fprintf( stderr, "%02X ", sel->data[i] );
785        fprintf( stderr, "\n" );
786    }
787#endif
788
789    if( ( sel->format == 8 )
790      && ( sel->selection == gdk_atom_intern( "XdndSelection", FALSE ) ) )
791    {
792        int      i;
793        char *   str = g_strndup( (char*)sel->data, sel->length );
794        gchar ** files = g_strsplit_set( str, "\r\n", -1 );
795        for( i = 0; files && files[i]; ++i )
796        {
797            char * filename;
798            if( !*files[i] ) /* empty filename... */
799                continue;
800
801            /* decode the filename */
802            filename = decode_uri( files[i] );
803            freeme = g_slist_prepend( freeme, filename );
804            if( !g_utf8_validate( filename, -1, NULL ) )
805                continue;
806
807            /* walk past "file://", if present */
808            if( g_str_has_prefix( filename, "file:" ) )
809            {
810                filename += 5;
811                while( g_str_has_prefix( filename, "//" ) )
812                    ++filename;
813            }
814
815            /* if the file doesn't exist, the first part
816               might be a hostname ... walk past it. */
817            if( !g_file_test( filename, G_FILE_TEST_EXISTS ) )
818            {
819                char * pch = strchr( filename + 1, '/' );
820                if( pch != NULL )
821                    filename = pch;
822            }
823
824            /* finally, add it to the list of torrents to try adding */
825            if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
826                paths = g_slist_prepend( paths, g_strdup( filename ) );
827        }
828
829        /* try to add any torrents we found */
830        if( paths )
831        {
832            paths = g_slist_reverse( paths );
833            tr_core_add_list_defaults( data->core, paths, TRUE );
834            tr_core_torrents_added( data->core );
835        }
836
837        freestrlist( freeme );
838        g_strfreev( files );
839        g_free( str );
840    }
841
842    gtk_drag_finish( dc, ( NULL != paths ), FALSE, time );
843}
844
845static void
846setupdrag( GtkWidget *    widget,
847           struct cbdata *data )
848{
849    GtkTargetEntry targets[] = {
850        { (char*)"STRING",          0, 0 },
851        { (char*)"text/plain",      0, 0 },
852        { (char*)"text/uri-list",   0, 0 },
853    };
854
855    g_signal_connect( widget, "drag_data_received", G_CALLBACK(
856                          gotdrag ), data );
857
858    gtk_drag_dest_set( widget, GTK_DEST_DEFAULT_ALL, targets,
859                       G_N_ELEMENTS( targets ), GDK_ACTION_COPY | GDK_ACTION_MOVE );
860}
861
862static void
863flushAddTorrentErrors( GtkWindow *  window,
864                       const char * primary,
865                       GSList **    files )
866{
867    GString *   s = g_string_new( NULL );
868    GSList *    l;
869    GtkWidget * w;
870
871    if( g_slist_length( *files ) > 1 ) {
872        for( l=*files; l!=NULL; l=l->next )
873            g_string_append_printf( s, "\xE2\x88\x99 %s\n", (const char*)l->data );
874    } else {
875        for( l=*files; l!=NULL; l=l->next )
876            g_string_append_printf( s, "%s\n", (const char*)l->data );
877    }
878    w = gtk_message_dialog_new( window,
879                                GTK_DIALOG_DESTROY_WITH_PARENT,
880                                GTK_MESSAGE_ERROR,
881                                GTK_BUTTONS_CLOSE,
882                                "%s", primary );
883    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
884                                              "%s", s->str );
885    g_signal_connect_swapped( w, "response",
886                              G_CALLBACK( gtk_widget_destroy ), w );
887    gtk_widget_show_all( w );
888    g_string_free( s, TRUE );
889
890    g_slist_foreach( *files, (GFunc)g_free, NULL );
891    g_slist_free( *files );
892    *files = NULL;
893}
894
895static void
896showTorrentErrors( struct cbdata * cbdata )
897{
898    if( cbdata->errqueue )
899        flushAddTorrentErrors( GTK_WINDOW( cbdata->wind ),
900                               ngettext( "Couldn't add corrupt torrent",
901                                         "Couldn't add corrupt torrents",
902                                         g_slist_length( cbdata->errqueue ) ),
903                               &cbdata->errqueue );
904
905    if( cbdata->dupqueue )
906        flushAddTorrentErrors( GTK_WINDOW( cbdata->wind ),
907                               ngettext( "Couldn't add duplicate torrent",
908                                         "Couldn't add duplicate torrents",
909                                         g_slist_length( cbdata->dupqueue ) ),
910                               &cbdata->dupqueue );
911}
912
913static void
914coreerr( TrCore * core UNUSED, guint code, const char * msg, struct cbdata * c )
915{
916    switch( code )
917    {
918        case TR_PARSE_ERR:
919            c->errqueue =
920                g_slist_append( c->errqueue, g_path_get_basename( msg ) );
921            break;
922
923        case TR_PARSE_DUPLICATE:
924            c->dupqueue = g_slist_append( c->dupqueue, g_strdup( msg ) );
925            break;
926
927        case TR_CORE_ERR_NO_MORE_TORRENTS:
928            showTorrentErrors( c );
929            break;
930
931        default:
932            g_assert_not_reached( );
933            break;
934    }
935}
936
937#if GTK_CHECK_VERSION( 2, 8, 0 )
938static void
939on_main_window_focus_in( GtkWidget      * widget UNUSED,
940                         GdkEventFocus  * event  UNUSED,
941                         gpointer                gdata )
942{
943    struct cbdata * cbdata = gdata;
944
945    if( cbdata->wind )
946        gtk_window_set_urgency_hint( GTK_WINDOW( cbdata->wind ), FALSE );
947}
948
949#endif
950
951static void
952onAddTorrent( TrCore *  core,
953              tr_ctor * ctor,
954              gpointer  gdata )
955{
956    struct cbdata * cbdata = gdata;
957    GtkWidget *     w = addSingleTorrentDialog( cbdata->wind, core, ctor );
958
959#if GTK_CHECK_VERSION( 2, 8, 0 )
960    g_signal_connect( w, "focus-in-event",
961                      G_CALLBACK( on_main_window_focus_in ),  cbdata );
962    if( cbdata->wind )
963        gtk_window_set_urgency_hint( cbdata->wind, TRUE );
964#endif
965}
966
967static void
968prefschanged( TrCore * core UNUSED,
969              const char *  key,
970              gpointer      data )
971{
972    struct cbdata  * cbdata = data;
973    tr_session     * tr     = tr_core_session( cbdata->core );
974
975    if( !strcmp( key, TR_PREFS_KEY_ENCRYPTION ) )
976    {
977        tr_sessionSetEncryption( tr, pref_int_get( key ) );
978    }
979    else if( !strcmp( key, TR_PREFS_KEY_DOWNLOAD_DIR ) )
980    {
981        tr_sessionSetDownloadDir( tr, pref_string_get( key ) );
982    }
983    else if( !strcmp( key, TR_PREFS_KEY_MSGLEVEL ) )
984    {
985        tr_setMessageLevel( pref_int_get( key ) );
986    }
987    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT ) )
988    {
989        tr_sessionSetPeerPort( tr, pref_int_get( key ) );
990    }
991    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_ENABLED ) )
992    {
993        tr_blocklistSetEnabled( tr, pref_flag_get( key ) );
994    }
995    else if( !strcmp( key, PREF_KEY_SHOW_TRAY_ICON ) )
996    {
997        const int show = pref_flag_get( key );
998        if( show && !cbdata->icon )
999            cbdata->icon = tr_icon_new( cbdata->core );
1000        else if( !show && cbdata->icon ) {
1001            g_object_unref( cbdata->icon );
1002            cbdata->icon = NULL;
1003        }
1004    }
1005    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_ENABLED ) )
1006    {
1007        tr_sessionLimitSpeed( tr, TR_DOWN, pref_flag_get( key ) );
1008    }
1009    else if( !strcmp( key, TR_PREFS_KEY_DSPEED ) )
1010    {
1011        tr_sessionSetSpeedLimit( tr, TR_DOWN, pref_int_get( key ) );
1012    }
1013    else if( !strcmp( key, TR_PREFS_KEY_USPEED_ENABLED ) )
1014    {
1015        tr_sessionLimitSpeed( tr, TR_UP, pref_flag_get( key ) );
1016    }
1017    else if( !strcmp( key, TR_PREFS_KEY_USPEED ) )
1018    {
1019        tr_sessionSetSpeedLimit( tr, TR_UP, pref_int_get( key ) );
1020    }
1021    else if( !strcmp( key, TR_PREFS_KEY_RATIO_ENABLED ) )
1022    {
1023        tr_sessionSetRatioLimited( tr, pref_flag_get( key ) );
1024    }
1025    else if( !strcmp( key, TR_PREFS_KEY_RATIO ) )
1026    {
1027        tr_sessionSetRatioLimit( tr, pref_double_get( key ) );
1028    }
1029    else if( !strcmp( key, TR_PREFS_KEY_PORT_FORWARDING ) )
1030    {
1031        tr_sessionSetPortForwardingEnabled( tr, pref_flag_get( key ) );
1032    }
1033    else if( !strcmp( key, TR_PREFS_KEY_PEX_ENABLED ) )
1034    {
1035        tr_sessionSetPexEnabled( tr, pref_flag_get( key ) );
1036    }
1037    else if( !strcmp( key, TR_PREFS_KEY_DHT_ENABLED ) )
1038    {
1039        tr_sessionSetDHTEnabled( tr, pref_flag_get( key ) );
1040    }
1041    else if( !strcmp( key, TR_PREFS_KEY_RPC_PORT ) )
1042    {
1043        tr_sessionSetRPCPort( tr, pref_int_get( key ) );
1044    }
1045    else if( !strcmp( key, TR_PREFS_KEY_RPC_ENABLED ) )
1046    {
1047        tr_sessionSetRPCEnabled( tr, pref_flag_get( key ) );
1048    }
1049    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST ) )
1050    {
1051        tr_sessionSetRPCWhitelist( tr, pref_string_get( key ) );
1052    }
1053    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST_ENABLED ) )
1054    {
1055        tr_sessionSetRPCWhitelistEnabled( tr, pref_flag_get( key ) );
1056    }
1057    else if( !strcmp( key, TR_PREFS_KEY_RPC_USERNAME ) )
1058    {
1059        tr_sessionSetRPCUsername( tr, pref_string_get( key ) );
1060    }
1061    else if( !strcmp( key, TR_PREFS_KEY_RPC_PASSWORD ) )
1062    {
1063        tr_sessionSetRPCPassword( tr, pref_string_get( key ) );
1064    }
1065    else if( !strcmp( key, TR_PREFS_KEY_RPC_AUTH_REQUIRED ) )
1066    {
1067        tr_sessionSetRPCPasswordEnabled( tr, pref_flag_get( key ) );
1068    }
1069    else if( !strcmp( key, TR_PREFS_KEY_PROXY ) )
1070    {
1071        tr_sessionSetProxy( tr, pref_string_get( key ) );
1072    }
1073    else if( !strcmp( key, TR_PREFS_KEY_PROXY_TYPE ) )
1074    {
1075        tr_sessionSetProxyType( tr, pref_int_get( key ) );
1076    }
1077    else if( !strcmp( key, TR_PREFS_KEY_PROXY_ENABLED ) )
1078    {
1079        tr_sessionSetProxyEnabled( tr, pref_flag_get( key ) );
1080    }
1081    else if( !strcmp( key, TR_PREFS_KEY_PROXY_AUTH_ENABLED ) )
1082    {
1083        tr_sessionSetProxyAuthEnabled( tr, pref_flag_get( key ) );
1084    }
1085    else if( !strcmp( key, TR_PREFS_KEY_PROXY_USERNAME ) )
1086    {
1087        tr_sessionSetProxyUsername( tr, pref_string_get( key ) );
1088    }
1089    else if( !strcmp( key, TR_PREFS_KEY_PROXY_PASSWORD ) )
1090    {
1091        tr_sessionSetProxyPassword( tr, pref_string_get( key ) );
1092    }
1093    else if( !strcmp( key, TR_PREFS_KEY_PROXY_PORT ) )
1094    {
1095        tr_sessionSetProxyPort( tr, pref_int_get( key ) );
1096    }
1097    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_UP ) )
1098    {
1099        tr_sessionSetAltSpeed( tr, TR_UP, pref_int_get( key ) );
1100    }
1101    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_DOWN ) )
1102    {
1103        tr_sessionSetAltSpeed( tr, TR_DOWN, pref_int_get( key ) );
1104    }
1105    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_ENABLED ) )
1106    {
1107        const gboolean b = pref_flag_get( key );
1108        tr_sessionUseAltSpeed( tr, b );
1109        action_toggle( key, b );
1110    }
1111    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN ) )
1112    {
1113        tr_sessionSetAltSpeedBegin( tr, pref_int_get( key ) );
1114    }
1115    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_END ) )
1116    {
1117        tr_sessionSetAltSpeedEnd( tr, pref_int_get( key ) );
1118    }
1119    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED ) )
1120    {
1121        tr_sessionUseAltSpeedTime( tr, pref_flag_get( key ) );
1122    }
1123    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_DAY ) )
1124    {
1125        tr_sessionSetAltSpeedDay( tr, pref_int_get( key ) );
1126    }
1127    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START ) )
1128    {
1129        tr_sessionSetPeerPortRandomOnStart( tr, pref_flag_get( key ) );
1130    }
1131    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR ) )
1132    {
1133        tr_sessionSetIncompleteDir( tr, pref_string_get( key ) );
1134    }
1135    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED ) )
1136    {
1137        tr_sessionSetIncompleteDirEnabled( tr, pref_flag_get( key ) );
1138    }
1139}
1140
1141static gboolean
1142updatemodel( gpointer gdata )
1143{
1144    struct cbdata *data = gdata;
1145    const gboolean done = data->isClosing || global_sigcount;
1146
1147    if( !done )
1148    {
1149        /* update the torrent data in the model */
1150        tr_core_update( data->core );
1151
1152        /* update the main window's statusbar and toolbar buttons */
1153        if( data->wind )
1154            tr_window_update( data->wind );
1155
1156        /* update the actions */
1157        refreshActions( data );
1158    }
1159
1160    return !done;
1161}
1162
1163static void
1164aboutDialogActivateLink( GtkAboutDialog * dialog    UNUSED,
1165                         const gchar *              link_,
1166                         gpointer         user_data UNUSED )
1167{
1168    gtr_open_file( link_ );
1169}
1170
1171static void
1172about( GtkWindow * parent )
1173{
1174    const char *authors[] =
1175    {
1176        "Charles Kerr (Backend; GTK+)",
1177        "Mitchell Livingston (Backend; OS X)",
1178        "Kevin Glowacz (Web client)",
1179        NULL
1180    };
1181
1182    const char *website_url = "http://www.transmissionbt.com/";
1183
1184    gtk_about_dialog_set_url_hook( aboutDialogActivateLink, NULL, NULL );
1185
1186    gtk_show_about_dialog( parent,
1187                           "name", g_get_application_name( ),
1188                           "comments",
1189                           _( "A fast and easy BitTorrent client" ),
1190                           "version", LONG_VERSION_STRING,
1191                           "website", website_url,
1192                           "website-label", website_url,
1193                           "copyright",
1194                           _( "Copyright 2005-2009 The Transmission Project" ),
1195                           "logo-icon-name", MY_NAME,
1196#ifdef SHOW_LICENSE
1197                           "license", LICENSE,
1198                           "wrap-license", TRUE,
1199#endif
1200                           "authors", authors,
1201                           /* Translators: translate "translator-credits" as
1202                              your name
1203                              to have it appear in the credits in the "About"
1204                              dialog */
1205                           "translator-credits", _( "translator-credits" ),
1206                           NULL );
1207}
1208
1209static void
1210startTorrentForeach( GtkTreeModel *      model,
1211                     GtkTreePath  * path UNUSED,
1212                     GtkTreeIter *       iter,
1213                     gpointer       data UNUSED )
1214{
1215    tr_torrent * tor = NULL;
1216
1217    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1218    tr_torrentStart( tor );
1219}
1220
1221static void
1222stopTorrentForeach( GtkTreeModel *      model,
1223                    GtkTreePath  * path UNUSED,
1224                    GtkTreeIter *       iter,
1225                    gpointer       data UNUSED )
1226{
1227    tr_torrent * tor = NULL;
1228
1229    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1230    tr_torrentStop( tor );
1231}
1232
1233static void
1234updateTrackerForeach( GtkTreeModel *      model,
1235                      GtkTreePath  * path UNUSED,
1236                      GtkTreeIter *       iter,
1237                      gpointer       data UNUSED )
1238{
1239    tr_torrent * tor = NULL;
1240
1241    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1242    tr_torrentManualUpdate( tor );
1243}
1244
1245static void
1246openFolderForeach( GtkTreeModel *           model,
1247                   GtkTreePath  * path      UNUSED,
1248                   GtkTreeIter *            iter,
1249                   gpointer       user_data UNUSED )
1250{
1251    TrTorrent * gtor = NULL;
1252
1253    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1254    tr_torrent_open_folder( gtor );
1255    g_object_unref( G_OBJECT( gtor ) );
1256}
1257
1258static void
1259recheckTorrentForeach( GtkTreeModel *      model,
1260                       GtkTreePath  * path UNUSED,
1261                       GtkTreeIter *       iter,
1262                       gpointer       data UNUSED )
1263{
1264    TrTorrent * gtor = NULL;
1265
1266    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1267    tr_torrentVerify( tr_torrent_handle( gtor ) );
1268    g_object_unref( G_OBJECT( gtor ) );
1269}
1270
1271static gboolean
1272msgwinclosed( void )
1273{
1274    action_toggle( "toggle-message-log", FALSE );
1275    return FALSE;
1276}
1277
1278static void
1279accumulateSelectedTorrents( GtkTreeModel *      model,
1280                            GtkTreePath  * path UNUSED,
1281                            GtkTreeIter *       iter,
1282                            gpointer            gdata )
1283{
1284    GSList **   data = ( GSList** ) gdata;
1285    TrTorrent * tor = NULL;
1286
1287    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
1288    *data = g_slist_prepend( *data, tor );
1289}
1290
1291static void
1292removeSelected( struct cbdata * data,
1293                gboolean        delete_files )
1294{
1295    GSList *           l = NULL;
1296    GtkTreeSelection * s = tr_window_get_selection( data->wind );
1297
1298    gtk_tree_selection_selected_foreach( s, accumulateSelectedTorrents, &l );
1299    gtk_tree_selection_unselect_all( s );
1300    if( l )
1301    {
1302        l = g_slist_reverse( l );
1303        confirmRemove( data->wind, data->core, l, delete_files );
1304    }
1305}
1306
1307static void
1308startAllTorrents( struct cbdata * data )
1309{
1310    tr_session * session = tr_core_session( data->core );
1311    const char * cmd = "{ \"method\": \"torrent-start\" }";
1312    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1313}
1314
1315static void
1316pauseAllTorrents( struct cbdata * data )
1317{
1318    tr_session * session = tr_core_session( data->core );
1319    const char * cmd = "{ \"method\": \"torrent-stop\" }";
1320    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1321}
1322
1323static tr_torrent*
1324getFirstSelectedTorrent( struct cbdata * data )
1325{
1326    tr_torrent * tor = NULL;
1327    GtkTreeSelection * s = tr_window_get_selection( data->wind );
1328    GtkTreeModel * m;
1329    GList * l = gtk_tree_selection_get_selected_rows( s, &m );
1330    if( l != NULL ) {
1331        GtkTreePath * p = l->data;
1332        GtkTreeIter i;
1333        if( gtk_tree_model_get_iter( m, &i, p ) )
1334            gtk_tree_model_get( m, &i, MC_TORRENT_RAW, &tor, -1 );
1335    }
1336    g_list_foreach( l, (GFunc)gtk_tree_path_free, NULL );
1337    g_list_free( l );
1338    return tor;
1339}
1340
1341static void
1342detailsClosed( gpointer gdata, GObject * dead )
1343{
1344    struct cbdata * data = gdata;
1345    data->details = g_slist_remove( data->details, dead );
1346}
1347
1348void
1349doAction( const char * action_name, gpointer user_data )
1350{
1351    struct cbdata * data = user_data;
1352    gboolean        changed = FALSE;
1353
1354    if(  !strcmp( action_name, "add-torrent-menu" )
1355      || !strcmp( action_name, "add-torrent-toolbar" ) )
1356    {
1357        addDialog( data->wind, data->core );
1358    }
1359    else if( !strcmp( action_name, "show-stats" ) )
1360    {
1361        GtkWidget * dialog = stats_dialog_create( data->wind, data->core );
1362        gtk_widget_show( dialog );
1363    }
1364    else if( !strcmp( action_name, "donate" ) )
1365    {
1366        gtr_open_file( "http://www.transmissionbt.com/donate.php" );
1367    }
1368    else if( !strcmp( action_name, "start-torrent" ) )
1369    {
1370        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1371        gtk_tree_selection_selected_foreach( s, startTorrentForeach, NULL );
1372        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1373    }
1374    else if( !strcmp( action_name, "pause-all-torrents" ) )
1375    {
1376        pauseAllTorrents( data );
1377    }
1378    else if( !strcmp( action_name, "start-all-torrents" ) )
1379    {
1380        startAllTorrents( data );
1381    }
1382    else if( !strcmp( action_name, "relocate-torrent" ) )
1383    {
1384        tr_torrent * tor = getFirstSelectedTorrent( data );
1385        if( tor )
1386        {
1387            GtkWindow * parent = GTK_WINDOW( data->wind );
1388            GtkWidget * w = gtr_relocate_dialog_new( parent, tor );
1389            gtk_widget_show( w );
1390        }
1391    }
1392    else if( !strcmp( action_name, "pause-torrent" ) )
1393    {
1394        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1395        gtk_tree_selection_selected_foreach( s, stopTorrentForeach, NULL );
1396        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1397    }
1398    else if( !strcmp( action_name, "verify-torrent" ) )
1399    {
1400        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1401        gtk_tree_selection_selected_foreach( s, recheckTorrentForeach, NULL );
1402        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1403    }
1404    else if( !strcmp( action_name, "open-torrent-folder" ) )
1405    {
1406        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1407        gtk_tree_selection_selected_foreach( s, openFolderForeach, data );
1408    }
1409    else if( !strcmp( action_name, "show-torrent-properties" ) )
1410    {
1411        GtkWidget * w = torrent_inspector_new( GTK_WINDOW( data->wind ), data->core );
1412        data->details = g_slist_prepend( data->details, w );
1413        g_object_weak_ref( G_OBJECT( w ), detailsClosed, data );
1414        refreshDetailsDialog( data, w );
1415        gtk_widget_show( w );
1416    }
1417    else if( !strcmp( action_name, "update-tracker" ) )
1418    {
1419        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1420        gtk_tree_selection_selected_foreach( s, updateTrackerForeach,
1421                                             data->wind );
1422    }
1423    else if( !strcmp( action_name, "new-torrent" ) )
1424    {
1425        GtkWidget * w = make_meta_ui( GTK_WINDOW( data->wind ), data->core );
1426        gtk_widget_show_all( w );
1427    }
1428    else if( !strcmp( action_name, "remove-torrent" ) )
1429    {
1430        removeSelected( data, FALSE );
1431    }
1432    else if( !strcmp( action_name, "delete-torrent" ) )
1433    {
1434        removeSelected( data, TRUE );
1435    }
1436    else if( !strcmp( action_name, "quit" ) )
1437    {
1438        askquit( data->core, data->wind, wannaquit, data );
1439    }
1440    else if( !strcmp( action_name, "select-all" ) )
1441    {
1442        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1443        gtk_tree_selection_select_all( s );
1444    }
1445    else if( !strcmp( action_name, "deselect-all" ) )
1446    {
1447        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1448        gtk_tree_selection_unselect_all( s );
1449    }
1450    else if( !strcmp( action_name, "edit-preferences" ) )
1451    {
1452        if( NULL == data->prefs )
1453        {
1454            data->prefs = tr_prefs_dialog_new( G_OBJECT( data->core ),
1455                                               data->wind );
1456            g_signal_connect( data->prefs, "destroy",
1457                              G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
1458            gtk_widget_show( GTK_WIDGET( data->prefs ) );
1459        }
1460    }
1461    else if( !strcmp( action_name, "toggle-message-log" ) )
1462    {
1463        if( !data->msgwin )
1464        {
1465            GtkWidget * win = msgwin_new( data->core );
1466            g_signal_connect( win, "destroy", G_CALLBACK( msgwinclosed ),
1467                              NULL );
1468            data->msgwin = win;
1469        }
1470        else
1471        {
1472            action_toggle( "toggle-message-log", FALSE );
1473            gtk_widget_destroy( data->msgwin );
1474            data->msgwin = NULL;
1475        }
1476    }
1477    else if( !strcmp( action_name, "show-about-dialog" ) )
1478    {
1479        about( data->wind );
1480    }
1481    else if( !strcmp ( action_name, "help" ) )
1482    {
1483        char * url = gtr_get_help_url( );
1484        gtr_open_file( url );
1485        g_free( url );
1486    }
1487    else if( !strcmp( action_name, "toggle-main-window" ) )
1488    {
1489        toggleMainWindow( data, FALSE );
1490    }
1491    else if( !strcmp( action_name, "present-main-window" ) )
1492    {
1493        toggleMainWindow( data, TRUE );
1494    }
1495    else g_error ( "Unhandled action: %s", action_name );
1496
1497    if( changed )
1498        updatemodel( data );
1499}
Note: See TracBrowser for help on using the repository browser.