source: trunk/gtk/main.c @ 4304

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

(gtk+) first cut of a filter bar

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