source: trunk/gtk/main.c @ 4861

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

(gtk) #661: Window position is not restored when unhiding from the systray

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