source: trunk/gtk/main.c @ 3942

Last change on this file since 3942 was 3942, checked in by charles, 15 years ago

fix bug in gtk client that caused torrent changes to show up too slowly in the GUI

  • Property svn:keywords set to Date Rev Author Id
File size: 30.5 KB
Line 
1/******************************************************************************
2 * $Id: main.c 3942 2007-11-23 15:36:31Z charles $
3 *
4 * Copyright (c) 2005-2007 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 <sys/param.h>
26#include <errno.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 "actions.h"
39#include "conf.h"
40#include "dialogs.h"
41#include "ipc.h"
42#include "makemeta-ui.h"
43#include "msgwin.h"
44#include "torrent-inspector.h"
45#include "tr_cell_renderer_progress.h"
46#include "tr_core.h"
47#include "tr_icon.h"
48#include "tr_prefs.h"
49#include "tr_torrent.h"
50#include "tr_window.h"
51#include "util.h"
52#include "ui.h"
53
54#include <libtransmission/transmission.h>
55#include <libtransmission/version.h>
56
57/* time in seconds to wait for torrents to stop when exiting */
58#define TRACKER_EXIT_TIMEOUT    10
59
60/* interval in milliseconds to update the torrent list display */
61#define UPDATE_INTERVAL         1000
62
63/* interval in milliseconds to check for stopped torrents and update display */
64#define EXIT_CHECK_INTERVAL     500
65
66/* number of fatal signals required to cause an immediate exit */
67#define SIGCOUNT_MAX            3
68
69#if GTK_CHECK_VERSION(2,8,0)
70#define SHOW_LICENSE
71static const char * LICENSE = 
72"The Transmission binaries and most of its source code is distributed "
73"license. "
74"\n\n"
75"Some files are copyrighted by Charles Kerr and are covered by "
76"the GPL version 2.  Works owned by the Transmission project "
77"are granted a special exemption to clause 2(b) so that the bulk "
78"of its code can remain under the MIT license.  This exemption does "
79"not extend to original or derived works not owned by the "
80"Transmission project. "
81"\n\n"
82"Permission is hereby granted, free of charge, to any person obtaining "
83"a copy of this software and associated documentation files (the "
84"'Software'), to deal in the Software without restriction, including "
85"without limitation the rights to use, copy, modify, merge, publish, "
86"distribute, sublicense, and/or sell copies of the Software, and to "
87"permit persons to whom the Software is furnished to do so, subject to "
88"the following conditions: "
89"\n\n"
90"The above copyright notice and this permission notice shall be included "
91"in all copies or substantial portions of the Software. "
92"\n\n"
93"THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, "
94"EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF "
95"MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. "
96"IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY "
97"CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, "
98"TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE "
99"SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.";
100#endif
101
102struct cbdata {
103    GtkWindow    * wind;
104    TrCore       * core;
105    GtkWidget    * icon;
106    GtkWidget    * msgwin;
107    GtkWidget    * prefs;
108    guint          timer;
109    gboolean       closing;
110    GList        * errqueue;
111};
112
113#define CBDATA_PTR "callback-data-pointer"
114
115static GtkUIManager * myUIManager = NULL;
116
117static sig_atomic_t global_sigcount = 0;
118
119static gboolean
120sendremote( GList * files, gboolean sendquit );
121static void
122appsetup( TrWindow * wind, GList * args,
123          struct cbdata * , gboolean paused );
124static void
125winsetup( struct cbdata * cbdata, TrWindow * wind );
126static void
127makeicon( struct cbdata * cbdata );
128static void
129wannaquit( void * vdata );
130static void
131setupdrag(GtkWidget *widget, struct cbdata *data);
132static void
133gotdrag(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
134        GtkSelectionData *sel, guint info, guint time, gpointer gdata);
135
136static void
137coreerr( TrCore * core, enum tr_core_err code, const char * msg,
138         gpointer gdata );
139static void
140coreprompt( TrCore *, GList *, enum tr_torrent_action, gboolean, gpointer );
141static void
142corepromptdata( TrCore *, uint8_t *, size_t, gboolean, gpointer );
143static void
144initializeFromPrefs( struct cbdata * cbdata );
145static void
146prefschanged( TrCore * core, const char * key, gpointer data );
147static void
148setpex( tr_torrent * tor, void * arg );
149static gboolean
150updatemodel(gpointer gdata);
151static GList *
152getselection( struct cbdata * cbdata );
153
154static void
155setupsighandlers(void);
156static void
157fatalsig(int sig);
158
159struct counts_data
160{
161    int totalCount;
162    int activeCount;
163    int inactiveCount;
164};
165
166static void
167accumulateStatusForeach (GtkTreeModel * model,
168                         GtkTreePath  * path UNUSED,
169                         GtkTreeIter  * iter,
170                         gpointer       user_data )
171{
172    int status = 0;
173    struct counts_data * counts = user_data;
174
175    ++counts->totalCount;
176
177    gtk_tree_model_get( model, iter, MC_STAT, &status, -1 );
178    if( TR_STATUS_IS_ACTIVE( status ) )
179        ++counts->activeCount;
180    else
181        ++counts->inactiveCount;
182}
183
184static void
185accumulateCanUpdateForeach (GtkTreeModel * model,
186                            GtkTreePath  * path UNUSED,
187                            GtkTreeIter  * iter,
188                            gpointer       accumulated_status)
189{
190    TrTorrent * gtor = NULL;
191    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
192    *(int*)accumulated_status |=
193        tr_torrentCanManualUpdate( tr_torrent_handle( gtor ) );
194    g_object_unref( G_OBJECT( gtor ) );
195}
196
197static void
198refreshTorrentActions( GtkTreeSelection * s )
199{
200    int canUpdate;
201    struct counts_data counts;
202
203    counts.activeCount = 0;
204    counts.inactiveCount = 0;
205    counts.totalCount = 0;
206    gtk_tree_selection_selected_foreach( s, accumulateStatusForeach, &counts );
207    action_sensitize( "pause-torrent", counts.activeCount!=0 );
208    action_sensitize( "start-torrent", counts.inactiveCount!=0 );
209    action_sensitize( "remove-torrent", counts.totalCount!=0 );
210    action_sensitize( "verify-torrent", counts.totalCount!=0 );
211    action_sensitize( "show-torrent-details", counts.totalCount!=0 );
212
213    canUpdate = 0;
214    gtk_tree_selection_selected_foreach( s, accumulateCanUpdateForeach, &canUpdate );
215    action_sensitize( "update-tracker", canUpdate!=0 );
216}
217
218static void
219selectionChangedCB( GtkTreeSelection * s, gpointer unused UNUSED )
220{
221    refreshTorrentActions( s );
222}
223
224int
225main( int argc, char ** argv )
226{
227    char * err;
228    struct cbdata * cbdata = g_new (struct cbdata, 1);
229    GList * argfiles;
230    gboolean didinit = FALSE;
231    gboolean didlock = FALSE;
232    gboolean sendquit = FALSE;
233    gboolean startpaused = FALSE;
234    char * domain = "transmission";
235    GOptionEntry entries[] = {
236        { "paused", 'p', 0, G_OPTION_ARG_NONE, &startpaused,
237          _("Start with all torrents paused"), NULL },
238        { "quit", 'q', 0, G_OPTION_ARG_NONE, &sendquit,
239          _( "Request that the running instance quit"), NULL },
240        { NULL, 0, 0, 0, NULL, NULL, NULL }
241    };
242
243    /* bind the gettext domain */
244    bindtextdomain( domain, TRANSMISSIONLOCALEDIR );
245    bind_textdomain_codeset( domain, "UTF-8" );
246    textdomain( domain );
247    g_set_application_name( _( "Transmission" ) );
248
249    /* initialize gtk */
250    g_thread_init( NULL );
251    gtk_init_with_args( &argc, &argv, _("[torrent files]"), entries, domain, NULL );
252    myUIManager = gtk_ui_manager_new ();
253    actions_init ( myUIManager, cbdata );
254    gtk_ui_manager_add_ui_from_string (myUIManager, fallback_ui_file, -1, NULL);
255    gtk_ui_manager_ensure_update (myUIManager);
256    gtk_window_set_default_icon_name ( "transmission-logo" );
257
258    argfiles = checkfilenames( argc-1, argv+1 );
259    didinit = cf_init( tr_getPrefsDirectory(), NULL );
260    didlock = didinit && sendremote( argfiles, sendquit );
261    setupsighandlers( ); /* set up handlers for fatal signals */
262
263    if( ( didinit || cf_init( tr_getPrefsDirectory(), &err ) ) &&
264        ( didlock || cf_lock( &err ) ) )
265    {
266        /* create main window now to be a parent to any error dialogs */
267        GtkWindow * mainwind = GTK_WINDOW( tr_window_new( myUIManager ) );
268
269        tr_prefs_init_global( );
270
271        /* set message level here before tr_init() */
272        msgwin_loadpref( );
273        appsetup( mainwind, argfiles, cbdata, startpaused );
274    }
275    else
276    {
277        gtk_widget_show( errmsg_full( NULL, (callbackfunc_t)gtk_main_quit,
278                                      NULL, "%s", err ) );
279        g_free( err );
280    }
281
282    freestrlist(argfiles);
283
284    gtk_main();
285
286    return 0;
287}
288
289static gboolean
290sendremote( GList * files, gboolean sendquit )
291{
292    const gboolean didlock = cf_lock( NULL );
293
294    /* send files if there's another instance, otherwise start normally */
295    if( !didlock && files )
296        exit( ipc_sendfiles_blocking( files ) ? 0 : 1 );
297
298    /* either send a quit message or exit if no other instance */
299    if( sendquit )
300        exit( didlock ? 0 : !ipc_sendquit_blocking() );
301
302    return didlock;
303}
304
305static void
306appsetup( TrWindow * wind, GList * args,
307          struct cbdata * cbdata, gboolean paused )
308{
309    enum tr_torrent_action action;
310
311    /* fill out cbdata */
312    cbdata->wind       = NULL;
313    cbdata->core       = tr_core_new();
314    cbdata->icon       = NULL;
315    cbdata->msgwin     = NULL;
316    cbdata->prefs      = NULL;
317    cbdata->timer      = 0;
318    cbdata->closing    = FALSE;
319    cbdata->errqueue   = NULL;
320
321    /* set up core handlers */
322    g_signal_connect( cbdata->core, "error", G_CALLBACK( coreerr ), cbdata );
323    g_signal_connect( cbdata->core, "directory-prompt",
324                      G_CALLBACK( coreprompt ), cbdata );
325    g_signal_connect( cbdata->core, "directory-prompt-data",
326                      G_CALLBACK( corepromptdata ), cbdata );
327    g_signal_connect_swapped( cbdata->core, "quit",
328                              G_CALLBACK( wannaquit ), cbdata );
329    g_signal_connect( cbdata->core, "prefs-changed",
330                      G_CALLBACK( prefschanged ), cbdata );
331
332    /* apply a few prefs */
333    initializeFromPrefs( cbdata );
334
335    /* add torrents from command-line and saved state */
336    tr_core_load( cbdata->core, paused );
337
338    if( NULL != args )
339    {
340        action = tr_prefs_get_action( PREF_KEY_ADDIPC );
341        tr_core_add_list( cbdata->core, args, action, paused );
342    }
343    tr_core_torrents_added( cbdata->core );
344
345    /* set up the ipc socket */
346    ipc_socket_setup( GTK_WINDOW( wind ), cbdata->core );
347
348    /* set up main window */
349    winsetup( cbdata, wind );
350
351    /* start model update timer */
352    cbdata->timer = g_timeout_add( UPDATE_INTERVAL, updatemodel, cbdata );
353    updatemodel( cbdata );
354
355    /* show the window */
356    gtk_widget_show( GTK_WIDGET(wind) );
357}
358
359static gboolean
360winclose( GtkWidget * w UNUSED, GdkEvent * event UNUSED, gpointer gdata )
361{
362    struct cbdata * cbdata = gdata;
363
364    if( cbdata->icon != NULL )
365        gtk_widget_hide( GTK_WIDGET( cbdata->wind ) );
366    else
367        askquit( cbdata->core, cbdata->wind, wannaquit, cbdata );
368
369    return TRUE; /* don't propagate event further */
370}
371
372static void
373rowChangedCB( GtkTreeModel  * model UNUSED,
374              GtkTreePath   * path,
375              GtkTreeIter   * iter UNUSED,
376              gpointer        sel)
377{
378    if( gtk_tree_selection_path_is_selected ( sel, path ) )
379        refreshTorrentActions( GTK_TREE_SELECTION(sel) );
380}
381
382static void
383winsetup( struct cbdata * cbdata, TrWindow * wind )
384{
385    GtkTreeModel * model;
386    GtkTreeSelection * sel;
387
388    g_assert( NULL == cbdata->wind );
389    cbdata->wind = GTK_WINDOW( wind );
390
391    sel = tr_window_get_selection( cbdata->wind );
392    g_signal_connect( sel, "changed", G_CALLBACK(selectionChangedCB), NULL );
393    selectionChangedCB( sel, NULL );
394    model = tr_core_model( cbdata->core );
395    gtk_tree_view_set_model ( gtk_tree_selection_get_tree_view(sel), model );
396    g_signal_connect( model, "row-changed", G_CALLBACK(rowChangedCB), sel );
397    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
398    refreshTorrentActions( sel );
399   
400    setupdrag( GTK_WIDGET(wind), cbdata );
401}
402
403static void
404makeicon( struct cbdata * cbdata )
405{
406    if( cbdata->icon == NULL )
407        cbdata->icon = tr_icon_new( );
408}
409
410static gpointer
411quitThreadFunc( gpointer gdata )
412{
413    struct cbdata * cbdata = gdata;
414
415    tr_close( tr_core_handle( cbdata->core ) );
416
417    /* shutdown the gui */
418    if( cbdata->prefs )
419        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
420    if( cbdata->wind )
421        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
422    g_object_unref( cbdata->core );
423    if( cbdata->icon )
424        g_object_unref( cbdata->icon );
425    if( cbdata->errqueue ) {
426        g_list_foreach( cbdata->errqueue, (GFunc)g_free, NULL );
427        g_list_free( cbdata->errqueue );
428    }
429    g_free( cbdata );
430
431    /* exit the gtk main loop */
432    gtk_main_quit( );
433    return NULL;
434}
435
436static void
437wannaquit( void * vdata )
438{
439    struct cbdata * cbdata = vdata;
440
441    /* stop the update timer */
442    if( cbdata->timer ) {
443        g_source_remove( cbdata->timer );
444        cbdata->timer = 0;
445    }
446
447    /* clear the UI */
448    gtk_list_store_clear( GTK_LIST_STORE( tr_core_model( cbdata->core ) ) );
449    gtk_widget_set_sensitive( GTK_WIDGET( cbdata->wind ), FALSE );
450
451    /* shut down libT */
452    g_thread_create( quitThreadFunc, vdata, TRUE, NULL );
453}
454
455static void
456gotdrag( GtkWidget         * widget UNUSED,
457         GdkDragContext    * dc,
458         gint                x UNUSED,
459         gint                y UNUSED,
460         GtkSelectionData  * sel,
461         guint               info UNUSED,
462         guint               time,
463         gpointer            gdata )
464{
465    struct cbdata * data = gdata;
466    GList * paths = NULL;
467    GList * freeme = NULL;
468
469#ifdef DND_DEBUG
470    char *sele = gdk_atom_name(sel->selection);
471    char *targ = gdk_atom_name(sel->target);
472    char *type = gdk_atom_name(sel->type);
473
474    g_message( "dropped file: sel=%s targ=%s type=%s fmt=%i len=%i",
475               sele, targ, type, sel->format, sel->length );
476    g_free(sele);
477    g_free(targ);
478    g_free(type);
479    if( sel->format == 8 ) {
480        for( i=0; i<sel->length; ++i )
481            fprintf(stderr, "%02X ", sel->data[i]);
482        fprintf(stderr, "\n");
483    }
484#endif
485
486    if( ( sel->format == 8 ) &&
487        ( sel->selection == gdk_atom_intern( "XdndSelection", FALSE ) ) )
488    {
489        int i;
490        char * str = g_strndup( (char*)sel->data, sel->length );
491        gchar ** files = g_strsplit_set( str, "\r\n", -1 );
492        for( i=0; files && files[i]; ++i )
493        {
494            char * filename;
495            if( !*files[i] ) /* empty filename... */
496                continue;
497
498            /* decode the filename */
499            filename = urldecode( files[i], -1 );
500            freeme = g_list_prepend( freeme, filename );
501            if( !g_utf8_validate( filename, -1, NULL ) )
502                continue;
503
504            /* walk past "file://", if present */
505            if( g_str_has_prefix( filename, "file:" ) ) {
506                filename += 5;
507                while( g_str_has_prefix( filename, "//" ) )
508                    ++filename;
509            }
510
511            /* if the file doesn't exist, the first part
512               might be a hostname ... walk past it. */
513            if( !g_file_test( filename, G_FILE_TEST_EXISTS ) ) {
514                char * pch = strchr( filename + 1, '/' );
515                if( pch != NULL )
516                    filename = pch;
517            }
518
519            /* finally, add it to the list of torrents to try adding */
520            if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
521                paths = g_list_prepend( paths, filename );
522        }
523
524        /* try to add any torrents we found */
525        if( paths != NULL )
526        {
527            enum tr_torrent_action action = tr_prefs_get_action( PREF_KEY_ADDSTD );
528            paths = g_list_reverse( paths );
529            tr_core_add_list( data->core, paths, action, FALSE );
530            tr_core_torrents_added( data->core );
531            g_list_free( paths );
532        }
533
534        freestrlist( freeme );
535        g_strfreev( files );
536        g_free( str );
537    }
538
539    gtk_drag_finish(dc, (NULL != paths), FALSE, time);
540}
541
542static void
543setupdrag(GtkWidget *widget, struct cbdata *data) {
544  GtkTargetEntry targets[] = {
545    { "STRING",     0, 0 },
546    { "text/plain", 0, 0 },
547    { "text/uri-list", 0, 0 },
548  };
549
550  g_signal_connect(widget, "drag_data_received", G_CALLBACK(gotdrag), data);
551
552  gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, targets,
553                    ALEN(targets), GDK_ACTION_COPY | GDK_ACTION_MOVE);
554}
555
556static void
557coreerr( TrCore * core UNUSED, enum tr_core_err code, const char * msg,
558         gpointer gdata )
559{
560    struct cbdata * cbdata = gdata;
561    char          * joined;
562
563    switch( code )
564    {
565        case TR_CORE_ERR_ADD_TORRENT:
566            cbdata->errqueue = g_list_append( cbdata->errqueue,
567                                              g_strdup( msg ) );
568            return;
569        case TR_CORE_ERR_NO_MORE_TORRENTS:
570            if( NULL != cbdata->errqueue )
571            {
572                joined = joinstrlist( cbdata->errqueue, "\n" );
573                errmsg( cbdata->wind,
574                        ngettext( "Failed to load torrent file:\n%s",
575                                  "Failed to load torrent files:\n%s",
576                                  g_list_length( cbdata->errqueue ) ),
577                        joined );
578                g_list_foreach( cbdata->errqueue, (GFunc) g_free, NULL );
579                g_list_free( cbdata->errqueue );
580                cbdata->errqueue = NULL;
581                g_free( joined );
582            }
583            return;
584        case TR_CORE_ERR_SAVE_STATE:
585            errmsg( cbdata->wind, "%s", msg );
586            return;
587    }
588
589    g_assert_not_reached();
590}
591
592void
593coreprompt( TrCore * core, GList * paths, enum tr_torrent_action act,
594            gboolean paused, gpointer gdata )
595{
596    struct cbdata * cbdata = gdata;
597
598    promptfordir( cbdata->wind, core, paths, NULL, 0, act, paused );
599}
600
601void
602corepromptdata( TrCore * core, uint8_t * data, size_t size,
603                gboolean paused, gpointer gdata )
604{
605    struct cbdata * cbdata = gdata;
606
607    promptfordir( cbdata->wind, core, NULL, data, size, TR_TOR_LEAVE, paused );
608}
609
610static void
611initializeFromPrefs( struct cbdata * cbdata )
612{
613    size_t i;
614    const char * keys[] =
615    {
616        PREF_KEY_PORT,
617        PREF_KEY_DL_LIMIT_ENABLED,
618        PREF_KEY_DL_LIMIT,
619        PREF_KEY_UL_LIMIT_ENABLED,
620        PREF_KEY_UL_LIMIT,
621        PREF_KEY_NAT,
622        PREF_KEY_PEX,
623        PREF_KEY_SYSTRAY,
624        PREF_KEY_SORT_COLUMN,
625        PREF_KEY_ENCRYPTED_ONLY
626    };
627
628    for( i=0; i<G_N_ELEMENTS(keys); ++i )
629        prefschanged( NULL, keys[i], cbdata );
630}
631
632static void
633prefschanged( TrCore * core UNUSED, const char * key, gpointer data )
634{
635    struct cbdata * cbdata = data;
636    tr_handle     * tr     = tr_core_handle( cbdata->core );
637
638    if( !strcmp( key, PREF_KEY_ENCRYPTED_ONLY ) )
639    {
640        const gboolean crypto_only = pref_flag_get( key );
641        tr_setEncryptionMode( tr, crypto_only ? TR_ENCRYPTION_REQUIRED
642                                              : TR_ENCRYPTION_PREFERRED );
643    }
644    else if( !strcmp( key, PREF_KEY_PORT ) )
645    {
646        const int port = pref_int_get( key );
647        tr_setBindPort( tr, port );
648    }
649    else if( !strcmp( key, PREF_KEY_DL_LIMIT_ENABLED ) )
650    {
651        const gboolean b = pref_flag_get( key );
652        tr_setUseGlobalSpeedLimit( tr, TR_DOWN, b );
653    }
654    else if( !strcmp( key, PREF_KEY_DL_LIMIT ) )
655    {
656        const int limit = pref_int_get( key );
657        tr_setGlobalSpeedLimit( tr, TR_DOWN, limit );
658    }
659    else if( !strcmp( key, PREF_KEY_UL_LIMIT_ENABLED ) )
660    {
661        const gboolean b = pref_flag_get( key );
662        tr_setUseGlobalSpeedLimit( tr, TR_UP, b );
663    }
664    else if( !strcmp( key, PREF_KEY_UL_LIMIT ) )
665    {
666        const int limit = pref_int_get( key );
667        tr_setGlobalSpeedLimit( tr, TR_UP, limit );
668    }
669    else if( !strcmp( key, PREF_KEY_NAT ) )
670    {
671        const gboolean enabled = pref_flag_get( key );
672        tr_natTraversalEnable( tr, enabled );
673    }
674    else if( !strcmp( key, PREF_KEY_SYSTRAY ) )
675    {
676        if( pref_flag_get( key ) )
677        {
678            makeicon( cbdata );
679        }
680        else if( cbdata->icon )
681        {
682            g_object_unref( cbdata->icon );
683            cbdata->icon = NULL;
684        }
685    }
686    else if( !strcmp( key, PREF_KEY_SORT_COLUMN ) )
687    {
688        tr_core_set_sort_column_from_prefs( cbdata->core );
689    }
690    else if( !strcmp( key, PREF_KEY_PEX ) )
691    {
692        gboolean enabled = pref_flag_get( key );
693        tr_torrentIterate( tr, setpex, &enabled );
694    }
695}
696
697void
698setpex( tr_torrent * tor, void * arg )
699{
700    gboolean * val;
701
702    val = arg;
703    tr_torrentDisablePex( tor, !(*val) );
704}
705
706gboolean
707updatemodel(gpointer gdata) {
708  struct cbdata *data = gdata;
709  float up, down;
710
711  if( !data->closing && 0 < global_sigcount )
712  {
713      wannaquit( data );
714      return FALSE;
715  }
716
717  /* update the torrent data in the model */
718  tr_core_update( data->core );
719
720  /* update the main window's statusbar and toolbar buttons */
721  if( NULL != data->wind )
722  {
723      tr_torrentRates( tr_core_handle( data->core ), &down, &up );
724      tr_window_update( data->wind, down, up );
725  }
726
727  /* update the message window */
728  msgwin_update();
729
730  return TRUE;
731}
732
733/* returns a GList of GtkTreeRowReferences to each selected row */
734static GList *
735getselection( struct cbdata * cbdata )
736{
737    GList * rows = NULL;
738
739    if( NULL != cbdata->wind )
740    {
741        GList * ii;
742        GtkTreeSelection *s = tr_window_get_selection(cbdata->wind);
743        GtkTreeModel * model = tr_core_model( cbdata->core );
744        rows = gtk_tree_selection_get_selected_rows( s, NULL );
745        for( ii = rows; NULL != ii; ii = ii->next )
746        {
747            GtkTreeRowReference * ref = gtk_tree_row_reference_new(
748                model, ii->data );
749            gtk_tree_path_free( ii->data );
750            ii->data = ref;
751        }
752    }
753
754    return rows;
755}
756
757static void
758about ( void )
759{
760  GtkWidget * w = gtk_about_dialog_new ();
761  GtkAboutDialog * a = GTK_ABOUT_DIALOG (w);
762  const char *authors[] = { "Charles Kerr (Backend; GTK+)",
763                            "Mitchell Livingston (Backend; OS X)",
764                            "Eric Petit (Backend; OS X)",
765                            "Josh Elsasser (Daemon; Backend; GTK+)",
766                            "Bryan Varner (BeOS)", 
767                            NULL };
768  gtk_about_dialog_set_version (a, LONG_VERSION_STRING );
769#ifdef SHOW_LICENSE
770  gtk_about_dialog_set_license (a, LICENSE);
771  gtk_about_dialog_set_wrap_license (a, TRUE);
772#endif
773  gtk_about_dialog_set_logo_icon_name( a, "transmission-logo" );
774  gtk_about_dialog_set_comments( a, _("A fast and easy BitTorrent client") );
775  gtk_about_dialog_set_website( a, "http://transmission.m0k.org/" );
776  gtk_about_dialog_set_copyright( a, _("Copyright 2005-2007 The Transmission Project") );
777  gtk_about_dialog_set_authors( a, authors );
778  /* note to translators: put yourself here for credit in the "About" dialog */
779  gtk_about_dialog_set_translator_credits( a, _("translator-credits") );
780  g_signal_connect_swapped( w, "response", G_CALLBACK (gtk_widget_destroy), w );
781  gtk_widget_show_all( w );
782}
783
784static void
785startTorrentForeach (GtkTreeModel * model,
786                     GtkTreePath  * path UNUSED,
787                     GtkTreeIter  * iter,
788                     gpointer       data UNUSED)
789{
790    TrTorrent * tor = NULL;
791    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
792    tr_torrent_start( tor );
793    g_object_unref( G_OBJECT( tor ) );
794}
795
796static void
797stopTorrentForeach (GtkTreeModel * model,
798                    GtkTreePath  * path UNUSED,
799                    GtkTreeIter  * iter,
800                    gpointer       data UNUSED)
801{
802    TrTorrent * tor = NULL;
803    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
804    tr_torrent_stop( tor );
805    g_object_unref( G_OBJECT( tor ) );
806}
807
808static void
809updateTrackerForeach (GtkTreeModel * model,
810                      GtkTreePath  * path UNUSED,
811                      GtkTreeIter  * iter,
812                      gpointer       data UNUSED)
813{
814    TrTorrent * tor = NULL;
815    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
816    tr_manualUpdate( tr_torrent_handle( tor ) );
817    g_object_unref( G_OBJECT( tor ) );
818}
819
820static void
821showInfoForeach (GtkTreeModel * model,
822                 GtkTreePath  * path UNUSED,
823                 GtkTreeIter  * iter,
824                 gpointer       data UNUSED)
825{
826    TrTorrent * tor = NULL;
827    GtkWidget * w;
828    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
829    w = torrent_inspector_new( GTK_WINDOW(data), tor );
830    gtk_widget_show( w );
831    g_object_unref( G_OBJECT( tor ) );
832}
833
834static void
835recheckTorrentForeach (GtkTreeModel * model,
836                       GtkTreePath  * path UNUSED,
837                       GtkTreeIter  * iter,
838                       gpointer       data UNUSED)
839{
840    TrTorrent * gtor = NULL;
841    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
842    tr_torrentRecheck( tr_torrent_handle( gtor ) );
843    g_object_unref( G_OBJECT( gtor ) );
844}
845
846static gboolean
847msgwinclosed()
848{
849  action_toggle( "toggle-message-log", FALSE );
850  return FALSE;
851}
852
853void
854doAction ( const char * action_name, gpointer user_data )
855{
856    struct cbdata * data = (struct cbdata *) user_data;
857    gboolean changed = FALSE;
858
859    if (!strcmp (action_name, "add-torrent"))
860    {
861        makeaddwind( data->wind, data->core );
862    }
863    else if (!strcmp (action_name, "start-torrent"))
864    {
865        GtkTreeSelection * s = tr_window_get_selection(data->wind);
866        gtk_tree_selection_selected_foreach( s, startTorrentForeach, NULL );
867        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
868    }
869    else if (!strcmp (action_name, "pause-torrent"))
870    {
871        GtkTreeSelection * s = tr_window_get_selection(data->wind);
872        gtk_tree_selection_selected_foreach( s, stopTorrentForeach, NULL );
873        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
874    }
875    else if (!strcmp (action_name, "verify-torrent"))
876    {
877        GtkTreeSelection * s = tr_window_get_selection(data->wind);
878        gtk_tree_selection_selected_foreach( s, recheckTorrentForeach, NULL );
879        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
880    }
881    else if (!strcmp (action_name, "show-torrent-details"))
882    {
883        GtkTreeSelection * s = tr_window_get_selection(data->wind);
884        gtk_tree_selection_selected_foreach( s, showInfoForeach, data->wind );
885    }
886    else if (!strcmp( action_name, "update-tracker"))
887    {
888        GtkTreeSelection * s = tr_window_get_selection(data->wind);
889        gtk_tree_selection_selected_foreach( s, updateTrackerForeach, data->wind );
890    }
891    else if (!strcmp (action_name, "create-torrent"))
892    {
893        GtkWidget * w = make_meta_ui( GTK_WINDOW( data->wind ),
894                                      tr_core_handle( data->core ) );
895        gtk_widget_show_all( w );
896    }
897    else if (!strcmp (action_name, "remove-torrent"))
898    {
899        /* this modifies the model's contents, so can't use foreach */
900        GList *l, *sel = getselection( data );
901        GtkTreeModel *model = tr_core_model( data->core );
902        for( l=sel; l!=NULL; l=l->next )
903        {
904            GtkTreeIter iter;
905            GtkTreeRowReference * reference = (GtkTreeRowReference *) l->data;
906            GtkTreePath * path = gtk_tree_row_reference_get_path( reference );
907            gtk_tree_model_get_iter( model, &iter, path );
908            tr_core_delete_torrent( data->core, &iter );
909            gtk_tree_row_reference_free( reference );
910            changed = TRUE;
911        }
912        g_list_free( sel );
913    }
914    else if (!strcmp (action_name, "close"))
915    {
916        if( data->wind != NULL )
917            winclose( NULL, NULL, data );
918    }
919    else if (!strcmp (action_name, "quit"))
920    {
921        askquit( data->core, data->wind, wannaquit, data );
922    }
923    else if (!strcmp (action_name, "select-all"))
924    {
925        GtkTreeSelection * s = tr_window_get_selection(data->wind);
926        gtk_tree_selection_select_all( s );
927    }
928    else if (!strcmp (action_name, "unselect-all"))
929    {
930        GtkTreeSelection * s = tr_window_get_selection(data->wind);
931        gtk_tree_selection_unselect_all( s );
932    }
933    else if (!strcmp (action_name, "edit-preferences"))
934    {
935        if( NULL == data->prefs )
936        {
937            data->prefs = tr_prefs_dialog_new( G_OBJECT(data->core), data->wind );
938            g_signal_connect( data->prefs, "destroy",
939                             G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
940            gtk_widget_show( GTK_WIDGET( data->prefs ) );
941        }
942    }
943    else if (!strcmp (action_name, "toggle-message-log"))
944    {
945        if( !data->msgwin )
946        {
947            GtkWidget * win = msgwin_create( data->core );
948            g_signal_connect( win, "destroy", G_CALLBACK( msgwinclosed ), 
949                             NULL );
950            data->msgwin = win;
951        }
952        else
953        {
954            action_toggle("toggle-message-log", FALSE);
955            gtk_widget_destroy( data->msgwin );
956            data->msgwin = NULL;
957        }
958    }
959    else if (!strcmp (action_name, "show-about-dialog"))
960    {
961        about();
962    }
963    else if (!strcmp (action_name, "toggle-main-window"))
964    {
965        GtkWidget * w = GTK_WIDGET (data->wind);
966        if (GTK_WIDGET_VISIBLE(w))
967            gtk_widget_hide (w);
968        else
969            gtk_window_present (GTK_WINDOW(w));
970    }
971    else g_error ("Unhandled action: %s", action_name );
972
973    if( changed )
974        updatemodel( data );
975}
976
977
978static void
979setupsighandlers(void) {
980  int sigs[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM};
981  struct sigaction sa;
982  int ii;
983
984  memset(&sa, 0,  sizeof(sa));
985  sa.sa_handler = fatalsig;
986  for(ii = 0; ii < ALEN(sigs); ii++)
987    sigaction(sigs[ii], &sa, NULL);
988}
989
990static void
991fatalsig(int sig) {
992  struct sigaction sa;
993
994  if(SIGCOUNT_MAX <= ++global_sigcount) {
995    memset(&sa, 0,  sizeof(sa));
996    sa.sa_handler = SIG_DFL;
997    sigaction(sig, &sa, NULL);
998    raise(sig);
999  }
1000}
Note: See TracBrowser for help on using the repository browser.