source: trunk/gtk/main.c @ 3153

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

accept some of the ideas in ticket #343 -- add logo and description in `about' dialog, and unbundle our lock icon because gtk+ has a stock authentication icon.

  • Property svn:keywords set to Date Rev Author Id
File size: 33.6 KB
Line 
1/******************************************************************************
2 * $Id: main.c 3153 2007-09-23 22:20:01Z 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 <getopt.h>
28#include <signal.h>
29#include <string.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <time.h>
33#include <unistd.h>
34
35#include <gtk/gtk.h>
36#include <glib/gi18n.h>
37#include <glib/gstdio.h>
38
39#include "actions.h"
40#include "conf.h"
41#include "dialogs.h"
42#include "ipc.h"
43#include "makemeta-ui.h"
44#include "msgwin.h"
45#include "torrent-inspector.h"
46#include "tr_cell_renderer_progress.h"
47#include "tr_core.h"
48#include "tr_icon.h"
49#include "tr_prefs.h"
50#include "tr_torrent.h"
51#include "tr_window.h"
52#include "util.h"
53#include "ui.h"
54
55#include <libtransmission/transmission.h>
56#include <libtransmission/version.h>
57
58/* time in seconds to wait for torrents to stop when exiting */
59#define TRACKER_EXIT_TIMEOUT    10
60
61/* interval in milliseconds to update the torrent list display */
62#define UPDATE_INTERVAL         1000
63
64/* interval in milliseconds to check for stopped torrents and update display */
65#define EXIT_CHECK_INTERVAL     500
66
67/* number of fatal signals required to cause an immediate exit */
68#define SIGCOUNT_MAX            3
69
70#if GTK_CHECK_VERSION(2,8,0)
71#define SHOW_LICENSE
72static const char * LICENSE = 
73"The Transmission binaries and most of its source code is distributed "
74"license. "
75"\n\n"
76"Some files are copyrighted by Charles Kerr and are covered by "
77"the GPL version 2.  Works owned by the Transmission project "
78"are granted a special exemption to clause 2(b) so that the bulk "
79"of its code can remain under the MIT license.  This exemption does "
80"not extend to original or derived works not owned by the "
81"Transmission project. "
82"\n\n"
83"Permission is hereby granted, free of charge, to any person obtaining "
84"a copy of this software and associated documentation files (the "
85"'Software'), to deal in the Software without restriction, including "
86"without limitation the rights to use, copy, modify, merge, publish, "
87"distribute, sublicense, and/or sell copies of the Software, and to "
88"permit persons to whom the Software is furnished to do so, subject to "
89"the following conditions: "
90"\n\n"
91"The above copyright notice and this permission notice shall be included "
92"in all copies or substantial portions of the Software. "
93"\n\n"
94"THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, "
95"EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF "
96"MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. "
97"IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY "
98"CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, "
99"TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE "
100"SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.";
101#endif
102
103struct cbdata {
104    GtkWindow    * wind;
105    TrCore       * core;
106    GtkWidget    * icon;
107    GtkWidget    * msgwin;
108    TrPrefs      * prefs;
109    guint          timer;
110    gboolean       closing;
111    GList        * errqueue;
112};
113
114struct exitdata {
115    struct cbdata * cbdata;
116    time_t          started;
117    guint           timer;
118};
119
120#define CBDATA_PTR              "callback-data-pointer"
121
122static GtkUIManager * myUIManager = NULL;
123
124static sig_atomic_t global_sigcount = 0;
125
126static GList *
127readargs( int argc, char ** argv, gboolean * sendquit, gboolean * paused );
128static gboolean
129sendremote( GList * files, gboolean sendquit );
130static void
131gtksetup( int * argc, char *** argv, struct cbdata* );
132static void
133appsetup( TrWindow * wind, GList * args,
134          struct cbdata * , gboolean paused );
135static void
136winsetup( struct cbdata * cbdata, TrWindow * wind );
137static void
138makeicon( struct cbdata * cbdata );
139static void
140wannaquit( void * vdata );
141static gboolean
142exitcheck(gpointer gdata);
143static void
144setupdrag(GtkWidget *widget, struct cbdata *data);
145static void
146gotdrag(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
147        GtkSelectionData *sel, guint info, guint time, gpointer gdata);
148
149static void
150coreerr( TrCore * core, enum tr_core_err code, const char * msg,
151         gpointer gdata );
152static void
153coreprompt( TrCore *, GList *, enum tr_torrent_action, gboolean, gpointer );
154static void
155corepromptdata( TrCore *, uint8_t *, size_t, gboolean, gpointer );
156static void
157readinitialprefs( struct cbdata * cbdata );
158static void
159prefschanged( TrCore * core, int id, gpointer data );
160static void
161setpex( tr_torrent * tor, void * arg );
162static gboolean
163updatemodel(gpointer gdata);
164static GList *
165getselection( struct cbdata * cbdata );
166
167static void
168setupsighandlers(void);
169static void
170fatalsig(int sig);
171
172static void
173accumulateStatusForeach (GtkTreeModel * model,
174                         GtkTreePath  * path UNUSED,
175                         GtkTreeIter  * iter,
176                         gpointer       accumulated_status)
177{
178    int status = 0;
179    gtk_tree_model_get( model, iter, MC_STAT, &status, -1 );
180    *(int*)accumulated_status |= status;
181}
182
183static void
184accumulateCanUpdateForeach (GtkTreeModel * model,
185                            GtkTreePath  * path UNUSED,
186                            GtkTreeIter  * iter,
187                            gpointer       accumulated_status)
188{
189    TrTorrent * gtor = NULL;
190    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
191    *(int*)accumulated_status |=
192        tr_torrentCanManualUpdate( tr_torrent_handle( gtor ) );
193    g_object_unref( G_OBJECT( gtor ) );
194}
195
196static void
197refreshTorrentActions( GtkTreeSelection * s )
198{
199    int status = 0;
200    gtk_tree_selection_selected_foreach( s, accumulateStatusForeach, &status );
201    action_sensitize( "stop-torrent", (status & TR_STATUS_ACTIVE) != 0);
202    action_sensitize( "start-torrent", (status & TR_STATUS_INACTIVE) != 0);
203    action_sensitize( "remove-torrent", status != 0);
204    action_sensitize( "recheck-torrent", status != 0);
205    action_sensitize( "show-torrent-inspector", status != 0);
206
207    status = 0;
208    gtk_tree_selection_selected_foreach( s, accumulateCanUpdateForeach, &status );
209    action_sensitize( "update-tracker", status != 0);
210}
211
212static void
213selectionChangedCB( GtkTreeSelection * s, gpointer unused UNUSED )
214{
215    refreshTorrentActions( s );
216}
217
218int
219main( int argc, char ** argv )
220{
221    struct cbdata * cbdata = g_new (struct cbdata, 1);
222    char       * err;
223    benc_val_t * state;
224    GList      * argfiles;
225    gboolean     didinit, didlock, sendquit, startpaused;
226
227    argfiles = readargs( argc, argv, &sendquit, &startpaused );
228    didinit = cf_init( tr_getPrefsDirectory(), NULL );
229    didlock = FALSE;
230    if( didinit )
231    {
232        /* maybe send remote commands, also try cf_lock() */
233        didlock = sendremote( argfiles, sendquit );
234    }
235    setupsighandlers();         /* set up handlers for fatal signals */
236    gtksetup( &argc, &argv, cbdata );   /* set up gtk and gettext */
237
238    if( ( didinit || cf_init( tr_getPrefsDirectory(), &err ) ) &&
239        ( didlock || cf_lock( &err ) ) )
240    {
241        GtkWindow  * mainwind;
242
243        /* create main window now to be a parent to any error dialogs */
244        mainwind = GTK_WINDOW( tr_window_new( myUIManager ) );
245
246        /* try to load prefs and saved state */
247        cf_loadprefs( &err );
248        if( NULL != err )
249        {
250            errmsg( mainwind, "%s", err );
251            g_free( err );
252        }
253        state = cf_loadstate( &err );
254        if( NULL != err )
255        {
256            errmsg( mainwind, "%s", err );
257            g_free( err );
258        }
259
260        msgwin_loadpref();      /* set message level here before tr_init() */
261        appsetup( mainwind, argfiles, cbdata, startpaused );
262        cf_freestate( state );
263    }
264    else
265    {
266        gtk_widget_show( errmsg_full( NULL, (callbackfunc_t)gtk_main_quit,
267                                      NULL, "%s", err ) );
268        g_free( err );
269    }
270
271    freestrlist(argfiles);
272
273    gtk_main();
274
275    return 0;
276}
277
278GList *
279readargs( int argc, char ** argv, gboolean * sendquit, gboolean * startpaused )
280{
281    struct option opts[] =
282    {
283        { "help",    no_argument, NULL, 'h' },
284        { "paused",  no_argument, NULL, 'p' },
285        { "quit",    no_argument, NULL, 'q' },
286        { "version", no_argument, NULL, 'v' },
287        { NULL, 0, NULL, 0 }
288    };
289    int          opt;
290    const char * name;
291
292    *sendquit    = FALSE;
293    *startpaused = FALSE;
294
295    gtk_parse_args( &argc, &argv );
296    name = g_get_prgname();
297
298    while( 0 <= ( opt = getopt_long( argc, argv, "hpqv", opts, NULL ) ) )
299    {
300        switch( opt )
301        {
302            case 'p':
303                *startpaused = TRUE;
304                break;
305            case 'q':
306                *sendquit = TRUE;
307                break;
308            case 'v':
309            case 'h':
310                printf(
311_("usage: %s [-hpq] [files...]\n"
312  "\n"
313  "Transmission %s http://transmission.m0k.org/\n"
314  "A free, lightweight BitTorrent client with a simple, intuitive interface\n"
315  "\n"
316  "  -h --help    display this message and exit\n"
317  "  -p --paused  start with all torrents paused\n"
318  "  -q --quit    request that the running %s instance quit\n"
319  "\n"
320  "Only one instance of %s may run at one time. Multiple\n"
321  "torrent files may be loaded at startup by adding them to the command\n"
322  "line. If %s is already running, those torrents will be\n"
323  "opened in the running instance.\n"),
324                        name, LONG_VERSION_STRING,
325                        name, name, name );
326                exit(0);
327                break;
328        }
329    }
330
331    argc -= optind;
332    argv += optind;
333
334    return checkfilenames( argc, argv );
335}
336
337static gboolean
338sendremote( GList * files, gboolean sendquit )
339{
340    gboolean didlock;
341
342    didlock = cf_lock( NULL );
343
344    if( NULL != files )
345    {
346        /* send files if there's another instance, otherwise start normally */
347        if( !didlock )
348        {
349            exit( ipc_sendfiles_blocking( files ) ? 0 : 1 );
350        }
351    }
352
353    if( sendquit )
354    {
355        /* either send a quit message or exit if no other instance */
356        if( !didlock )
357        {
358            exit( ipc_sendquit_blocking() ? 0 : 1 );
359        }
360        exit( 0 );
361    }
362
363    return didlock;
364}
365
366static void
367gtksetup( int * argc, char *** argv, struct cbdata * callback_data )
368{
369
370    bindtextdomain( "transmission-gtk", LOCALEDIR );
371    bind_textdomain_codeset( "transmission-gtk", "UTF-8" );
372    textdomain( "transmission-gtk" );
373
374    g_set_application_name( _("Transmission") );
375    gtk_init( argc, argv );
376
377    /* connect up the actions */
378    myUIManager = gtk_ui_manager_new ();
379    actions_init ( myUIManager, callback_data );
380    gtk_ui_manager_add_ui_from_string (myUIManager, fallback_ui_file, -1, NULL);
381    gtk_ui_manager_ensure_update (myUIManager);
382
383    /* tweak some style properties in dialogs to get closer to the GNOME HiG */
384    gtk_rc_parse_string(
385        "style \"transmission-standard\"\n"
386        "{\n"
387        "    GtkDialog::action-area-border  = 6\n"
388        "    GtkDialog::button-spacing      = 12\n"
389        "    GtkDialog::content-area-border = 6\n"
390        "}\n"
391        "widget \"TransmissionDialog\" style \"transmission-standard\"\n" );
392
393    gtk_window_set_default_icon_name ( "transmission-logo" );
394}
395
396static void
397appsetup( TrWindow * wind, GList * args,
398          struct cbdata * cbdata, gboolean paused )
399{
400    enum tr_torrent_action action;
401
402    /* fill out cbdata */
403    cbdata->wind       = NULL;
404    cbdata->core       = tr_core_new();
405    cbdata->icon       = NULL;
406    cbdata->msgwin     = NULL;
407    cbdata->prefs      = NULL;
408    cbdata->timer      = 0;
409    cbdata->closing    = FALSE;
410    cbdata->errqueue   = NULL;
411
412    /* set up core handlers */
413    g_signal_connect( cbdata->core, "error", G_CALLBACK( coreerr ), cbdata );
414    g_signal_connect( cbdata->core, "directory-prompt",
415                      G_CALLBACK( coreprompt ), cbdata );
416    g_signal_connect( cbdata->core, "directory-prompt-data",
417                      G_CALLBACK( corepromptdata ), cbdata );
418    g_signal_connect_swapped( cbdata->core, "quit",
419                              G_CALLBACK( wannaquit ), cbdata );
420    g_signal_connect( cbdata->core, "prefs-changed",
421                      G_CALLBACK( prefschanged ), cbdata );
422
423    /* apply a few prefs */
424    readinitialprefs( cbdata );
425
426    /* add torrents from command-line and saved state */
427    tr_core_load( cbdata->core, paused );
428
429    if( NULL != args )
430    {
431        action = toraddaction( tr_prefs_get( PREF_ID_ADDIPC ) );
432        tr_core_add_list( cbdata->core, args, action, paused );
433    }
434    tr_core_torrents_added( cbdata->core );
435
436    /* set up the ipc socket */
437    ipc_socket_setup( GTK_WINDOW( wind ), cbdata->core );
438
439    /* set up main window */
440    winsetup( cbdata, wind );
441
442    /* start model update timer */
443    cbdata->timer = g_timeout_add( UPDATE_INTERVAL, updatemodel, cbdata );
444    updatemodel( cbdata );
445
446    /* show the window */
447    gtk_widget_show( GTK_WIDGET(wind) );
448}
449
450static gboolean
451winclose( GtkWidget * widget UNUSED, GdkEvent * event UNUSED, gpointer gdata )
452{
453    struct cbdata * cbdata = (struct cbdata *) gdata;
454
455    if( cbdata->icon != NULL )
456        gtk_widget_hide( GTK_WIDGET( cbdata->wind ) );
457    else
458        askquit( cbdata->core, cbdata->wind, wannaquit, cbdata );
459
460    return TRUE; /* don't propagate event further */
461}
462
463static void
464rowChangedCB( GtkTreeModel  * model UNUSED,
465              GtkTreePath   * path UNUSED,
466              GtkTreeIter   * iter UNUSED,
467              gpointer        sel)
468{
469    refreshTorrentActions( GTK_TREE_SELECTION(sel) );
470}
471
472static void
473winsetup( struct cbdata * cbdata, TrWindow * wind )
474{
475    GtkTreeModel * model;
476    GtkTreeSelection * sel;
477
478    g_assert( NULL == cbdata->wind );
479    cbdata->wind = GTK_WINDOW( wind );
480
481    sel = tr_window_get_selection( cbdata->wind );
482    g_signal_connect( sel, "changed", G_CALLBACK(selectionChangedCB), NULL );
483    selectionChangedCB( sel, NULL );
484    model = tr_core_model( cbdata->core );
485    gtk_tree_view_set_model ( gtk_tree_selection_get_tree_view(sel), model );
486    g_signal_connect( model, "row-changed", G_CALLBACK(rowChangedCB), sel );
487    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
488   
489    setupdrag( GTK_WIDGET(wind), cbdata );
490}
491
492static void
493makeicon( struct cbdata * cbdata )
494{
495    if( NULL == cbdata->icon )
496        cbdata->icon = tr_icon_new( );
497}
498
499static void
500wannaquit( void * vdata )
501{
502  struct cbdata * data;
503  struct exitdata *edata;
504
505  data = vdata;
506  if( data->closing )
507  {
508      return;
509  }
510  data->closing = TRUE;
511
512  /* stop the update timer */
513  if(0 < data->timer)
514    g_source_remove(data->timer);
515  data->timer = 0;
516
517  /* pause torrents and stop nat traversal */
518  tr_core_shutdown( data->core );
519
520  /* set things up to wait for torrents to stop */
521  edata = g_new0(struct exitdata, 1);
522  edata->cbdata = data;
523  edata->started = time(NULL);
524  /* check if torrents are still running */
525  if(exitcheck(edata)) {
526    /* yes, start the exit timer and disable widgets */
527    edata->timer = g_timeout_add(EXIT_CHECK_INTERVAL, exitcheck, edata);
528    if( NULL != data->wind )
529    {
530        gtk_widget_set_sensitive( GTK_WIDGET( data->wind ), FALSE );
531    }
532  }
533}
534
535static gboolean
536exitcheck( gpointer gdata )
537{
538    struct exitdata    * edata;
539    struct cbdata      * cbdata;
540
541    edata  = gdata;
542    cbdata = edata->cbdata;
543
544    /* keep waiting until we're ready to quit or we hit the exit timeout */
545    if( time( NULL ) - edata->started < TRACKER_EXIT_TIMEOUT )
546    {
547        if( !tr_core_quiescent( cbdata->core ) )
548        {
549            updatemodel( cbdata );
550            return TRUE;
551        }
552    }
553
554    /* exit otherwise */
555    if( 0 < edata->timer )
556    {
557        g_source_remove( edata->timer );
558    }
559    g_free( edata );
560    /* note that cbdata->prefs holds a reference to cbdata->core, and
561       it's destruction may trigger callbacks that use cbdata->core */
562    if( NULL != cbdata->prefs )
563    {
564        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
565    }
566    if( NULL != cbdata->wind )
567    {
568        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
569    }
570    g_object_unref( cbdata->core );
571    if( NULL != cbdata->icon )
572    {
573        g_object_unref( cbdata->icon );
574    }
575    g_assert( 0 == cbdata->timer );
576    if( NULL != cbdata->errqueue )
577    {
578        g_list_foreach( cbdata->errqueue, (GFunc) g_free, NULL );
579        g_list_free( cbdata->errqueue );
580    }
581    g_free( cbdata );
582    gtk_main_quit();
583
584    return FALSE;
585}
586
587static void
588gotdrag(GtkWidget *widget SHUTUP, GdkDragContext *dc, gint x SHUTUP,
589        gint y SHUTUP, GtkSelectionData *sel, guint info SHUTUP, guint time,
590        gpointer gdata) {
591  struct cbdata *data = gdata;
592  char prefix[] = "file:";
593  char *files, *decoded, *deslashed, *hostless;
594  int ii, len;
595  GList *errs;
596  struct stat sb;
597  int prelen = strlen(prefix);
598  GList *paths, *freeables;
599  enum tr_torrent_action action;
600
601#ifdef DND_DEBUG
602  char *sele = gdk_atom_name(sel->selection);
603  char *targ = gdk_atom_name(sel->target);
604  char *type = gdk_atom_name(sel->type);
605
606  fprintf(stderr, "dropped file: sel=%s targ=%s type=%s fmt=%i len=%i\n",
607          sele, targ, type, sel->format, sel->length);
608  g_free(sele);
609  g_free(targ);
610  g_free(type);
611  if(8 == sel->format) {
612    for(ii = 0; ii < sel->length; ii++)
613      fprintf(stderr, "%02X ", sel->data[ii]);
614    fprintf(stderr, "\n");
615  }
616#endif
617
618  errs = NULL;
619  paths = NULL;
620  freeables = NULL;
621  if(gdk_atom_intern("XdndSelection", FALSE) == sel->selection &&
622     8 == sel->format) {
623    /* split file list on carriage returns and linefeeds */
624    files = g_new(char, sel->length + 1);
625    memcpy(files, sel->data, sel->length);
626    files[sel->length] = '\0';
627    for(ii = 0; '\0' != files[ii]; ii++)
628      if('\015' == files[ii] || '\012' == files[ii])
629        files[ii] = '\0';
630
631    /* try to get a usable filename out of the URI supplied and add it */
632    for(ii = 0; ii < sel->length; ii += len + 1) {
633      if('\0' == files[ii])
634        len = 0;
635      else {
636        len = strlen(files + ii);
637        /* de-urlencode the URI */
638        decoded = urldecode(files + ii, len);
639        freeables = g_list_append(freeables, decoded);
640        if(g_utf8_validate(decoded, -1, NULL)) {
641          /* remove the file: prefix */
642          if(prelen < len && 0 == strncmp(prefix, decoded, prelen)) {
643            deslashed = decoded + prelen;
644            /* trim excess / characters from the beginning */
645            while('/' == deslashed[0] && '/' == deslashed[1])
646              deslashed++;
647            /* if the file doesn't exist, the first part might be a hostname */
648            if(0 > g_stat(deslashed, &sb) &&
649               NULL != (hostless = strchr(deslashed + 1, '/')) &&
650               0 == g_stat(hostless, &sb))
651              deslashed = hostless;
652            /* finally, add it to the list of torrents to try adding */
653            paths = g_list_append(paths, deslashed);
654          }
655        }
656      }
657    }
658
659    /* try to add any torrents we found */
660    if( NULL != paths )
661    {
662        action = toraddaction( tr_prefs_get( PREF_ID_ADDSTD ) );
663        tr_core_add_list( data->core, paths, action, FALSE );
664        tr_core_torrents_added( data->core );
665        g_list_free(paths);
666    }
667    freestrlist(freeables);
668    g_free(files);
669  }
670
671  gtk_drag_finish(dc, (NULL != paths), FALSE, time);
672}
673
674static void
675setupdrag(GtkWidget *widget, struct cbdata *data) {
676  GtkTargetEntry targets[] = {
677    { "STRING",     0, 0 },
678    { "text/plain", 0, 0 },
679    { "text/uri-list", 0, 0 },
680  };
681
682  g_signal_connect(widget, "drag_data_received", G_CALLBACK(gotdrag), data);
683
684  gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, targets,
685                    ALEN(targets), GDK_ACTION_COPY | GDK_ACTION_MOVE);
686}
687
688static void
689coreerr( TrCore * core SHUTUP, enum tr_core_err code, const char * msg,
690         gpointer gdata )
691{
692    struct cbdata * cbdata = gdata;
693    char          * joined;
694
695    switch( code )
696    {
697        case TR_CORE_ERR_ADD_TORRENT:
698            cbdata->errqueue = g_list_append( cbdata->errqueue,
699                                              g_strdup( msg ) );
700            return;
701        case TR_CORE_ERR_NO_MORE_TORRENTS:
702            if( NULL != cbdata->errqueue )
703            {
704                joined = joinstrlist( cbdata->errqueue, "\n" );
705                errmsg( cbdata->wind,
706                        ngettext( "Failed to load torrent file:\n%s",
707                                  "Failed to load torrent files:\n%s",
708                                  g_list_length( cbdata->errqueue ) ),
709                        joined );
710                g_list_foreach( cbdata->errqueue, (GFunc) g_free, NULL );
711                g_list_free( cbdata->errqueue );
712                cbdata->errqueue = NULL;
713                g_free( joined );
714            }
715            return;
716        case TR_CORE_ERR_SAVE_STATE:
717            errmsg( cbdata->wind, "%s", msg );
718            return;
719    }
720
721    g_assert_not_reached();
722}
723
724void
725coreprompt( TrCore * core, GList * paths, enum tr_torrent_action act,
726            gboolean paused, gpointer gdata )
727{
728    struct cbdata * cbdata = gdata;
729
730    promptfordir( cbdata->wind, core, paths, NULL, 0, act, paused );
731}
732
733void
734corepromptdata( TrCore * core, uint8_t * data, size_t size,
735                gboolean paused, gpointer gdata )
736{
737    struct cbdata * cbdata = gdata;
738
739    promptfordir( cbdata->wind, core, NULL, data, size, TR_TOR_LEAVE, paused );
740}
741
742static void
743readinitialprefs( struct cbdata * cbdata )
744{
745    int prefs[] =
746    {
747        PREF_ID_PORT,
748        PREF_ID_DOWNLIMIT,
749        PREF_ID_USEDOWNLIMIT,
750        PREF_ID_UPLIMIT,
751        PREF_ID_USEUPLIMIT,
752        PREF_ID_NAT,
753        PREF_ID_ICON,
754        PREF_ID_PEX,
755    };
756    int ii;
757
758    for( ii = 0; ALEN( prefs ) > ii; ii++ )
759    {
760        prefschanged( NULL, prefs[ii], cbdata );
761    }
762}
763
764static void
765prefschanged( TrCore * core SHUTUP, int id, gpointer data )
766{
767    struct cbdata * cbdata = data;
768    tr_handle     * tr     = tr_core_handle( cbdata->core );
769    gboolean        boolval;
770
771    switch( id )
772    {
773        case PREF_ID_PORT:
774            tr_setBindPort( tr, tr_prefs_get_int_with_default( id ) );
775            break;
776
777        case PREF_ID_USEDOWNLIMIT:
778            tr_setUseGlobalSpeedLimit( tr, TR_DOWN,
779                tr_prefs_get_bool_with_default( PREF_ID_USEDOWNLIMIT ) );
780            break;
781
782        case PREF_ID_DOWNLIMIT:
783            tr_setGlobalSpeedLimit( tr, TR_DOWN,
784                tr_prefs_get_int_with_default( PREF_ID_DOWNLIMIT ) );
785            break;
786
787        case PREF_ID_USEUPLIMIT:
788            tr_setUseGlobalSpeedLimit( tr, TR_UP,
789                tr_prefs_get_bool_with_default( PREF_ID_USEUPLIMIT ) );
790            break;
791
792        case PREF_ID_UPLIMIT:
793            tr_setGlobalSpeedLimit( tr, TR_UP,
794                tr_prefs_get_int_with_default( PREF_ID_UPLIMIT ) );
795            break;
796
797        case PREF_ID_NAT:
798            tr_natTraversalEnable( tr, tr_prefs_get_bool_with_default( id ) );
799            break;
800
801        case PREF_ID_ICON:
802            if( tr_prefs_get_bool_with_default( id ) )
803            {
804                makeicon( cbdata );
805            }
806            else if( NULL != cbdata->icon )
807            {
808g_message ("foo");
809                g_object_unref( cbdata->icon );
810                cbdata->icon = NULL;
811            }
812            break;
813
814        case PREF_ID_PEX:
815            boolval = tr_prefs_get_bool_with_default( id );
816            tr_torrentIterate( tr, setpex, &boolval );
817            break;
818
819        case PREF_ID_DIR:
820        case PREF_ID_ASKDIR:
821        case PREF_ID_ADDSTD:
822        case PREF_ID_ADDIPC:
823        case PREF_ID_MSGLEVEL:
824        case PREF_MAX_ID:
825            break;
826    }
827}
828
829void
830setpex( tr_torrent * tor, void * arg )
831{
832    gboolean * val;
833
834    val = arg;
835    tr_torrentDisablePex( tor, !(*val) );
836}
837
838gboolean
839updatemodel(gpointer gdata) {
840  struct cbdata *data = gdata;
841  float up, down;
842
843  if( !data->closing && 0 < global_sigcount )
844  {
845      wannaquit( data );
846      return FALSE;
847  }
848
849  /* update the torrent data in the model */
850  tr_core_update( data->core );
851
852  /* update the main window's statusbar and toolbar buttons */
853  if( NULL != data->wind )
854  {
855      tr_torrentRates( tr_core_handle( data->core ), &down, &up );
856      tr_window_update( data->wind, down, up );
857  }
858
859  /* update the message window */
860  msgwin_update();
861
862  return TRUE;
863}
864
865/* returns a GList containing a GtkTreeRowReference to each selected row */
866static GList *
867getselection( struct cbdata * cbdata )
868{
869    GList * rows = NULL;
870
871    if( NULL != cbdata->wind )
872    {
873        GList * ii;
874        GtkTreeSelection *s = tr_window_get_selection(cbdata->wind);
875        GtkTreeModel * model = tr_core_model( cbdata->core );
876        rows = gtk_tree_selection_get_selected_rows( s, NULL );
877        for( ii = rows; NULL != ii; ii = ii->next )
878        {
879            GtkTreeRowReference * ref = gtk_tree_row_reference_new(
880                model, ii->data );
881            gtk_tree_path_free( ii->data );
882            ii->data = ref;
883        }
884    }
885
886    return rows;
887}
888
889static void
890about ( void )
891{
892  GtkWidget * w = gtk_about_dialog_new ();
893  GtkAboutDialog * a = GTK_ABOUT_DIALOG (w);
894  const char *authors[] = { "Eric Petit (Back-end; OS X)",
895                            "Josh Elsasser (Back-end; GTK+)",
896                            "Mitchell Livingston (Back-end; OS X)",
897                            "Charles Kerr (Back-end; GTK+)",
898                            "Bryan Varner (BeOS)", 
899                            NULL };
900  gtk_about_dialog_set_version (a, LONG_VERSION_STRING );
901#ifdef SHOW_LICENSE
902  gtk_about_dialog_set_license (a, LICENSE);
903  gtk_about_dialog_set_wrap_license (a, TRUE);
904#endif
905  gtk_about_dialog_set_logo_icon_name( a, "transmission-logo" );
906  gtk_about_dialog_set_comments( a, _("A GTK+ BitTorrent Client.") );
907  gtk_about_dialog_set_website( a, "http://transmission.m0k.org/" );
908  gtk_about_dialog_set_copyright( a, _("Copyright 2005-2007 The Transmission Project") );
909  gtk_about_dialog_set_authors( a, authors );
910  gtk_about_dialog_set_translator_credits( a, _("translator-credits") );
911  g_signal_connect_swapped( w, "response", G_CALLBACK (gtk_widget_destroy), w );
912  gtk_widget_show_all( w );
913}
914
915static void
916startTorrentForeach (GtkTreeModel * model,
917                     GtkTreePath  * path UNUSED,
918                     GtkTreeIter  * iter,
919                     gpointer       data UNUSED)
920{
921    TrTorrent * tor = NULL;
922    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
923    tr_torrent_start( tor );
924    g_object_unref( G_OBJECT( tor ) );
925}
926
927static void
928stopTorrentForeach (GtkTreeModel * model,
929                    GtkTreePath  * path UNUSED,
930                    GtkTreeIter  * iter,
931                    gpointer       data UNUSED)
932{
933    TrTorrent * tor = NULL;
934    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
935    tr_torrent_stop( tor );
936    g_object_unref( G_OBJECT( tor ) );
937}
938
939static void
940updateTrackerForeach (GtkTreeModel * model,
941                      GtkTreePath  * path UNUSED,
942                      GtkTreeIter  * iter,
943                      gpointer       data UNUSED)
944{
945    TrTorrent * tor = NULL;
946    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
947    tr_manualUpdate( tr_torrent_handle( tor ) );
948    g_object_unref( G_OBJECT( tor ) );
949}
950
951static void
952showInfoForeach (GtkTreeModel * model,
953                 GtkTreePath  * path UNUSED,
954                 GtkTreeIter  * iter,
955                 gpointer       data UNUSED)
956{
957    TrTorrent * tor = NULL;
958    GtkWidget * w;
959    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
960    w = torrent_inspector_new( GTK_WINDOW(data), tor );
961    gtk_widget_show( w );
962    g_object_unref( G_OBJECT( tor ) );
963}
964
965static void
966recheckTorrentForeach (GtkTreeModel * model,
967                       GtkTreePath  * path UNUSED,
968                       GtkTreeIter  * iter,
969                       gpointer       data UNUSED)
970{
971    TrTorrent * gtor = NULL;
972    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
973    tr_torrentRecheck( tr_torrent_handle( gtor ) );
974    g_object_unref( G_OBJECT( gtor ) );
975}
976
977static gboolean
978msgwinclosed()
979{
980  action_toggle( "toggle-debug-window", FALSE );
981  return FALSE;
982}
983
984void
985doAction ( const char * action_name, gpointer user_data )
986{
987    struct cbdata * data = (struct cbdata *) user_data;
988    gboolean changed = FALSE;
989
990    if (!strcmp (action_name, "add-torrent"))
991    {
992        makeaddwind( data->wind, data->core );
993    }
994    else if (!strcmp (action_name, "start-torrent"))
995    {
996        GtkTreeSelection * s = tr_window_get_selection(data->wind);
997        gtk_tree_selection_selected_foreach( s, startTorrentForeach, NULL );
998        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
999    }
1000    else if (!strcmp (action_name, "stop-torrent"))
1001    {
1002        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1003        gtk_tree_selection_selected_foreach( s, stopTorrentForeach, NULL );
1004        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1005    }
1006    else if (!strcmp (action_name, "recheck-torrent"))
1007    {
1008        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1009        gtk_tree_selection_selected_foreach( s, recheckTorrentForeach, NULL );
1010        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1011    }
1012    else if (!strcmp (action_name, "show-torrent-inspector"))
1013    {
1014        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1015        gtk_tree_selection_selected_foreach( s, showInfoForeach, data->wind );
1016    }
1017    else if (!strcmp( action_name, "update-tracker"))
1018    {
1019        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1020        gtk_tree_selection_selected_foreach( s, updateTrackerForeach, data->wind );
1021    }
1022    else if (!strcmp (action_name, "create-torrent"))
1023    {
1024        GtkWidget * w = make_meta_ui( GTK_WINDOW( data->wind ), tr_core_handle( data->core ) );
1025        gtk_widget_show_all( w );
1026    }
1027    else if (!strcmp (action_name, "remove-torrent"))
1028    {
1029        /* this modifies the model's contents, so can't use foreach */
1030        GList *l, *sel = getselection( data );
1031        GtkTreeModel *model = tr_core_model( data->core );
1032        for( l=sel; l!=NULL; l=l->next )
1033        {
1034            GtkTreeIter iter;
1035            GtkTreeRowReference * reference = (GtkTreeRowReference *) l->data;
1036            GtkTreePath * path = gtk_tree_row_reference_get_path( reference );
1037            gtk_tree_model_get_iter( model, &iter, path );
1038            tr_core_delete_torrent( data->core, &iter );
1039            gtk_tree_row_reference_free( reference );
1040            changed = TRUE;
1041        }
1042        g_list_free( sel );
1043    }
1044    else if (!strcmp (action_name, "close"))
1045    {
1046        if( data->wind != NULL )
1047            winclose( NULL, NULL, data );
1048    }
1049    else if (!strcmp (action_name, "quit"))
1050    {
1051        askquit( data->core, data->wind, wannaquit, data );
1052    }
1053    else if (!strcmp (action_name, "select-all"))
1054    {
1055        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1056        gtk_tree_selection_select_all( s );
1057    }
1058    else if (!strcmp (action_name, "unselect-all"))
1059    {
1060        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1061        gtk_tree_selection_unselect_all( s );
1062    }
1063    else if (!strcmp (action_name, "edit-preferences"))
1064    {
1065        if( NULL == data->prefs )
1066        {
1067            data->prefs = tr_prefs_new_with_parent( G_OBJECT( data->core ),
1068                                                    data->wind );
1069            g_signal_connect( data->prefs, "destroy",
1070                             G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
1071            gtk_widget_show( GTK_WIDGET( data->prefs ) );
1072        }
1073    }
1074    else if (!strcmp (action_name, "toggle-debug-window"))
1075    {
1076        if( !data->msgwin )
1077        {
1078            GtkWidget * win = msgwin_create( data->core );
1079            g_signal_connect( win, "destroy", G_CALLBACK( msgwinclosed ), 
1080                             NULL );
1081            data->msgwin = win;
1082        }
1083        else
1084        {
1085            action_toggle("toggle-debug-window", FALSE);
1086            gtk_widget_destroy( data->msgwin );
1087            data->msgwin = NULL;
1088        }
1089    }
1090    else if (!strcmp (action_name, "show-about-dialog"))
1091    {
1092        about();
1093    }
1094    else if (!strcmp (action_name, "toggle-main-window"))
1095    {
1096        GtkWidget * w = GTK_WIDGET (data->wind);
1097        if (GTK_WIDGET_VISIBLE(w))
1098            gtk_widget_hide (w);
1099        else
1100            gtk_window_present (GTK_WINDOW(w));
1101    }
1102    else g_error ("Unhandled action: %s", action_name );
1103
1104    if(changed)
1105    {
1106        updatemodel( data );
1107        tr_core_save( data->core );
1108    }
1109}
1110
1111
1112static void
1113setupsighandlers(void) {
1114  int sigs[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM};
1115  struct sigaction sa;
1116  int ii;
1117
1118  memset(&sa, 0,  sizeof(sa));
1119  sa.sa_handler = fatalsig;
1120  for(ii = 0; ii < ALEN(sigs); ii++)
1121    sigaction(sigs[ii], &sa, NULL);
1122}
1123
1124static void
1125fatalsig(int sig) {
1126  struct sigaction sa;
1127
1128  if(SIGCOUNT_MAX <= ++global_sigcount) {
1129    memset(&sa, 0,  sizeof(sa));
1130    sa.sa_handler = SIG_DFL;
1131    sigaction(sig, &sa, NULL);
1132    raise(sig);
1133  }
1134}
Note: See TracBrowser for help on using the repository browser.