source: trunk/gtk/main.c @ 1647

Last change on this file since 1647 was 1647, checked in by joshe, 15 years ago

Make evil initial window sizing magic a bit less evil and a bit less magic.

  • Property svn:keywords set to Date Rev Author Id
File size: 31.9 KB
Line 
1/******************************************************************************
2 * $Id: main.c 1647 2007-04-03 08:18:53Z joshe $
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 "conf.h"
40#include "dialogs.h"
41#include "ipc.h"
42#include "msgwin.h"
43#include "tr_backend.h"
44#include "tr_cell_renderer_progress.h"
45#include "tr_icon.h"
46#include "tr_prefs.h"
47#include "tr_torrent.h"
48#include "tr_window.h"
49#include "util.h"
50
51#include "transmission.h"
52
53#include "img_icon_full.h"
54
55/* time in seconds to wait for torrents to stop when exiting */
56#define TRACKER_EXIT_TIMEOUT    10
57
58/* interval in milliseconds to update the torrent list display */
59#define UPDATE_INTERVAL         1000
60
61/* interval in milliseconds to check for stopped torrents and update display */
62#define EXIT_CHECK_INTERVAL     500
63
64/* number of fatal signals required to cause an immediate exit */
65#define SIGCOUNT_MAX            3
66
67struct cbdata {
68    TrBackend    * back;
69    GtkWindow    * wind;
70    GtkTreeModel * model;
71    TrIcon       * icon;
72    TrPrefs      * prefs;
73    guint          timer;
74    gboolean       msgwinopen;
75    gboolean       closing;
76};
77
78struct exitdata {
79    struct cbdata * cbdata;
80    time_t          started;
81    guint           timer;
82};
83
84enum
85{
86    ACT_OPEN = 0,
87    ACT_START,
88    ACT_STOP,
89    ACT_DELETE,
90    ACT_SEPARATOR1,
91    ACT_INFO,
92    ACT_FILES,
93    ACT_DEBUG,
94    ACT_SEPARATOR2,
95    ACT_PREF,
96    ACT_SEPARATOR3,
97    ACT_CLOSE,
98    ACT_QUIT,
99    ACT_ICON,
100    ACTION_COUNT,
101};
102
103struct
104{
105    const char * label;
106    const char * icon;
107    guint        key;
108    int          flags;
109    const char * tooltip;
110}
111actions[] =
112{
113    { NULL,        GTK_STOCK_ADD,       'o', ACTF_WHEREVER | ACTF_ALWAYS,
114      N_("Add a new torrent") },
115    { N_("Start"), GTK_STOCK_EXECUTE,     0, ACTF_WHEREVER | ACTF_INACTIVE,
116      N_("Start a torrent that is not running") },
117    { NULL,        GTK_STOCK_STOP,        0, ACTF_WHEREVER | ACTF_ACTIVE,
118      N_("Stop a torrent that is running") },
119    { NULL,        GTK_STOCK_REMOVE,      0, ACTF_WHEREVER | ACTF_WHATEVER,
120      N_("Remove a torrent") },
121    { NULL,        NULL,                  0, ACTF_SEPARATOR, NULL },
122    { NULL,        GTK_STOCK_PROPERTIES,  0, ACTF_WHEREVER | ACTF_WHATEVER,
123      N_("Show additional information about a torrent") },
124    { N_("Files"), NULL,                  0, ACTF_MENU     | ACTF_WHATEVER,
125      N_("Show a list of the files in a torrent"), },
126    { N_("Open debug window"), NULL,      0, ACTF_MENU     | ACTF_ALWAYS,
127      NULL },
128    { NULL,        NULL,                  0, ACTF_SEPARATOR, NULL },
129    { NULL,        GTK_STOCK_PREFERENCES, 0, ACTF_WHEREVER | ACTF_ALWAYS,
130      N_("Customize application behavior") },
131    { NULL,        NULL,                  0, ACTF_SEPARATOR, NULL },
132    { NULL,        GTK_STOCK_CLOSE,       0, ACTF_MENU     | ACTF_ALWAYS,
133      N_("Close the main window") },
134    { NULL,        GTK_STOCK_QUIT,        0, ACTF_MENU     | ACTF_ALWAYS,
135      N_("Exit the program") },
136    /* this isn't a terminator for the list, it's ACT_ICON */
137    { NULL,        NULL,                  0, 0, NULL },
138};
139
140#define CBDATA_PTR              "callback-data-pointer"
141
142static sig_atomic_t global_sigcount = 0;
143
144static GList *
145readargs( int argc, char ** argv, gboolean * sendquit, gboolean * paused );
146static gboolean
147sendremote( GList * files, gboolean sendquit );
148static void
149gtksetup( int * argc, char *** argv );
150static void
151appsetup( TrWindow * wind, benc_val_t * state, GList * args, gboolean paused );
152static void
153winsetup( struct cbdata * cbdata, TrWindow * wind );
154static void
155remakewind( struct cbdata * cbdata );
156static void
157makeicon( struct cbdata * cbdata );
158static gboolean
159winclose( GtkWidget * widget, GdkEvent * event, gpointer gdata );
160static void
161wannaquit( void * vdata );
162static gboolean
163exitcheck(gpointer gdata);
164static void
165setupdrag(GtkWidget *widget, struct cbdata *data);
166static void
167gotdrag(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
168        GtkSelectionData *sel, guint info, guint time, gpointer gdata);
169
170static void
171readinitialprefs( struct cbdata * cbdata );
172static void
173prefschanged( GtkWidget * widget, int id, gpointer data );
174static void
175setpex( tr_torrent_t * tor, void * arg );
176static gboolean
177updatemodel(gpointer gdata);
178static void
179boolwindclosed(GtkWidget *widget, gpointer gdata);
180static void
181windact(GtkWidget *widget, int action, gpointer gdata);
182static GList *
183getselection( struct cbdata * cbdata );
184static void
185handleaction( struct cbdata *data, int action );
186
187static void
188addtorrents(void *vdata, void *state, GList *files,
189            const char *dir, guint flags);
190static void
191savetorrents(struct cbdata *data);
192static void
193safepipe(void);
194static void
195setupsighandlers(void);
196static void
197fatalsig(int sig);
198
199int
200main( int argc, char ** argv )
201{
202    GtkWindow  * mainwind;
203    char       * err;
204    benc_val_t * state;
205    GList      * argfiles;
206    gboolean     didinit, didlock, sendquit, startpaused;
207
208    safepipe();                 /* ignore SIGPIPE */
209    argfiles = readargs( argc, argv, &sendquit, &startpaused );
210    didinit = cf_init( tr_getPrefsDirectory(), NULL );
211    didlock = FALSE;
212    if( didinit )
213    {
214        /* maybe send remote commands, also try cf_lock() */
215        didlock = sendremote( argfiles, sendquit );
216    }
217    setupsighandlers();         /* set up handlers for fatal signals */
218    gtksetup( &argc, &argv );   /* set up gtk and gettext */
219
220    if( ( didinit || cf_init( tr_getPrefsDirectory(), &err ) ) &&
221        ( didlock || cf_lock( &err ) ) )
222    {
223        /* create main window now to be a parent to any error dialogs */
224        mainwind = GTK_WINDOW( tr_window_new() );
225
226        /* try to load prefs and saved state */
227        cf_loadprefs( &err );
228        if( NULL != err )
229        {
230            errmsg( mainwind, "%s", err );
231            g_free( err );
232        }
233        state = cf_loadstate( &err );
234        if( NULL != err )
235        {
236            errmsg( mainwind, "%s", err );
237            g_free( err );
238        }
239
240        msgwin_loadpref();      /* set message level here before tr_init() */
241        appsetup( TR_WINDOW( mainwind ), state, argfiles, startpaused );
242        cf_freestate( state );
243    }
244    else
245    {
246        gtk_widget_show( errmsg_full( NULL, (callbackfunc_t)gtk_main_quit,
247                                      NULL, "%s", err ) );
248        g_free( err );
249    }
250
251    freestrlist(argfiles);
252
253    gtk_main();
254
255    return 0;
256}
257
258GList *
259readargs( int argc, char ** argv, gboolean * sendquit, gboolean * startpaused )
260{
261    struct option opts[] =
262    {
263        { "help",    no_argument, NULL, 'h' },
264        { "paused",  no_argument, NULL, 'p' },
265        { "quit",    no_argument, NULL, 'q' },
266        { "version", no_argument, NULL, 'v' },
267        { NULL, 0, NULL, 0 }
268    };
269    int          opt;
270    const char * name;
271
272    *sendquit    = FALSE;
273    *startpaused = FALSE;
274
275    gtk_parse_args( &argc, &argv );
276    name = g_get_prgname();
277
278    while( 0 <= ( opt = getopt_long( argc, argv, "hpqv", opts, NULL ) ) )
279    {
280        switch( opt )
281        {
282            case 'p':
283                *startpaused = TRUE;
284                break;
285            case 'q':
286                *sendquit = TRUE;
287                break;
288            case 'v':
289            case 'h':
290                printf(
291_("usage: %s [-hpq] [files...]\n"
292  "\n"
293  "Transmission %s (r%d) http://transmission.m0k.org/\n"
294  "A free, lightweight BitTorrent client with a simple, intuitive interface\n"
295  "\n"
296  "  -h --help    display this message and exit\n"
297  "  -p --paused  start with all torrents paused\n"
298  "  -q --quit    request that the running %s instance quit\n"
299  "\n"
300  "Only one instance of %s may run at one time. Multiple\n"
301  "torrent files may be loaded at startup by adding them to the command\n"
302  "line. If %s is already running, those torrents will be\n"
303  "opened in the running instance.\n"),
304                        name, VERSION_STRING, VERSION_REVISION,
305                        name, name, name );
306                exit(0);
307                break;
308        }
309    }
310
311    argc -= optind;
312    argv += optind;
313
314    return checkfilenames( argc, argv );
315}
316
317static gboolean
318sendremote( GList * files, gboolean sendquit )
319{
320    gboolean didlock;
321
322    didlock = cf_lock( NULL );
323
324    if( NULL != files )
325    {
326        /* send files if there's another instance, otherwise start normally */
327        if( !didlock )
328        {
329            exit( ipc_sendfiles_blocking( files ) ? 0 : 1 );
330        }
331    }
332
333    if( sendquit )
334    {
335        /* either send a quit message or exit if no other instance */
336        if( !didlock )
337        {
338            exit( ipc_sendquit_blocking() ? 0 : 1 );
339        }
340        exit( 0 );
341    }
342
343    return didlock;
344}
345
346static void
347gtksetup( int * argc, char *** argv )
348{
349    GdkPixbuf * icon;
350
351    gtk_init( argc, argv );
352
353    bindtextdomain( "transmission-gtk", LOCALEDIR );
354    bind_textdomain_codeset( "transmission-gtk", "UTF-8" );
355    textdomain( "transmission-gtk" );
356
357    g_set_application_name( _("Transmission") );
358
359    /* tweak some style properties in dialogs to get closer to the GNOME HiG */
360    gtk_rc_parse_string(
361        "style \"transmission-standard\"\n"
362        "{\n"
363        "    GtkDialog::action-area-border  = 6\n"
364        "    GtkDialog::button-spacing      = 12\n"
365        "    GtkDialog::content-area-border = 6\n"
366        "}\n"
367        "widget \"TransmissionDialog\" style \"transmission-standard\"\n" );
368
369    icon = gdk_pixbuf_new_from_inline( -1, tr_icon_full, FALSE, NULL );
370    gtk_window_set_default_icon( icon );
371    g_object_unref( icon );
372}
373
374static void
375appsetup( TrWindow * wind, benc_val_t * state, GList * args, gboolean paused )
376{
377    GType types[] =
378    {
379        /* info->name, info->totalSize, status,     error,      errorString, */
380        G_TYPE_STRING, G_TYPE_UINT64,   G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING,
381        /* progress,  rateDownload, rateUpload,   eta,        peersTotal, */
382        G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT,
383        /* peersUploading, peersDownloading, downloaded,    uploaded */
384        G_TYPE_INT,        G_TYPE_INT,       G_TYPE_UINT64, G_TYPE_UINT64,
385        /* the TrTorrent object */
386        TR_TORRENT_TYPE,
387    };
388    struct cbdata * cbdata;
389    GtkListStore  * store;
390    guint           flags;
391
392    /* create the model used to store torrent data */
393    g_assert( ALEN( types ) == MC_ROW_COUNT );
394    store = gtk_list_store_newv( MC_ROW_COUNT, types );
395
396    /* fill out cbdata */
397    cbdata = g_new0( struct cbdata, 1 );
398    cbdata->back       = tr_backend_new();
399    cbdata->wind       = NULL;
400    cbdata->model      = GTK_TREE_MODEL(store);
401    cbdata->icon       = NULL;
402    cbdata->prefs      = NULL;
403    cbdata->timer      = 0;
404    cbdata->msgwinopen = FALSE;
405    cbdata->closing    = FALSE;
406
407    /* apply a few prefs */
408    readinitialprefs( cbdata );
409
410    /* set up main window */
411    winsetup( cbdata, wind );
412
413    /* add torrents from command-line and saved state */
414    flags = addactionflag( tr_prefs_get( PREF_ID_ADDIPC ) );
415    g_assert( !( flags & ( TR_TORNEW_PAUSED | TR_TORNEW_RUNNING ) ) );
416    if( paused )
417    {
418        flags |= TR_TORNEW_PAUSED;
419    }
420    addtorrents( cbdata, state, args, NULL, flags );
421
422    /* start model update timer */
423    cbdata->timer = g_timeout_add( UPDATE_INTERVAL, updatemodel, cbdata );
424    updatemodel( cbdata );
425
426    /* set up the ipc socket now that we're ready to get torrents from it */
427    ipc_socket_setup( GTK_WINDOW( wind ), addtorrents, wannaquit, cbdata );
428
429    /* show the window */
430    tr_window_show( wind );
431}
432
433static void
434winsetup( struct cbdata * cbdata, TrWindow * wind )
435{
436    int ii;
437    GtkWidget  * drag;
438
439    g_assert( ACTION_COUNT == ALEN( actions ) );
440    g_assert( NULL == cbdata->wind );
441    cbdata->wind = GTK_WINDOW( wind );
442    for( ii = 0; ii < ALEN( actions ); ii++ )
443    {
444        tr_window_action_add( wind, ii, actions[ii].flags,
445                              gettext( actions[ii].label ), actions[ii].icon,
446                              gettext( actions[ii].tooltip ),
447                              actions[ii].key );
448    }
449    g_object_set( wind, "model", cbdata->model,
450                        "double-click-action", ACT_INFO, NULL);
451
452    g_signal_connect( wind, "action",       G_CALLBACK( windact  ), cbdata );
453    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
454   
455    g_object_get( wind, "drag-widget", &drag, NULL );
456    setupdrag( drag, cbdata );
457}
458
459static void
460remakewind( struct cbdata * cbdata )
461{
462    GtkWidget * win;
463
464    if( NULL != cbdata->wind )
465    {
466        return;
467    }
468
469    /* create window */
470    win = tr_window_new();
471    winsetup( cbdata, TR_WINDOW( win ) );
472    tr_window_show( TR_WINDOW( win ) );
473}
474
475static void
476makeicon( struct cbdata * cbdata )
477{
478    TrIcon * icon;
479    int      ii;
480
481    if( NULL != cbdata->icon )
482    {
483        return;
484    }
485
486    icon = tr_icon_new();
487    for( ii = 0; ii < ALEN( actions ); ii++ )
488    {
489        tr_icon_action_add( TR_ICON( icon ), ii, actions[ii].flags,
490                              gettext( actions[ii].label ), actions[ii].icon );
491    }
492    g_object_set( icon, "activate-action", ACT_ICON, NULL);
493    g_signal_connect( icon, "action", G_CALLBACK( windact ), cbdata );
494
495    cbdata->icon = icon;
496}
497
498static gboolean
499winclose( GtkWidget * widget SHUTUP, GdkEvent * event SHUTUP, gpointer gdata )
500{
501    struct cbdata * cbdata;
502
503    cbdata = gdata;
504
505    if( NULL != cbdata->icon && tr_icon_docked( cbdata->icon ) )
506    {
507        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
508        cbdata->wind = NULL;
509    }
510    else
511    {
512        askquit( cbdata->wind, wannaquit, cbdata );
513    }
514
515    /* don't propagate event further */
516    return TRUE;
517}
518
519static void
520wannaquit( void * vdata )
521{
522  struct cbdata * data;
523  struct exitdata *edata;
524  GtkTreeIter iter;
525  TrTorrent *tor;
526
527  data = vdata;
528  if( data->closing )
529  {
530      return;
531  }
532  data->closing = TRUE;
533
534  /* stop the update timer */
535  if(0 < data->timer)
536    g_source_remove(data->timer);
537  data->timer = 0;
538
539  /*
540    Add a reference to all torrents in the list, which will be removed
541    when the politely-stopped signal is emitted.  This is necessary
542    because a reference is added when a torrent is removed
543    from the model and tr_torrent_stop_polite() is called on it.
544  */
545  if(gtk_tree_model_get_iter_first(data->model, &iter)) {
546    do
547      gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
548    while(gtk_tree_model_iter_next(data->model, &iter));
549  }
550
551  /* try to politely stop all the torrents */
552  tr_backend_stop_torrents(data->back);
553
554  /* shut down nat traversal */
555  tr_natTraversalEnable(tr_backend_handle(data->back), 0);
556
557  /* set things up to wait for torrents to stop */
558  edata = g_new0(struct exitdata, 1);
559  edata->cbdata = data;
560  edata->started = time(NULL);
561  /* check if torrents are still running */
562  if(exitcheck(edata)) {
563    /* yes, start the exit timer and disable widgets */
564    edata->timer = g_timeout_add(EXIT_CHECK_INTERVAL, exitcheck, edata);
565    if( NULL != data->wind )
566    {
567        gtk_widget_set_sensitive( GTK_WIDGET( data->wind ), FALSE );
568    }
569  }
570}
571
572static gboolean
573exitcheck( gpointer gdata )
574{
575    struct exitdata    * edata;
576    struct cbdata      * cbdata;
577    tr_handle_status_t * hstat;
578
579    edata  = gdata;
580    cbdata = edata->cbdata;
581    hstat  = tr_handleStatus( tr_backend_handle( cbdata->back ) );
582
583    /* keep going if we haven't hit the exit timeout and
584       we either have torrents left or nat traversal is active */
585    if( time( NULL ) - edata->started < TRACKER_EXIT_TIMEOUT )
586    {
587        if( !tr_backend_torrents_stopped( cbdata->back, FALSE ) ||
588            TR_NAT_TRAVERSAL_DISABLED != hstat->natTraversalStatus )
589        {
590            updatemodel( cbdata );
591            return TRUE;
592        }
593    }
594    else
595    {
596        /* time the remaining torrents out so they signal politely-stopped */
597        tr_backend_torrents_stopped( cbdata->back, TRUE );
598    }
599
600    /* exit otherwise */
601    if( 0 < edata->timer )
602    {
603        g_source_remove( edata->timer );
604    }
605    g_free( edata );
606    /* The prefs window need to be destroyed first as destroying it may
607       trigger callbacks that use cbdata->back. Ick. */
608    if( NULL != cbdata->prefs )
609    {
610        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
611    }
612    g_object_unref( G_OBJECT( cbdata->back ) );
613    if( NULL != cbdata->wind )
614    {
615        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
616    }
617    g_object_unref( cbdata->model );
618    if( NULL != cbdata->icon )
619    {
620        g_object_unref( cbdata->icon );
621    }
622    g_assert( 0 == cbdata->timer );
623    g_free( cbdata );
624    gtk_main_quit();
625
626    return FALSE;
627}
628
629static void
630gotdrag(GtkWidget *widget SHUTUP, GdkDragContext *dc, gint x SHUTUP,
631        gint y SHUTUP, GtkSelectionData *sel, guint info SHUTUP, guint time,
632        gpointer gdata) {
633  struct cbdata *data = gdata;
634  char prefix[] = "file:";
635  char *files, *decoded, *deslashed, *hostless;
636  int ii, len;
637  GList *errs;
638  struct stat sb;
639  int prelen = strlen(prefix);
640  GList *paths, *freeables;
641
642#ifdef DND_DEBUG
643  char *sele = gdk_atom_name(sel->selection);
644  char *targ = gdk_atom_name(sel->target);
645  char *type = gdk_atom_name(sel->type);
646
647  fprintf(stderr, "dropped file: sel=%s targ=%s type=%s fmt=%i len=%i\n",
648          sele, targ, type, sel->format, sel->length);
649  g_free(sele);
650  g_free(targ);
651  g_free(type);
652  if(8 == sel->format) {
653    for(ii = 0; ii < sel->length; ii++)
654      fprintf(stderr, "%02X ", sel->data[ii]);
655    fprintf(stderr, "\n");
656  }
657#endif
658
659  errs = NULL;
660  paths = NULL;
661  freeables = NULL;
662  if(gdk_atom_intern("XdndSelection", FALSE) == sel->selection &&
663     8 == sel->format) {
664    /* split file list on carriage returns and linefeeds */
665    files = g_new(char, sel->length + 1);
666    memcpy(files, sel->data, sel->length);
667    files[sel->length] = '\0';
668    for(ii = 0; '\0' != files[ii]; ii++)
669      if('\015' == files[ii] || '\012' == files[ii])
670        files[ii] = '\0';
671
672    /* try to get a usable filename out of the URI supplied and add it */
673    for(ii = 0; ii < sel->length; ii += len + 1) {
674      if('\0' == files[ii])
675        len = 0;
676      else {
677        len = strlen(files + ii);
678        /* de-urlencode the URI */
679        decoded = urldecode(files + ii, len);
680        freeables = g_list_append(freeables, decoded);
681        if(g_utf8_validate(decoded, -1, NULL)) {
682          /* remove the file: prefix */
683          if(prelen < len && 0 == strncmp(prefix, decoded, prelen)) {
684            deslashed = decoded + prelen;
685            /* trim excess / characters from the beginning */
686            while('/' == deslashed[0] && '/' == deslashed[1])
687              deslashed++;
688            /* if the file doesn't exist, the first part might be a hostname */
689            if(0 > g_stat(deslashed, &sb) &&
690               NULL != (hostless = strchr(deslashed + 1, '/')) &&
691               0 == g_stat(hostless, &sb))
692              deslashed = hostless;
693            /* finally, add it to the list of torrents to try adding */
694            paths = g_list_append(paths, deslashed);
695          }
696        }
697      }
698    }
699
700    /* try to add any torrents we found */
701    if( NULL != paths )
702    {
703        addtorrents( data, NULL, paths, NULL,
704                     addactionflag( tr_prefs_get( PREF_ID_ADDSTD ) ) );
705    }
706    freestrlist(freeables);
707    g_free(files);
708  }
709
710  gtk_drag_finish(dc, (NULL != paths), FALSE, time);
711}
712
713static void
714setupdrag(GtkWidget *widget, struct cbdata *data) {
715  GtkTargetEntry targets[] = {
716    { "STRING",     0, 0 },
717    { "text/plain", 0, 0 },
718    { "text/uri-list", 0, 0 },
719  };
720
721  g_signal_connect(widget, "drag_data_received", G_CALLBACK(gotdrag), data);
722
723  gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, targets,
724                    ALEN(targets), GDK_ACTION_COPY | GDK_ACTION_MOVE);
725}
726
727static void
728readinitialprefs( struct cbdata * cbdata )
729{
730    int prefs[] =
731    {
732        PREF_ID_PORT,
733        PREF_ID_USEDOWNLIMIT,
734        PREF_ID_USEUPLIMIT,
735        PREF_ID_NAT,
736        PREF_ID_ICON,
737        PREF_ID_PEX,
738    };
739    int ii;
740
741    for( ii = 0; ALEN( prefs ) > ii; ii++ )
742    {
743        prefschanged( NULL, prefs[ii], cbdata );
744    }
745}
746
747static void
748prefschanged( GtkWidget * widget SHUTUP, int id, gpointer data )
749{
750    struct cbdata * cbdata;
751    tr_handle_t   * tr;
752    int             num;
753    gboolean        boolval;
754
755    cbdata = data;
756    tr     = tr_backend_handle( cbdata->back );
757
758    switch( id )
759    {
760        case PREF_ID_PORT:
761            tr_setBindPort( tr, tr_prefs_get_int_with_default( id ) );
762            break;
763
764        case PREF_ID_USEDOWNLIMIT:
765        case PREF_ID_DOWNLIMIT:
766            num = -1;
767            if( tr_prefs_get_bool_with_default( PREF_ID_USEDOWNLIMIT ) )
768            {
769                num = tr_prefs_get_int_with_default( PREF_ID_DOWNLIMIT );
770            }
771            tr_setGlobalDownloadLimit( tr, num );
772            break;
773
774        case PREF_ID_USEUPLIMIT:
775        case PREF_ID_UPLIMIT:
776            num = -1;
777            if( tr_prefs_get_bool_with_default( PREF_ID_USEUPLIMIT ) )
778            {
779                num = tr_prefs_get_int_with_default( PREF_ID_UPLIMIT );
780            }
781            tr_setGlobalUploadLimit( tr, num );
782            break;
783
784        case PREF_ID_NAT:
785            tr_natTraversalEnable( tr, tr_prefs_get_bool_with_default( id ) );
786            break;
787
788        case PREF_ID_ICON:
789            if( tr_prefs_get_bool_with_default( id ) )
790            {
791                makeicon( cbdata );
792            }
793            else if( NULL != cbdata->icon )
794            {
795                g_object_unref( cbdata->icon );
796                cbdata->icon = NULL;
797            }
798            break;
799
800        case PREF_ID_PEX:
801            boolval = tr_prefs_get_bool_with_default( id );
802            tr_torrentIterate( tr, setpex, &boolval );
803            break;
804
805        case PREF_ID_DIR:
806        case PREF_ID_ASKDIR:
807        case PREF_ID_ADDSTD:
808        case PREF_ID_ADDIPC:
809        case PREF_ID_MSGLEVEL:
810        case PREF_MAX_ID:
811            break;
812    }
813}
814
815void
816setpex( tr_torrent_t * tor, void * arg )
817{
818    gboolean * val;
819
820    val = arg;
821    tr_torrentDisablePex( tor, !(*val) );
822}
823
824gboolean
825updatemodel(gpointer gdata) {
826  struct cbdata *data = gdata;
827  TrTorrent *tor;
828  tr_stat_t *st;
829  tr_info_t *in;
830  GtkTreeIter iter;
831  float up, down;
832
833  if( !data->closing && 0 < global_sigcount )
834  {
835      wannaquit( data );
836      return FALSE;
837  }
838
839  if(gtk_tree_model_get_iter_first(data->model, &iter)) {
840    do {
841      gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
842      st = tr_torrent_stat(tor);
843      in = tr_torrent_info(tor);
844      g_object_unref(tor);
845      /* XXX find out if setting the same data emits changed signal */
846      gtk_list_store_set(GTK_LIST_STORE(data->model), &iter, MC_NAME, in->name,
847        MC_SIZE, in->totalSize, MC_STAT, st->status, MC_ERR, st->error,
848        MC_TERR, st->errorString, MC_PROG, st->progress,
849        MC_DRATE, st->rateDownload, MC_URATE, st->rateUpload, MC_ETA, st->eta,
850        MC_PEERS, st->peersTotal, MC_UPEERS, st->peersUploading,
851        MC_DPEERS, st->peersDownloading, MC_DOWN, st->downloaded,
852        MC_UP, st->uploaded, -1);
853    } while(gtk_tree_model_iter_next(data->model, &iter));
854  }
855
856  /* update the main window's statusbar and toolbar buttons */
857  if( NULL != data->wind )
858  {
859      tr_torrentRates( tr_backend_handle( data->back ), &down, &up );
860      tr_window_update( TR_WINDOW( data->wind ), down, up );
861  }
862
863  /* check for politely stopped torrents unless we're exiting */
864  if( !data->closing )
865  {
866      tr_backend_torrents_stopped( data->back, FALSE );
867  }
868
869  /* update the message window */
870  msgwin_update();
871
872  return TRUE;
873}
874
875static void
876boolwindclosed(GtkWidget *widget SHUTUP, gpointer gdata) {
877  gboolean *preachy_gcc = gdata;
878 
879  *preachy_gcc = FALSE;
880}
881
882static void
883windact( GtkWidget * wind SHUTUP, int action, gpointer gdata )
884{
885    g_assert( 0 <= action );
886    handleaction( gdata, action );
887}
888
889/* returns a GList containing a GtkTreeRowReference to each selected row */
890static GList *
891getselection( struct cbdata * cbdata )
892{
893    GtkTreeSelection    * sel;
894    GList               * rows, * ii;
895    GtkTreeRowReference * ref;
896
897    if( NULL == cbdata->wind )
898    {
899        return NULL;
900    }
901    g_object_get( cbdata->wind, "selection", &sel, NULL );
902    rows = gtk_tree_selection_get_selected_rows( sel, NULL );
903    for( ii = rows; NULL != ii; ii = ii->next )
904    {
905        ref = gtk_tree_row_reference_new( cbdata->model, ii->data );
906        gtk_tree_path_free( ii->data );
907        ii->data = ref;
908    }
909
910    return rows;
911}
912
913static void
914handleaction( struct cbdata * data, int act )
915{
916  GList *rows, *ii;
917  GtkTreePath *path;
918  GtkTreeIter iter;
919  TrTorrent *tor;
920  int status;
921  gboolean changed;
922  GtkWidget * win;
923
924  g_assert( ACTION_COUNT > act );
925
926  switch( act )
927  {
928      case ACT_OPEN:
929          makeaddwind( data->wind, addtorrents, data );
930          return;
931      case ACT_PREF:
932          if( NULL != data->prefs )
933          {
934              return;
935          }
936          data->prefs = tr_prefs_new_with_parent( data->wind );
937          g_signal_connect( data->prefs, "prefs-changed",
938                            G_CALLBACK( prefschanged ), data );
939          g_signal_connect( data->prefs, "destroy",
940                            G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
941          gtk_widget_show( GTK_WIDGET( data->prefs ) );
942          return;
943      case ACT_DEBUG:
944          if( !data->msgwinopen )
945          {
946              data->msgwinopen = TRUE;
947              win = msgwin_create();
948              g_signal_connect( win, "destroy", G_CALLBACK( boolwindclosed ),
949                                &data->msgwinopen );
950          }
951          return;
952      case ACT_ICON:
953          remakewind( data );
954          return;
955      case ACT_CLOSE:
956          if( NULL != data->wind )
957          {
958              winclose( NULL, NULL, data );
959          }
960          return;
961      case ACT_QUIT:
962          askquit( data->wind, wannaquit, data );
963          return;
964      case ACT_SEPARATOR1:
965      case ACT_SEPARATOR2:
966      case ACT_SEPARATOR3:
967          return;
968      case ACT_START:
969      case ACT_STOP:
970      case ACT_DELETE:
971      case ACT_INFO:
972      case ACT_FILES:
973      case ACTION_COUNT:
974          break;
975  }
976
977  /* get a list of references to selected rows */
978  rows = getselection( data );
979
980  changed = FALSE;
981  for(ii = rows; NULL != ii; ii = ii->next) {
982    if(NULL != (path = gtk_tree_row_reference_get_path(ii->data)) &&
983       gtk_tree_model_get_iter(data->model, &iter, path)) {
984      gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor,
985                         MC_STAT, &status, -1);
986      if( ACT_ISAVAIL( actions[act].flags, status ) )
987
988      {
989          switch( act )
990          {
991              case ACT_START:
992                  tr_torrentStart( tr_torrent_handle( tor ) );
993                  changed = TRUE;
994                  break;
995              case ACT_STOP:
996                  tr_torrentStop( tr_torrent_handle( tor ) );
997                  changed = TRUE;
998                  break;
999              case ACT_DELETE:
1000                  /* tor will be unref'd in the politely_stopped handler */
1001                  g_object_ref( tor );
1002                  tr_torrent_stop_politely( tor );
1003                  if( TR_FLAG_SAVE & tr_torrent_info( tor )->flags )
1004                  {
1005                      tr_torrentRemoveSaved( tr_torrent_handle( tor ) );
1006                  }
1007                  gtk_list_store_remove( GTK_LIST_STORE( data->model ),
1008                                         &iter );
1009                  changed = TRUE;
1010                  break;
1011              case ACT_INFO:
1012                  makeinfowind( data->wind, tor );
1013                  break;
1014              case ACT_FILES:
1015                  makefileswind( data->wind, tor );
1016                  break;
1017              case ACT_OPEN:
1018              case ACT_PREF:
1019              case ACT_DEBUG:
1020              case ACT_ICON:
1021              case ACT_CLOSE:
1022              case ACT_QUIT:
1023              case ACT_SEPARATOR1:
1024              case ACT_SEPARATOR2:
1025              case ACT_SEPARATOR3:
1026              case ACTION_COUNT:
1027                  break;
1028          }
1029      }
1030      g_object_unref(tor);
1031    }
1032    if(NULL != path)
1033      gtk_tree_path_free(path);
1034    gtk_tree_row_reference_free(ii->data);
1035  }
1036  g_list_free(rows);
1037
1038  if(changed) {
1039    savetorrents(data);
1040    updatemodel(data);
1041  }
1042}
1043
1044static void
1045addtorrents(void *vdata, void *state, GList *files,
1046            const char *dir, guint flags) {
1047  struct cbdata *data = vdata;
1048  GList *torlist, *errlist, *ii;
1049  char *errstr;
1050  TrTorrent *tor;
1051  GtkTreeIter iter;
1052  const char * pref;
1053
1054  errlist = NULL;
1055  torlist = NULL;
1056
1057  if( NULL != state )
1058  {
1059      torlist = tr_backend_load_state( data->back, state, flags, &errlist );
1060  }
1061
1062  if(NULL != files) {
1063    if( NULL == dir )
1064    {
1065        pref = tr_prefs_get( PREF_ID_ASKDIR );
1066        if( NULL != pref && strbool( pref ) )
1067        {
1068            promptfordir( data->wind, addtorrents, data, files, flags );
1069            files = NULL;
1070        }
1071        dir = getdownloaddir();
1072    }
1073    for(ii = g_list_first(files); NULL != ii; ii = ii->next) {
1074      errstr = NULL;
1075      tor = tr_torrent_new(G_OBJECT(data->back), ii->data, dir,
1076                           flags, &errstr);
1077      if(NULL != tor)
1078        torlist = g_list_append(torlist, tor);
1079      if(NULL != errstr)
1080        errlist = g_list_append(errlist, errstr);
1081    }
1082  }
1083
1084  for(ii = g_list_first(torlist); NULL != ii; ii = ii->next) {
1085    gtk_list_store_append(GTK_LIST_STORE(data->model), &iter);
1086    gtk_list_store_set(GTK_LIST_STORE(data->model), &iter,
1087                       MC_TORRENT, ii->data, -1);
1088    /* we will always ref a torrent before politely stopping it */
1089    g_signal_connect(ii->data, "politely_stopped",
1090                     G_CALLBACK(g_object_unref), data);
1091    g_object_unref(ii->data);
1092  }
1093
1094  if(NULL != errlist) {
1095    errstr = joinstrlist(errlist, "\n");
1096    errmsg( data->wind, ngettext( "Failed to load torrent file:\n%s",
1097                                  "Failed to load torrent files:\n%s",
1098                                  g_list_length( errlist ) ), errstr );
1099    g_list_foreach(errlist, (GFunc)g_free, NULL);
1100    g_list_free(errlist);
1101    g_free(errstr);
1102  }
1103
1104  if(NULL != torlist) {
1105    updatemodel(data);
1106    savetorrents(data);
1107  }
1108}
1109
1110static void
1111savetorrents( struct cbdata *data )
1112{
1113    char * errstr;
1114
1115    tr_backend_save_state( data->back, &errstr );
1116    if( NULL != errstr )
1117    {
1118        errmsg( data->wind, "%s", errstr );
1119        g_free( errstr );
1120    }
1121}
1122
1123static void
1124safepipe(void) {
1125  struct sigaction sa;
1126
1127  bzero(&sa, sizeof(sa));
1128  sa.sa_handler = SIG_IGN;
1129  sigaction(SIGPIPE, &sa, NULL);
1130}
1131
1132static void
1133setupsighandlers(void) {
1134  int sigs[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM};
1135  struct sigaction sa;
1136  int ii;
1137
1138  bzero(&sa, sizeof(sa));
1139  sa.sa_handler = fatalsig;
1140  for(ii = 0; ii < ALEN(sigs); ii++)
1141    sigaction(sigs[ii], &sa, NULL);
1142}
1143
1144static void
1145fatalsig(int sig) {
1146  struct sigaction sa;
1147
1148  if(SIGCOUNT_MAX <= ++global_sigcount) {
1149    bzero(&sa, sizeof(sa));
1150    sa.sa_handler = SIG_DFL;
1151    sigaction(sig, &sa, NULL);
1152    raise(sig);
1153  }
1154}
Note: See TracBrowser for help on using the repository browser.