source: trunk/gtk/main.c @ 4903

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

(gtk) #611: don't hide in the systray without letting the user know

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