source: trunk/gtk/main.c @ 3867

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

Rename "Debug Window" as "Message Log", which is the terminology the OS X client uses too

  • Property svn:keywords set to Date Rev Author Id
File size: 30.4 KB
Line 
1/******************************************************************************
2 * $Id: main.c 3867 2007-11-18 02:32:46Z 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 UNUSED,
375              GtkTreeIter   * iter UNUSED,
376              gpointer        sel)
377{
378    refreshTorrentActions( GTK_TREE_SELECTION(sel) );
379}
380
381static void
382winsetup( struct cbdata * cbdata, TrWindow * wind )
383{
384    GtkTreeModel * model;
385    GtkTreeSelection * sel;
386
387    g_assert( NULL == cbdata->wind );
388    cbdata->wind = GTK_WINDOW( wind );
389
390    sel = tr_window_get_selection( cbdata->wind );
391    g_signal_connect( sel, "changed", G_CALLBACK(selectionChangedCB), NULL );
392    selectionChangedCB( sel, NULL );
393    model = tr_core_model( cbdata->core );
394    gtk_tree_view_set_model ( gtk_tree_selection_get_tree_view(sel), model );
395    g_signal_connect( model, "row-changed", G_CALLBACK(rowChangedCB), sel );
396    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
397   
398    setupdrag( GTK_WIDGET(wind), cbdata );
399}
400
401static void
402makeicon( struct cbdata * cbdata )
403{
404    if( cbdata->icon == NULL )
405        cbdata->icon = tr_icon_new( );
406}
407
408static gpointer
409quitThreadFunc( gpointer gdata )
410{
411    struct cbdata * cbdata = gdata;
412
413    tr_close( tr_core_handle( cbdata->core ) );
414
415    /* shutdown the gui */
416    if( cbdata->prefs )
417        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
418    if( cbdata->wind )
419        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
420    g_object_unref( cbdata->core );
421    if( cbdata->icon )
422        g_object_unref( cbdata->icon );
423    if( cbdata->errqueue ) {
424        g_list_foreach( cbdata->errqueue, (GFunc)g_free, NULL );
425        g_list_free( cbdata->errqueue );
426    }
427    g_free( cbdata );
428
429    /* exit the gtk main loop */
430    gtk_main_quit( );
431    return NULL;
432}
433
434static void
435wannaquit( void * vdata )
436{
437    struct cbdata * cbdata = vdata;
438
439    /* stop the update timer */
440    if( cbdata->timer ) {
441        g_source_remove( cbdata->timer );
442        cbdata->timer = 0;
443    }
444
445    /* clear the UI */
446    gtk_list_store_clear( GTK_LIST_STORE( tr_core_model( cbdata->core ) ) );
447    gtk_widget_set_sensitive( GTK_WIDGET( cbdata->wind ), FALSE );
448
449    /* shut down libT */
450    g_thread_create( quitThreadFunc, vdata, TRUE, NULL );
451}
452
453static void
454gotdrag( GtkWidget         * widget UNUSED,
455         GdkDragContext    * dc,
456         gint                x UNUSED,
457         gint                y UNUSED,
458         GtkSelectionData  * sel,
459         guint               info UNUSED,
460         guint               time,
461         gpointer            gdata )
462{
463    struct cbdata * data = gdata;
464    GList * paths = NULL;
465    GList * freeme = NULL;
466
467#ifdef DND_DEBUG
468    char *sele = gdk_atom_name(sel->selection);
469    char *targ = gdk_atom_name(sel->target);
470    char *type = gdk_atom_name(sel->type);
471
472    g_message( "dropped file: sel=%s targ=%s type=%s fmt=%i len=%i",
473               sele, targ, type, sel->format, sel->length );
474    g_free(sele);
475    g_free(targ);
476    g_free(type);
477    if( sel->format == 8 ) {
478        for( i=0; i<sel->length; ++i )
479            fprintf(stderr, "%02X ", sel->data[i]);
480        fprintf(stderr, "\n");
481    }
482#endif
483
484    if( ( sel->format == 8 ) &&
485        ( sel->selection == gdk_atom_intern( "XdndSelection", FALSE ) ) )
486    {
487        int i;
488        char * str = g_strndup( (char*)sel->data, sel->length );
489        gchar ** files = g_strsplit_set( str, "\r\n", -1 );
490        for( i=0; files && files[i]; ++i )
491        {
492            char * filename;
493            if( !*files[i] ) /* empty filename... */
494                continue;
495
496            /* decode the filename */
497            filename = urldecode( files[i], -1 );
498            freeme = g_list_prepend( freeme, filename );
499            if( !g_utf8_validate( filename, -1, NULL ) )
500                continue;
501
502            /* walk past "file://", if present */
503            if( g_str_has_prefix( filename, "file:" ) ) {
504                filename += 5;
505                while( g_str_has_prefix( filename, "//" ) )
506                    ++filename;
507            }
508
509            /* if the file doesn't exist, the first part
510               might be a hostname ... walk past it. */
511            if( !g_file_test( filename, G_FILE_TEST_EXISTS ) ) {
512                char * pch = strchr( filename + 1, '/' );
513                if( pch != NULL )
514                    filename = pch;
515            }
516
517            /* finally, add it to the list of torrents to try adding */
518            if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
519                paths = g_list_prepend( paths, filename );
520        }
521
522        /* try to add any torrents we found */
523        if( paths != NULL )
524        {
525            enum tr_torrent_action action = tr_prefs_get_action( PREF_KEY_ADDSTD );
526            paths = g_list_reverse( paths );
527            tr_core_add_list( data->core, paths, action, FALSE );
528            tr_core_torrents_added( data->core );
529            g_list_free( paths );
530        }
531
532        freestrlist( freeme );
533        g_strfreev( files );
534        g_free( str );
535    }
536
537    gtk_drag_finish(dc, (NULL != paths), FALSE, time);
538}
539
540static void
541setupdrag(GtkWidget *widget, struct cbdata *data) {
542  GtkTargetEntry targets[] = {
543    { "STRING",     0, 0 },
544    { "text/plain", 0, 0 },
545    { "text/uri-list", 0, 0 },
546  };
547
548  g_signal_connect(widget, "drag_data_received", G_CALLBACK(gotdrag), data);
549
550  gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, targets,
551                    ALEN(targets), GDK_ACTION_COPY | GDK_ACTION_MOVE);
552}
553
554static void
555coreerr( TrCore * core UNUSED, enum tr_core_err code, const char * msg,
556         gpointer gdata )
557{
558    struct cbdata * cbdata = gdata;
559    char          * joined;
560
561    switch( code )
562    {
563        case TR_CORE_ERR_ADD_TORRENT:
564            cbdata->errqueue = g_list_append( cbdata->errqueue,
565                                              g_strdup( msg ) );
566            return;
567        case TR_CORE_ERR_NO_MORE_TORRENTS:
568            if( NULL != cbdata->errqueue )
569            {
570                joined = joinstrlist( cbdata->errqueue, "\n" );
571                errmsg( cbdata->wind,
572                        ngettext( "Failed to load torrent file:\n%s",
573                                  "Failed to load torrent files:\n%s",
574                                  g_list_length( cbdata->errqueue ) ),
575                        joined );
576                g_list_foreach( cbdata->errqueue, (GFunc) g_free, NULL );
577                g_list_free( cbdata->errqueue );
578                cbdata->errqueue = NULL;
579                g_free( joined );
580            }
581            return;
582        case TR_CORE_ERR_SAVE_STATE:
583            errmsg( cbdata->wind, "%s", msg );
584            return;
585    }
586
587    g_assert_not_reached();
588}
589
590void
591coreprompt( TrCore * core, GList * paths, enum tr_torrent_action act,
592            gboolean paused, gpointer gdata )
593{
594    struct cbdata * cbdata = gdata;
595
596    promptfordir( cbdata->wind, core, paths, NULL, 0, act, paused );
597}
598
599void
600corepromptdata( TrCore * core, uint8_t * data, size_t size,
601                gboolean paused, gpointer gdata )
602{
603    struct cbdata * cbdata = gdata;
604
605    promptfordir( cbdata->wind, core, NULL, data, size, TR_TOR_LEAVE, paused );
606}
607
608static void
609initializeFromPrefs( struct cbdata * cbdata )
610{
611    size_t i;
612    const char * keys[] =
613    {
614        PREF_KEY_PORT,
615        PREF_KEY_DL_LIMIT_ENABLED,
616        PREF_KEY_DL_LIMIT,
617        PREF_KEY_UL_LIMIT_ENABLED,
618        PREF_KEY_UL_LIMIT,
619        PREF_KEY_NAT,
620        PREF_KEY_PEX,
621        PREF_KEY_SYSTRAY,
622        PREF_KEY_SORT_COLUMN,
623        PREF_KEY_ENCRYPTED_ONLY
624    };
625
626    for( i=0; i<G_N_ELEMENTS(keys); ++i )
627        prefschanged( NULL, keys[i], cbdata );
628}
629
630static void
631prefschanged( TrCore * core UNUSED, const char * key, gpointer data )
632{
633    struct cbdata * cbdata = data;
634    tr_handle     * tr     = tr_core_handle( cbdata->core );
635
636    if( !strcmp( key, PREF_KEY_ENCRYPTED_ONLY ) )
637    {
638        const gboolean crypto_only = pref_flag_get( key );
639        tr_setEncryptionMode( tr, crypto_only ? TR_ENCRYPTION_REQUIRED
640                                              : TR_ENCRYPTION_PREFERRED );
641    }
642    else if( !strcmp( key, PREF_KEY_PORT ) )
643    {
644        const int port = pref_int_get( key );
645        tr_setBindPort( tr, port );
646    }
647    else if( !strcmp( key, PREF_KEY_DL_LIMIT_ENABLED ) )
648    {
649        const gboolean b = pref_flag_get( key );
650        tr_setUseGlobalSpeedLimit( tr, TR_DOWN, b );
651    }
652    else if( !strcmp( key, PREF_KEY_DL_LIMIT ) )
653    {
654        const int limit = pref_int_get( key );
655        tr_setGlobalSpeedLimit( tr, TR_DOWN, limit );
656    }
657    else if( !strcmp( key, PREF_KEY_UL_LIMIT_ENABLED ) )
658    {
659        const gboolean b = pref_flag_get( key );
660        tr_setUseGlobalSpeedLimit( tr, TR_UP, b );
661    }
662    else if( !strcmp( key, PREF_KEY_UL_LIMIT ) )
663    {
664        const int limit = pref_int_get( key );
665        tr_setGlobalSpeedLimit( tr, TR_UP, limit );
666    }
667    else if( !strcmp( key, PREF_KEY_NAT ) )
668    {
669        const gboolean enabled = pref_flag_get( key );
670        tr_natTraversalEnable( tr, enabled );
671    }
672    else if( !strcmp( key, PREF_KEY_SYSTRAY ) )
673    {
674        if( pref_flag_get( key ) )
675        {
676            makeicon( cbdata );
677        }
678        else if( cbdata->icon )
679        {
680            g_object_unref( cbdata->icon );
681            cbdata->icon = NULL;
682        }
683    }
684    else if( !strcmp( key, PREF_KEY_SORT_COLUMN ) )
685    {
686        tr_core_set_sort_column_from_prefs( cbdata->core );
687    }
688    else if( !strcmp( key, PREF_KEY_PEX ) )
689    {
690        gboolean enabled = pref_flag_get( key );
691        tr_torrentIterate( tr, setpex, &enabled );
692    }
693}
694
695void
696setpex( tr_torrent * tor, void * arg )
697{
698    gboolean * val;
699
700    val = arg;
701    tr_torrentDisablePex( tor, !(*val) );
702}
703
704gboolean
705updatemodel(gpointer gdata) {
706  struct cbdata *data = gdata;
707  float up, down;
708
709  if( !data->closing && 0 < global_sigcount )
710  {
711      wannaquit( data );
712      return FALSE;
713  }
714
715  /* update the torrent data in the model */
716  tr_core_update( data->core );
717
718  /* update the main window's statusbar and toolbar buttons */
719  if( NULL != data->wind )
720  {
721      tr_torrentRates( tr_core_handle( data->core ), &down, &up );
722      tr_window_update( data->wind, down, up );
723  }
724
725  /* update the message window */
726  msgwin_update();
727
728  return TRUE;
729}
730
731/* returns a GList of GtkTreeRowReferences to each selected row */
732static GList *
733getselection( struct cbdata * cbdata )
734{
735    GList * rows = NULL;
736
737    if( NULL != cbdata->wind )
738    {
739        GList * ii;
740        GtkTreeSelection *s = tr_window_get_selection(cbdata->wind);
741        GtkTreeModel * model = tr_core_model( cbdata->core );
742        rows = gtk_tree_selection_get_selected_rows( s, NULL );
743        for( ii = rows; NULL != ii; ii = ii->next )
744        {
745            GtkTreeRowReference * ref = gtk_tree_row_reference_new(
746                model, ii->data );
747            gtk_tree_path_free( ii->data );
748            ii->data = ref;
749        }
750    }
751
752    return rows;
753}
754
755static void
756about ( void )
757{
758  GtkWidget * w = gtk_about_dialog_new ();
759  GtkAboutDialog * a = GTK_ABOUT_DIALOG (w);
760  const char *authors[] = { "Charles Kerr (Backend; GTK+)",
761                            "Mitchell Livingston (Backend; OS X)",
762                            "Eric Petit (Backend; OS X)",
763                            "Josh Elsasser (Daemon; Backend; GTK+)",
764                            "Bryan Varner (BeOS)", 
765                            NULL };
766  gtk_about_dialog_set_version (a, LONG_VERSION_STRING );
767#ifdef SHOW_LICENSE
768  gtk_about_dialog_set_license (a, LICENSE);
769  gtk_about_dialog_set_wrap_license (a, TRUE);
770#endif
771  gtk_about_dialog_set_logo_icon_name( a, "transmission-logo" );
772  gtk_about_dialog_set_comments( a, _("A fast and easy BitTorrent client") );
773  gtk_about_dialog_set_website( a, "http://transmission.m0k.org/" );
774  gtk_about_dialog_set_copyright( a, _("Copyright 2005-2007 The Transmission Project") );
775  gtk_about_dialog_set_authors( a, authors );
776  /* note to translators: put yourself here for credit in the "About" dialog */
777  gtk_about_dialog_set_translator_credits( a, _("translator-credits") );
778  g_signal_connect_swapped( w, "response", G_CALLBACK (gtk_widget_destroy), w );
779  gtk_widget_show_all( w );
780}
781
782static void
783startTorrentForeach (GtkTreeModel * model,
784                     GtkTreePath  * path UNUSED,
785                     GtkTreeIter  * iter,
786                     gpointer       data UNUSED)
787{
788    TrTorrent * tor = NULL;
789    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
790    tr_torrent_start( tor );
791    g_object_unref( G_OBJECT( tor ) );
792}
793
794static void
795stopTorrentForeach (GtkTreeModel * model,
796                    GtkTreePath  * path UNUSED,
797                    GtkTreeIter  * iter,
798                    gpointer       data UNUSED)
799{
800    TrTorrent * tor = NULL;
801    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
802    tr_torrent_stop( tor );
803    g_object_unref( G_OBJECT( tor ) );
804}
805
806static void
807updateTrackerForeach (GtkTreeModel * model,
808                      GtkTreePath  * path UNUSED,
809                      GtkTreeIter  * iter,
810                      gpointer       data UNUSED)
811{
812    TrTorrent * tor = NULL;
813    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
814    tr_manualUpdate( tr_torrent_handle( tor ) );
815    g_object_unref( G_OBJECT( tor ) );
816}
817
818static void
819showInfoForeach (GtkTreeModel * model,
820                 GtkTreePath  * path UNUSED,
821                 GtkTreeIter  * iter,
822                 gpointer       data UNUSED)
823{
824    TrTorrent * tor = NULL;
825    GtkWidget * w;
826    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
827    w = torrent_inspector_new( GTK_WINDOW(data), tor );
828    gtk_widget_show( w );
829    g_object_unref( G_OBJECT( tor ) );
830}
831
832static void
833recheckTorrentForeach (GtkTreeModel * model,
834                       GtkTreePath  * path UNUSED,
835                       GtkTreeIter  * iter,
836                       gpointer       data UNUSED)
837{
838    TrTorrent * gtor = NULL;
839    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
840    tr_torrentRecheck( tr_torrent_handle( gtor ) );
841    g_object_unref( G_OBJECT( gtor ) );
842}
843
844static gboolean
845msgwinclosed()
846{
847  action_toggle( "toggle-message-log", FALSE );
848  return FALSE;
849}
850
851void
852doAction ( const char * action_name, gpointer user_data )
853{
854    struct cbdata * data = (struct cbdata *) user_data;
855    gboolean changed = FALSE;
856
857    if (!strcmp (action_name, "add-torrent"))
858    {
859        makeaddwind( data->wind, data->core );
860    }
861    else if (!strcmp (action_name, "start-torrent"))
862    {
863        GtkTreeSelection * s = tr_window_get_selection(data->wind);
864        gtk_tree_selection_selected_foreach( s, startTorrentForeach, NULL );
865        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
866    }
867    else if (!strcmp (action_name, "pause-torrent"))
868    {
869        GtkTreeSelection * s = tr_window_get_selection(data->wind);
870        gtk_tree_selection_selected_foreach( s, stopTorrentForeach, NULL );
871        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
872    }
873    else if (!strcmp (action_name, "verify-torrent"))
874    {
875        GtkTreeSelection * s = tr_window_get_selection(data->wind);
876        gtk_tree_selection_selected_foreach( s, recheckTorrentForeach, NULL );
877        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
878    }
879    else if (!strcmp (action_name, "show-torrent-details"))
880    {
881        GtkTreeSelection * s = tr_window_get_selection(data->wind);
882        gtk_tree_selection_selected_foreach( s, showInfoForeach, data->wind );
883    }
884    else if (!strcmp( action_name, "update-tracker"))
885    {
886        GtkTreeSelection * s = tr_window_get_selection(data->wind);
887        gtk_tree_selection_selected_foreach( s, updateTrackerForeach, data->wind );
888    }
889    else if (!strcmp (action_name, "create-torrent"))
890    {
891        GtkWidget * w = make_meta_ui( GTK_WINDOW( data->wind ),
892                                      tr_core_handle( data->core ) );
893        gtk_widget_show_all( w );
894    }
895    else if (!strcmp (action_name, "remove-torrent"))
896    {
897        /* this modifies the model's contents, so can't use foreach */
898        GList *l, *sel = getselection( data );
899        GtkTreeModel *model = tr_core_model( data->core );
900        for( l=sel; l!=NULL; l=l->next )
901        {
902            GtkTreeIter iter;
903            GtkTreeRowReference * reference = (GtkTreeRowReference *) l->data;
904            GtkTreePath * path = gtk_tree_row_reference_get_path( reference );
905            gtk_tree_model_get_iter( model, &iter, path );
906            tr_core_delete_torrent( data->core, &iter );
907            gtk_tree_row_reference_free( reference );
908            changed = TRUE;
909        }
910        g_list_free( sel );
911    }
912    else if (!strcmp (action_name, "close"))
913    {
914        if( data->wind != NULL )
915            winclose( NULL, NULL, data );
916    }
917    else if (!strcmp (action_name, "quit"))
918    {
919        askquit( data->core, data->wind, wannaquit, data );
920    }
921    else if (!strcmp (action_name, "select-all"))
922    {
923        GtkTreeSelection * s = tr_window_get_selection(data->wind);
924        gtk_tree_selection_select_all( s );
925    }
926    else if (!strcmp (action_name, "unselect-all"))
927    {
928        GtkTreeSelection * s = tr_window_get_selection(data->wind);
929        gtk_tree_selection_unselect_all( s );
930    }
931    else if (!strcmp (action_name, "edit-preferences"))
932    {
933        if( NULL == data->prefs )
934        {
935            data->prefs = tr_prefs_dialog_new( G_OBJECT(data->core), data->wind );
936            g_signal_connect( data->prefs, "destroy",
937                             G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
938            gtk_widget_show( GTK_WIDGET( data->prefs ) );
939        }
940    }
941    else if (!strcmp (action_name, "toggle-message-log"))
942    {
943        if( !data->msgwin )
944        {
945            GtkWidget * win = msgwin_create( data->core );
946            g_signal_connect( win, "destroy", G_CALLBACK( msgwinclosed ), 
947                             NULL );
948            data->msgwin = win;
949        }
950        else
951        {
952            action_toggle("toggle-message-log", FALSE);
953            gtk_widget_destroy( data->msgwin );
954            data->msgwin = NULL;
955        }
956    }
957    else if (!strcmp (action_name, "show-about-dialog"))
958    {
959        about();
960    }
961    else if (!strcmp (action_name, "toggle-main-window"))
962    {
963        GtkWidget * w = GTK_WIDGET (data->wind);
964        if (GTK_WIDGET_VISIBLE(w))
965            gtk_widget_hide (w);
966        else
967            gtk_window_present (GTK_WINDOW(w));
968    }
969    else g_error ("Unhandled action: %s", action_name );
970
971    if( changed )
972        updatemodel( data );
973}
974
975
976static void
977setupsighandlers(void) {
978  int sigs[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM};
979  struct sigaction sa;
980  int ii;
981
982  memset(&sa, 0,  sizeof(sa));
983  sa.sa_handler = fatalsig;
984  for(ii = 0; ii < ALEN(sigs); ii++)
985    sigaction(sigs[ii], &sa, NULL);
986}
987
988static void
989fatalsig(int sig) {
990  struct sigaction sa;
991
992  if(SIGCOUNT_MAX <= ++global_sigcount) {
993    memset(&sa, 0,  sizeof(sa));
994    sa.sa_handler = SIG_DFL;
995    sigaction(sig, &sa, NULL);
996    raise(sig);
997  }
998}
Note: See TracBrowser for help on using the repository browser.