source: trunk/gtk/main.c @ 2007

Last change on this file since 2007 was 2007, checked in by charles, 14 years ago

Work with gtk < 2.8

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