source: trunk/gtk/main.c @ 5913

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

sine we now have two public ports (peer and rpc), rename "publicPort" as "peerPort"

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