source: trunk/gtk/main.c @ 5160

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

(gtk) fix crash-when-removing-more-than-one-torrent bug reported by wereHamster and Lacrocivious

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