source: trunk/gtk/main.c @ 12354

Last change on this file since 12354 was 12354, checked in by jordan, 11 years ago

(trunk gtk) transmission-gtk requires GTK+ 2.8 or higher, so remove the `#if GTK_CHECK_VERSION(2,8,0)' checks because we already know the answer.

  • Property svn:keywords set to Date Rev Author Id
File size: 59.7 KB
Line 
1/******************************************************************************
2 * $Id: main.c 12354 2011-04-12 11:13:41Z jordan $
3 *
4 * Copyright (c) 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 <locale.h>
26#include <signal.h>
27#include <string.h>
28#include <stdio.h>
29#include <stdlib.h> /* exit() */
30#include <sys/param.h>
31#include <time.h>
32#include <unistd.h>
33
34#include <curl/curl.h>
35
36#include <gtk/gtk.h>
37#include <glib/gi18n.h>
38#include <glib/gstdio.h>
39
40#ifdef HAVE_GCONF2
41 #include <gconf/gconf.h>
42 #include <gconf/gconf-client.h>
43#endif
44
45#include <libtransmission/transmission.h>
46#include <libtransmission/rpcimpl.h>
47#include <libtransmission/utils.h>
48#include <libtransmission/version.h>
49#include <libtransmission/web.h>
50
51#include "actions.h"
52#include "conf.h"
53#include "details.h"
54#include "dialogs.h"
55#include "hig.h"
56#include "makemeta-ui.h"
57#include "msgwin.h"
58#include "notify.h"
59#include "open-dialog.h"
60#include "relocate.h"
61#include "stats.h"
62#include "tr-core.h"
63#include "tr-icon.h"
64#include "tr-prefs.h"
65#include "tr-window.h"
66#include "util.h"
67#include "ui.h"
68
69#define MY_CONFIG_NAME "transmission"
70#define MY_READABLE_NAME "transmission-gtk"
71
72#define SHOW_LICENSE
73static const char * LICENSE =
74"The OS X client, CLI client, and parts of libtransmission are licensed under the terms of the MIT license.\n\n"
75"The Transmission daemon, GTK+ client, Qt client, Web client, and most of libtransmission are licensed under the terms of the GNU GPL version 2, with two special exceptions:\n\n"
76"1. The MIT-licensed portions of Transmission listed above are exempt from GPLv2 clause 2(b) and may retain their MIT license.\n\n"
77"2. Permission is granted to link the code in this release with the OpenSSL project's 'OpenSSL' library and to distribute the linked executables. Works derived from Transmission may, at their authors' discretion, keep or delete this exception.";
78
79struct cbdata
80{
81    gboolean            is_iconified;
82    guint               timer;
83    guint               refresh_actions_tag;
84    gpointer            icon;
85    GtkWindow         * wind;
86    TrCore            * core;
87    GtkWidget         * msgwin;
88    GtkWidget         * prefs;
89    GSList            * error_list;
90    GSList            * duplicates_list;
91    GSList            * details;
92    GtkTreeSelection  * sel;
93    gpointer            quit_dialog;
94};
95
96/***
97****
98****  DETAILS DIALOGS MANAGEMENT
99****
100***/
101
102static void
103gtr_window_present( GtkWindow * window )
104{
105    gtk_window_present_with_time( window, gtk_get_current_event_time( ) );
106}
107
108/***
109****
110****  DETAILS DIALOGS MANAGEMENT
111****
112***/
113
114static int
115compare_integers( const void * a, const void * b )
116{
117    return *(int*)a - *(int*)b;
118}
119
120static char*
121get_details_dialog_key( GSList * id_list )
122{
123    int i;
124    int n;
125    int * ids;
126    GSList * l;
127    GString * gstr = g_string_new( NULL );
128
129    n = g_slist_length( id_list );
130    ids = g_new( int, n );
131    i = 0;
132    for( l=id_list; l!=NULL; l=l->next )
133        ids[i++] = GPOINTER_TO_INT( l->data );
134    g_assert( i == n );
135    qsort( ids, n, sizeof(int), compare_integers );
136
137    for( i=0; i<n; ++i )
138        g_string_append_printf( gstr, "%d ", ids[i] );
139
140    g_free( ids );
141    return g_string_free( gstr, FALSE );
142}
143
144struct DetailsDialogHandle
145{
146    char * key;
147    GtkWidget * dialog;
148};
149
150static GSList*
151getSelectedTorrentIds( struct cbdata * data )
152{
153    GList * l;
154    GtkTreeModel * model;
155    GSList * ids = NULL;
156    GList * paths = NULL;
157    GtkTreeSelection * s = data->sel;
158
159    /* build a list of the selected torrents' ids */
160    for( paths=l=gtk_tree_selection_get_selected_rows(s,&model); l; l=l->next ) {
161        GtkTreeIter iter;
162        if( gtk_tree_model_get_iter( model, &iter, l->data ) ) {
163            int id;
164            gtk_tree_model_get( model, &iter, MC_TORRENT_ID, &id, -1 );
165            ids = g_slist_append( ids, GINT_TO_POINTER( id ) );
166        }
167    }
168
169    /* cleanup */
170    g_list_foreach( paths, (GFunc)gtk_tree_path_free, NULL );
171    g_list_free( paths );
172    return ids;
173}
174
175static struct DetailsDialogHandle*
176find_details_dialog_from_ids( struct cbdata * cbdata, GSList * ids )
177{
178    GSList * l;
179    struct DetailsDialogHandle * ret = NULL;
180    char * key = get_details_dialog_key( ids );
181
182    for( l=cbdata->details; l!=NULL && ret==NULL; l=l->next ) {
183        struct DetailsDialogHandle * h = l->data;
184        if( !strcmp( h->key, key ) )
185            ret = h;
186    }
187
188    g_free( key );
189    return ret;
190}
191
192static struct DetailsDialogHandle*
193find_details_dialog_from_widget( struct cbdata * cbdata, gpointer w )
194{
195    GSList * l;
196    struct DetailsDialogHandle * ret = NULL;
197
198    for( l=cbdata->details; l!=NULL && ret==NULL; l=l->next ) {
199        struct DetailsDialogHandle * h = l->data;
200        if( h->dialog == w )
201            ret = h;
202    }
203
204    return ret;
205}
206
207static void
208on_details_dialog_closed( gpointer gdata, GObject * dead )
209{
210    struct cbdata * data = gdata;
211    struct DetailsDialogHandle * h = find_details_dialog_from_widget( data, dead );
212
213    if( h != NULL )
214    {
215        data->details = g_slist_remove( data->details, h );
216        g_free( h->key );
217        g_free( h );
218    }
219}
220
221static void
222show_details_dialog_for_selected_torrents( struct cbdata * data )
223{
224    GtkWidget * w;
225    GSList * ids = getSelectedTorrentIds( data );
226    struct DetailsDialogHandle * h = find_details_dialog_from_ids( data, ids );
227
228    if( h != NULL )
229        w = h->dialog;
230    else {
231        h = g_new( struct DetailsDialogHandle, 1 );
232        h->key = get_details_dialog_key( ids );
233        h->dialog = w = gtr_torrent_details_dialog_new( data->wind, data->core );
234        gtr_torrent_details_dialog_set_torrents( w, ids );
235        data->details = g_slist_append( data->details, h );
236        g_object_weak_ref( G_OBJECT( w ), on_details_dialog_closed, data );
237        gtk_widget_show( w );
238    }
239    gtr_window_present( GTK_WINDOW( w ) );
240    g_slist_free( ids );
241}
242
243/****
244*****
245*****  ON SELECTION CHANGED
246*****
247****/
248
249struct counts_data
250{
251    int total_count;
252    int active_count;
253    int inactive_count;
254};
255
256static void
257get_selected_torrent_counts_foreach( GtkTreeModel * model, GtkTreePath * path UNUSED,
258                                     GtkTreeIter * iter, gpointer user_data )
259{
260    int activity = 0;
261    struct counts_data * counts = user_data;
262
263    ++counts->total_count;
264
265    gtk_tree_model_get( model, iter, MC_ACTIVITY, &activity, -1 );
266
267    if( activity == TR_STATUS_STOPPED )
268        ++counts->inactive_count;
269    else
270        ++counts->active_count;
271}
272
273static void
274get_selected_torrent_counts( struct cbdata * data, struct counts_data * counts )
275{
276    counts->active_count = 0;
277    counts->inactive_count = 0;
278    counts->total_count = 0;
279
280    gtk_tree_selection_selected_foreach( data->sel, get_selected_torrent_counts_foreach, counts );
281}
282
283static void
284count_updatable_foreach( GtkTreeModel * model, GtkTreePath * path UNUSED,
285                         GtkTreeIter * iter, gpointer accumulated_status )
286{
287    tr_torrent * tor;
288    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
289    *(int*)accumulated_status |= tr_torrentCanManualUpdate( tor );
290}
291
292static gboolean
293refresh_actions( gpointer gdata )
294{
295    int canUpdate;
296    struct counts_data sel_counts;
297    struct cbdata * data = gdata;
298    const size_t total = gtr_core_get_torrent_count( data->core );
299    const size_t active = gtr_core_get_active_torrent_count( data->core );
300    const int torrent_count = gtk_tree_model_iter_n_children( gtr_core_model( data->core ), NULL );
301
302    gtr_action_set_sensitive( "select-all", torrent_count != 0 );
303    gtr_action_set_sensitive( "deselect-all", torrent_count != 0 );
304    gtr_action_set_sensitive( "pause-all-torrents", active != 0 );
305    gtr_action_set_sensitive( "start-all-torrents", active != total );
306
307    get_selected_torrent_counts( data, &sel_counts );
308    gtr_action_set_sensitive( "pause-torrent", sel_counts.active_count != 0 );
309    gtr_action_set_sensitive( "start-torrent", sel_counts.inactive_count != 0 );
310    gtr_action_set_sensitive( "remove-torrent", sel_counts.total_count != 0 );
311    gtr_action_set_sensitive( "delete-torrent", sel_counts.total_count != 0 );
312    gtr_action_set_sensitive( "verify-torrent", sel_counts.total_count != 0 );
313    gtr_action_set_sensitive( "relocate-torrent", sel_counts.total_count != 0 );
314    gtr_action_set_sensitive( "show-torrent-properties", sel_counts.total_count != 0 );
315    gtr_action_set_sensitive( "open-torrent-folder", sel_counts.total_count == 1 );
316    gtr_action_set_sensitive( "copy-magnet-link-to-clipboard", sel_counts.total_count == 1 );
317
318    canUpdate = 0;
319    gtk_tree_selection_selected_foreach( data->sel, count_updatable_foreach, &canUpdate );
320    gtr_action_set_sensitive( "update-tracker", canUpdate != 0 );
321
322    data->refresh_actions_tag = 0;
323    return FALSE;
324}
325
326static void
327on_selection_changed( GtkTreeSelection * s UNUSED, gpointer gdata )
328{
329    struct cbdata * data = gdata;
330
331    if( data->refresh_actions_tag == 0 )
332        data->refresh_actions_tag = gtr_idle_add( refresh_actions, data );
333}
334
335/***
336****
337****
338***/
339
340static void app_setup( TrWindow       * wind,
341                       GSList         * torrent_files,
342                       struct cbdata  * cbdata,
343                       gboolean         paused,
344                       gboolean         minimized );
345
346static void main_window_setup( struct cbdata * cbdata, TrWindow * wind );
347
348static void on_app_exit( gpointer vdata );
349
350static void on_core_error( TrCore *, guint, const char *, struct cbdata * );
351
352static void on_add_torrent( TrCore *, tr_ctor *, gpointer );
353
354static void on_prefs_changed( TrCore * core, const char * key, gpointer );
355
356static gboolean update_model( gpointer gdata );
357
358/***
359****
360***/
361
362static void
363register_magnet_link_handler( void )
364{
365#ifdef HAVE_GCONF2
366    GError * err;
367    GConfValue * value;
368    GConfClient * client = gconf_client_get_default( );
369    const char * key = "/desktop/gnome/url-handlers/magnet/command";
370
371    /* if there's already a manget handler registered, don't do anything */
372    value = gconf_client_get( client, key, NULL );
373    if( value != NULL )
374    {
375        gconf_value_free( value );
376        return;
377    }
378
379    err = NULL;
380    if( !gconf_client_set_string( client, key, "transmission '%s'", &err ) )
381    {
382        tr_inf( "Unable to register Transmission as default magnet link handler: \"%s\"", err->message );
383        g_clear_error( &err );
384    }
385    else
386    {
387        gconf_client_set_bool( client, "/desktop/gnome/url-handlers/magnet/needs_terminal", FALSE, NULL );
388        gconf_client_set_bool( client, "/desktop/gnome/url-handlers/magnet/enabled", TRUE, NULL );
389        tr_inf( "Transmission registered as default magnet link handler" );
390    }
391
392    g_object_unref( G_OBJECT( client ) );
393#endif
394}
395
396static void
397on_main_window_size_allocated( GtkWidget      * gtk_window,
398                               GtkAllocation  * alloc UNUSED,
399                               gpointer         gdata UNUSED )
400{
401    GdkWindow * gdk_window = gtr_widget_get_window( gtk_window );
402    const gboolean isMaximized = ( gdk_window != NULL )
403                              && ( gdk_window_get_state( gdk_window ) & GDK_WINDOW_STATE_MAXIMIZED );
404
405    gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_IS_MAXIMIZED, isMaximized );
406
407    if( !isMaximized )
408    {
409        int x, y, w, h;
410        gtk_window_get_position( GTK_WINDOW( gtk_window ), &x, &y );
411        gtk_window_get_size( GTK_WINDOW( gtk_window ), &w, &h );
412        gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_X, x );
413        gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_Y, y );
414        gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_WIDTH, w );
415        gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_HEIGHT, h );
416    }
417}
418
419/***
420**** listen to changes that come from RPC
421***/
422
423struct torrent_idle_data
424{
425    TrCore * core;
426    int id;
427    gboolean delete_files;
428};
429
430static gboolean
431rpc_torrent_remove_idle( gpointer gdata )
432{
433    struct torrent_idle_data * data = gdata;
434
435    gtr_core_remove_torrent( data->core, data->id, data->delete_files );
436
437    g_free( data );
438    return FALSE; /* tell g_idle not to call this func twice */
439}
440
441static gboolean
442rpc_torrent_add_idle( gpointer gdata )
443{
444    tr_torrent * tor;
445    struct torrent_idle_data * data = gdata;
446
447    if(( tor = gtr_core_find_torrent( data->core, data->id )))
448        gtr_core_add_torrent( data->core, tor, TRUE );
449
450    g_free( data );
451    return FALSE; /* tell g_idle not to call this func twice */
452}
453
454static tr_rpc_callback_status
455on_rpc_changed( tr_session            * session,
456                tr_rpc_callback_type    type,
457                struct tr_torrent     * tor,
458                void                  * gdata )
459{
460    tr_rpc_callback_status status = TR_RPC_OK;
461    struct cbdata * cbdata = gdata;
462    gdk_threads_enter( );
463
464    switch( type )
465    {
466        case TR_RPC_SESSION_CLOSE:
467            gtr_action_activate( "quit" );
468            break;
469
470        case TR_RPC_TORRENT_ADDED: {
471            struct torrent_idle_data * data = g_new0( struct torrent_idle_data, 1 );
472            data->id = tr_torrentId( tor );
473            data->core = cbdata->core;
474            gtr_idle_add( rpc_torrent_add_idle, data );
475            break;
476        }
477
478        case TR_RPC_TORRENT_REMOVING:
479        case TR_RPC_TORRENT_TRASHING: {
480            struct torrent_idle_data * data = g_new0( struct torrent_idle_data, 1 );
481            data->id = tr_torrentId( tor );
482            data->core = cbdata->core;
483            data->delete_files = type == TR_RPC_TORRENT_TRASHING;
484            gtr_idle_add( rpc_torrent_remove_idle, data );
485            status = TR_RPC_NOREMOVE;
486            break;
487        }
488
489        case TR_RPC_SESSION_CHANGED: {
490            int i;
491            tr_benc tmp;
492            tr_benc * newval;
493            tr_benc * oldvals = gtr_pref_get_all( );
494            const char * key;
495            GSList * l;
496            GSList * changed_keys = NULL;
497            tr_bencInitDict( &tmp, 100 );
498            tr_sessionGetSettings( session, &tmp );
499            for( i=0; tr_bencDictChild( &tmp, i, &key, &newval ); ++i )
500            {
501                bool changed;
502                tr_benc * oldval = tr_bencDictFind( oldvals, key );
503                if( !oldval )
504                    changed = true;
505                else {
506                    char * a = tr_bencToStr( oldval, TR_FMT_BENC, NULL );
507                    char * b = tr_bencToStr( newval, TR_FMT_BENC, NULL );
508                    changed = strcmp( a, b ) != 0;
509                    tr_free( b );
510                    tr_free( a );
511                }
512
513                if( changed )
514                    changed_keys = g_slist_append( changed_keys, (gpointer)key );
515            }
516            tr_sessionGetSettings( session, oldvals );
517
518            for( l=changed_keys; l!=NULL; l=l->next )
519                gtr_core_pref_changed( cbdata->core, l->data );
520
521            g_slist_free( changed_keys );
522            tr_bencFree( &tmp );
523            break;
524        }
525
526        case TR_RPC_TORRENT_CHANGED:
527        case TR_RPC_TORRENT_MOVED:
528        case TR_RPC_TORRENT_STARTED:
529        case TR_RPC_TORRENT_STOPPED:
530            /* nothing interesting to do here */
531            break;
532    }
533
534    gdk_threads_leave( );
535    return status;
536}
537
538/***
539****  signal handling
540***/
541
542static sig_atomic_t global_sigcount = 0;
543static struct cbdata * sighandler_cbdata = NULL;
544
545static void
546signal_handler( int sig )
547{
548    if( ++global_sigcount > 1 )
549    {
550        signal( sig, SIG_DFL );
551        raise( sig );
552    }
553    else switch( sig )
554    {
555        case SIGINT:
556        case SIGTERM:
557            g_message( _( "Got signal %d; trying to shut down cleanly. Do it again if it gets stuck." ), sig );
558            gtr_actions_handler( "quit", sighandler_cbdata );
559            break;
560
561        default:
562            g_message( "unhandled signal" );
563            break;
564    }
565}
566
567static void
568setupsighandlers( void )
569{
570    signal( SIGINT, signal_handler );
571    signal( SIGKILL, signal_handler );
572}
573
574/***
575****
576***/
577
578static GSList *
579checkfilenames( int argc, char **argv )
580{
581    int i;
582    GSList * ret = NULL;
583    char * pwd = g_get_current_dir( );
584
585    for( i=0; i<argc; ++i )
586    {
587        if( gtr_is_supported_url( argv[i] ) || gtr_is_magnet_link( argv[i] ) )
588        {
589            ret = g_slist_prepend( ret, g_strdup( argv[i] ) );
590        }
591        else /* local file */
592        {
593            char * filename = g_path_is_absolute( argv[i] )
594                            ? g_strdup ( argv[i] )
595                            : g_build_filename( pwd, argv[i], NULL );
596
597            if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
598                ret = g_slist_prepend( ret, filename );
599            else {
600                if( gtr_is_hex_hashcode( argv[i] ) )
601                    ret = g_slist_prepend( ret, g_strdup_printf( "magnet:?xt=urn:btih:%s", argv[i] ) );
602                g_free( filename );
603            }
604        }
605    }
606
607    g_free( pwd );
608    return g_slist_reverse( ret );
609}
610
611
612#ifdef HAVE_GCONF2
613static void
614apply_desktop_proxy_settings( CURL * easy, GConfClient * client, const char * host_key, const char * port_key )
615{
616    int port;
617    GConfValue * value;
618    static gboolean env_set;
619    static gboolean env_checked = FALSE;
620
621    /* Both libcurl and GNOME have hooks for proxy support.
622     * If someone has set the http_proxy environment variable,
623     * don't apply the GNOME settings here. That way libcurl can override GNOME. */
624    if( !env_checked ) {
625        const char * str = g_getenv( "http_proxy" );
626        env_set = str && *str;
627        env_checked = TRUE;
628    }
629    if( env_set )
630        return;
631
632    if(( value = gconf_client_get( client, host_key, NULL )))
633    {
634        const char * url = gconf_value_get_string( value );
635
636        if( url && *url )
637        {
638            char * scheme = NULL;
639            GConfValue * port_value;
640
641            if( !tr_urlParse( url, strlen( url ), &scheme, NULL, NULL, NULL ) )
642            {
643                if( !tr_strcmp0( scheme, "socks4" ) )
644                    curl_easy_setopt( easy, CURLOPT_PROXYTYPE, (long)CURLPROXY_SOCKS4 );
645                else if( !tr_strcmp0( scheme, "socks5" ) )
646                    curl_easy_setopt( easy, CURLOPT_PROXYTYPE, (long)CURLPROXY_SOCKS5 );
647                else if( !tr_strcmp0( scheme, "http" ) )
648                    curl_easy_setopt( easy, CURLOPT_PROXYTYPE, (long)CURLPROXY_HTTP );
649            }
650
651            curl_easy_setopt( easy, CURLOPT_PROXY, url );
652
653            if( port_key != NULL )
654            {
655                if(( port_value = gconf_client_get( client, port_key, NULL )))
656                {
657                    if(( port = gconf_value_get_int( value )))
658                        curl_easy_setopt( easy, CURLOPT_PROXYPORT, (long)port );
659
660                    gconf_value_free( port_value );
661                }
662            }
663
664            tr_free( scheme );
665        }
666
667        gconf_value_free( value );
668    }
669}
670#endif
671
672static void
673curl_config_func( tr_session * session UNUSED, void * vcurl UNUSED, const char * destination )
674{
675#ifdef HAVE_GCONF2
676    GConfValue * value;
677    CURL * easy = vcurl;
678    gboolean use_http_proxy = TRUE;
679    GConfClient * client = gconf_client_get_default( );
680
681    /* get GNOME's proxy mode */
682    if(( value = gconf_client_get( client, "/system/proxy/mode", NULL )))
683    {
684        const char * mode = gconf_value_get_string( value );
685
686        if( !tr_strcmp0( mode, "auto" ) )
687        {
688            apply_desktop_proxy_settings( easy, client, "/system/proxy/autoconfig_url", NULL );
689            use_http_proxy = FALSE;
690        }
691        else if( !tr_strcmp0( mode, "manual" ))
692        {
693            char * scheme = NULL;
694            if( !tr_urlParse( destination, strlen( destination ), &scheme, NULL, NULL, NULL ) )
695            {
696                if( !tr_strcmp0( scheme, "ftp" ) )
697                {
698                    apply_desktop_proxy_settings( easy, client, "/system/proxy/ftp_host", "/system/proxy/ftp_port" );
699                    use_http_proxy = FALSE;
700                }
701                else if( !tr_strcmp0( scheme, "https" ) )
702                {
703                    apply_desktop_proxy_settings( easy, client, "/system/proxy/secure_host", "/system/proxy/secure_port" );
704                    use_http_proxy = FALSE;
705                }
706            }
707            tr_free( scheme );
708        }
709
710        gconf_value_free( value );
711    }
712
713    /* if this the proxy hasn't been handled yet and "use_http_proxy" is disabled, then don't use a proxy */
714    if( use_http_proxy ) {
715        if(( value = gconf_client_get( client, "/system/http_proxy/use_http_proxy", NULL ))) {
716            use_http_proxy = gconf_value_get_bool( value ) != 0;
717            gconf_value_free( value );
718        }
719    }
720
721    if( use_http_proxy )
722    {
723        gboolean auth = FALSE;
724        apply_desktop_proxy_settings( easy, client, "/system/http_proxy/host", "/system/http_proxy/port" );
725
726        if(( value = gconf_client_get( client, "/system/http_proxy/use_authentication", NULL )))
727        {
728            auth = gconf_value_get_bool( value );
729            gconf_value_free( value );
730        }
731
732        if( auth )
733        {
734            GConfValue * user_value = gconf_client_get( client, "/system/http_proxy/authentication_user", NULL );
735            const char * user = ( user_value != NULL ) ? gconf_value_get_string( user_value ) : NULL;
736            GConfValue * pass_value = gconf_client_get( client, "/system/http_proxy/authentication_password", NULL );
737            const char * pass = ( pass_value != NULL ) ? gconf_value_get_string( pass_value ) : NULL;
738
739            if( ( user != NULL ) && ( pass != NULL ) )
740            {
741                char * userpass = g_strdup_printf( "%s:%s", user, pass );
742                curl_easy_setopt( easy, CURLOPT_PROXYUSERPWD, userpass );
743                g_free( userpass );
744            }
745
746            if( pass_value ) gconf_value_free( pass_value );
747            if( user_value ) gconf_value_free( user_value );
748        }
749    }
750
751    g_object_unref( G_OBJECT( client ) );
752#endif
753}
754
755int
756main( int argc, char ** argv )
757{
758    char * err = NULL;
759    GSList * argfiles;
760    GError * gerr;
761    gboolean didinit = FALSE;
762    gboolean didlock = FALSE;
763    gboolean showversion = FALSE;
764    gboolean startpaused = FALSE;
765    gboolean startminimized = FALSE;
766    const char * domain = MY_READABLE_NAME;
767    char * configDir = NULL;
768    gtr_lockfile_state_t tr_state;
769
770    GOptionEntry entries[] = {
771        { "paused",     'p', 0, G_OPTION_ARG_NONE,
772          &startpaused, _( "Start with all torrents paused" ), NULL },
773        { "version",    '\0', 0, G_OPTION_ARG_NONE,
774          &showversion, _( "Show version number and exit" ), NULL },
775#ifdef STATUS_ICON_SUPPORTED
776        { "minimized",  'm', 0, G_OPTION_ARG_NONE,
777          &startminimized,
778          _( "Start minimized in notification area" ), NULL },
779#endif
780        { "config-dir", 'g', 0, G_OPTION_ARG_FILENAME, &configDir,
781          _( "Where to look for configuration files" ), NULL },
782        { NULL, 0,   0, 0, NULL, NULL, NULL }
783    };
784
785    /* bind the gettext domain */
786    setlocale( LC_ALL, "" );
787    bindtextdomain( domain, TRANSMISSIONLOCALEDIR );
788    bind_textdomain_codeset( domain, "UTF-8" );
789    textdomain( domain );
790    g_set_application_name( _( "Transmission" ) );
791    tr_formatter_mem_init( mem_K, _(mem_K_str), _(mem_M_str), _(mem_G_str), _(mem_T_str) );
792    tr_formatter_size_init( disk_K, _(disk_K_str), _(disk_M_str), _(disk_G_str), _(disk_T_str) );
793    tr_formatter_speed_init( speed_K, _(speed_K_str), _(speed_M_str), _(speed_G_str), _(speed_T_str) );
794
795    /* initialize gtk */
796    if( !g_thread_supported( ) )
797        g_thread_init( NULL );
798
799    gerr = NULL;
800    if( !gtk_init_with_args( &argc, &argv, (char*)_( "[torrent files or urls]" ), entries,
801                             (char*)domain, &gerr ) )
802    {
803        fprintf( stderr, "%s\n", gerr->message );
804        g_clear_error( &gerr );
805        return 0;
806    }
807
808    if( showversion )
809    {
810        fprintf( stderr, "%s %s\n", MY_READABLE_NAME, LONG_VERSION_STRING );
811        return 0;
812    }
813
814    if( configDir == NULL )
815        configDir = (char*) tr_getDefaultConfigDir( MY_CONFIG_NAME );
816
817    gtr_notify_init( );
818    didinit = cf_init( configDir, NULL ); /* must come before actions_init */
819
820    setupsighandlers( ); /* set up handlers for fatal signals */
821
822    didlock = cf_lock( &tr_state, &err );
823    argfiles = checkfilenames( argc - 1, argv + 1 );
824
825    if( !didlock && argfiles )
826    {
827        /* We have torrents to add but there's another copy of Transmsision
828         * running... chances are we've been invoked from a browser, etc.
829         * So send the files over to the "real" copy of Transmission, and
830         * if that goes well, then our work is done. */
831        GSList * l;
832        gboolean delegated = FALSE;
833        const gboolean trash_originals = gtr_pref_flag_get( TR_PREFS_KEY_TRASH_ORIGINAL );
834
835        for( l=argfiles; l!=NULL; l=l->next )
836        {
837            const char * filename = l->data;
838            const gboolean added = gtr_dbus_add_torrent( filename );
839
840            if( added && trash_originals )
841                gtr_file_trash_or_remove( filename );
842
843            delegated |= added;
844        }
845
846        if( delegated ) {
847            g_slist_foreach( argfiles, (GFunc)g_free, NULL );
848            g_slist_free( argfiles );
849            argfiles = NULL;
850
851            if( err ) {
852                g_free( err );
853                err = NULL;
854            }
855        }
856    }
857    else if( ( !didlock ) && ( tr_state == GTR_LOCKFILE_ELOCK ) )
858    {
859        /* There's already another copy of Transmission running,
860         * so tell it to present its window to the user */
861        err = NULL;
862        if( !gtr_dbus_present_window( ) )
863            err = g_strdup( _( "Transmission is already running, but is not responding. To start a new session, you must first close the existing Transmission process." ) );
864    }
865
866    if( didlock && ( didinit || cf_init( configDir, &err ) ) )
867    {
868        /* No other copy of Transmission running...
869         * so we're going to be the primary. */
870
871        const char * str;
872        GtkWindow * win;
873        GtkUIManager * myUIManager;
874        tr_session * session;
875        struct cbdata * cbdata = g_new0( struct cbdata, 1 );
876
877        sighandler_cbdata = cbdata;
878
879        /* ensure the directories are created */
880        if(( str = gtr_pref_string_get( TR_PREFS_KEY_DOWNLOAD_DIR )))
881            gtr_mkdir_with_parents( str, 0777 );
882        if(( str = gtr_pref_string_get( TR_PREFS_KEY_INCOMPLETE_DIR )))
883            gtr_mkdir_with_parents( str, 0777 );
884
885        /* initialize the libtransmission session */
886        session = tr_sessionInit( "gtk", configDir, TRUE, gtr_pref_get_all( ) );
887        tr_sessionSetWebConfigFunc( session, curl_config_func );
888
889        gtr_pref_flag_set( TR_PREFS_KEY_ALT_SPEED_ENABLED, tr_sessionUsesAltSpeed( session ) );
890        gtr_pref_int_set( TR_PREFS_KEY_PEER_PORT, tr_sessionGetPeerPort( session ) );
891        cbdata->core = gtr_core_new( session );
892
893        /* init the ui manager */
894        myUIManager = gtk_ui_manager_new ( );
895        gtr_actions_init ( myUIManager, cbdata );
896        gtk_ui_manager_add_ui_from_string ( myUIManager, fallback_ui_file, -1, NULL );
897        gtk_ui_manager_ensure_update ( myUIManager );
898        gtk_window_set_default_icon_name ( MY_CONFIG_NAME );
899
900        /* create main window now to be a parent to any error dialogs */
901        win = GTK_WINDOW( gtr_window_new( myUIManager, cbdata->core ) );
902        g_signal_connect( win, "size-allocate", G_CALLBACK( on_main_window_size_allocated ), cbdata );
903
904        app_setup( win, argfiles, cbdata, startpaused, startminimized );
905        tr_sessionSetRPCCallback( session, on_rpc_changed, cbdata );
906
907        /* on startup, check & see if it's time to update the blocklist */
908        if( gtr_pref_flag_get( TR_PREFS_KEY_BLOCKLIST_ENABLED ) ) {
909            if( gtr_pref_flag_get( PREF_KEY_BLOCKLIST_UPDATES_ENABLED ) ) {
910                const int64_t last_time = gtr_pref_int_get( "blocklist-date" );
911                const int SECONDS_IN_A_WEEK = 7 * 24 * 60 * 60;
912                const time_t now = time( NULL );
913                if( last_time + SECONDS_IN_A_WEEK < now )
914                    gtr_core_blocklist_update( cbdata->core );
915            }
916        }
917
918        /* if there's no magnet link handler registered, register us */
919        register_magnet_link_handler( );
920
921        gtk_main( );
922    }
923    else if( err )
924    {
925        const char * primary_text = _( "Transmission cannot be started." );
926        GtkWidget * w = gtk_message_dialog_new( NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, primary_text, NULL );
927        gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ), "%s", err );
928        g_signal_connect( w, "response", G_CALLBACK(gtk_main_quit), NULL );
929        gtk_widget_show( w );
930        g_free( err );
931        gtk_main( );
932    }
933
934    return 0;
935}
936
937static void
938on_core_busy( TrCore * core UNUSED, gboolean busy, struct cbdata * c )
939{
940    gtr_window_set_busy( c->wind, busy );
941}
942
943static void
944app_setup( TrWindow      * wind,
945           GSList        * files,
946           struct cbdata * cbdata,
947           gboolean        pause_all,
948           gboolean        is_iconified )
949{
950    const gboolean do_start = gtr_pref_flag_get( TR_PREFS_KEY_START ) && !pause_all;
951    const gboolean do_prompt = gtr_pref_flag_get( PREF_KEY_OPTIONS_PROMPT );
952    const gboolean do_notify = TRUE;
953
954    cbdata->is_iconified = is_iconified;
955
956    if( is_iconified )
957        gtr_pref_flag_set( PREF_KEY_SHOW_TRAY_ICON, TRUE );
958
959    gtr_actions_set_core( cbdata->core );
960
961    /* set up core handlers */
962    g_signal_connect( cbdata->core, "busy", G_CALLBACK( on_core_busy ), cbdata );
963    g_signal_connect( cbdata->core, "add-error", G_CALLBACK( on_core_error ), cbdata );
964    g_signal_connect( cbdata->core, "add-prompt", G_CALLBACK( on_add_torrent ), cbdata );
965    g_signal_connect( cbdata->core, "prefs-changed", G_CALLBACK( on_prefs_changed ), cbdata );
966
967    /* add torrents from command-line and saved state */
968    gtr_core_load( cbdata->core, pause_all );
969    gtr_core_add_list( cbdata->core, files, do_start, do_prompt, do_notify );
970    files = NULL;
971    gtr_core_torrents_added( cbdata->core );
972
973    /* set up main window */
974    main_window_setup( cbdata, wind );
975
976    /* set up the icon */
977    on_prefs_changed( cbdata->core, PREF_KEY_SHOW_TRAY_ICON, cbdata );
978
979    /* start model update timer */
980    cbdata->timer = gtr_timeout_add_seconds( MAIN_WINDOW_REFRESH_INTERVAL_SECONDS, update_model, cbdata );
981    update_model( cbdata );
982
983    /* either show the window or iconify it */
984    if( !is_iconified )
985        gtk_widget_show( GTK_WIDGET( wind ) );
986    else
987    {
988        gtk_window_set_skip_taskbar_hint( cbdata->wind,
989                                          cbdata->icon != NULL );
990        cbdata->is_iconified = FALSE; // ensure that the next toggle iconifies
991        gtr_action_set_toggled( "toggle-main-window", FALSE );
992    }
993
994    if( !gtr_pref_flag_get( PREF_KEY_USER_HAS_GIVEN_INFORMED_CONSENT ) )
995    {
996        GtkWidget * w = gtk_message_dialog_new( GTK_WINDOW( wind ),
997                                                GTK_DIALOG_DESTROY_WITH_PARENT,
998                                                GTK_MESSAGE_INFO,
999                                                GTK_BUTTONS_NONE,
1000                                                "%s",
1001             _( "Transmission is a file-sharing program. When you run a torrent, its data will be made available to others by means of upload. You and you alone are fully responsible for exercising proper judgement and abiding by your local laws." ) );
1002        gtk_dialog_add_button( GTK_DIALOG( w ), GTK_STOCK_QUIT, GTK_RESPONSE_REJECT );
1003        gtk_dialog_add_button( GTK_DIALOG( w ), _( "I _Accept" ), GTK_RESPONSE_ACCEPT );
1004        gtk_dialog_set_default_response( GTK_DIALOG( w ), GTK_RESPONSE_ACCEPT );
1005        switch( gtk_dialog_run( GTK_DIALOG( w ) ) ) {
1006            case GTK_RESPONSE_ACCEPT:
1007                /* only show it once */
1008                gtr_pref_flag_set( PREF_KEY_USER_HAS_GIVEN_INFORMED_CONSENT, TRUE );
1009                gtk_widget_destroy( w );
1010                break;
1011            default:
1012                exit( 0 );
1013        }
1014    }
1015}
1016
1017static void
1018toggleMainWindow( struct cbdata * cbdata )
1019{
1020    GtkWindow * window = cbdata->wind;
1021    const int   doShow = cbdata->is_iconified;
1022    static int  x = 0;
1023    static int  y = 0;
1024
1025    if( doShow )
1026    {
1027        cbdata->is_iconified = 0;
1028        gtk_window_set_skip_taskbar_hint( window, FALSE );
1029        gtk_window_move( window, x, y );
1030        gtr_widget_set_visible( GTK_WIDGET( window ), TRUE );
1031        gtr_window_present( window );
1032    }
1033    else
1034    {
1035        gtk_window_get_position( window, &x, &y );
1036        gtk_window_set_skip_taskbar_hint( window, TRUE );
1037        gtr_widget_set_visible( GTK_WIDGET( window ), FALSE );
1038        cbdata->is_iconified = 1;
1039    }
1040}
1041
1042static gboolean
1043winclose( GtkWidget * w    UNUSED,
1044          GdkEvent * event UNUSED,
1045          gpointer         gdata )
1046{
1047    struct cbdata * cbdata = gdata;
1048
1049    if( cbdata->icon != NULL )
1050        gtr_action_activate ( "toggle-main-window" );
1051    else
1052        on_app_exit( cbdata );
1053
1054    return TRUE; /* don't propagate event further */
1055}
1056
1057static void
1058rowChangedCB( GtkTreeModel  * model UNUSED,
1059              GtkTreePath   * path,
1060              GtkTreeIter   * iter  UNUSED,
1061              gpointer        gdata )
1062{
1063    struct cbdata * data = gdata;
1064    if( gtk_tree_selection_path_is_selected ( data->sel, path ) )
1065        refresh_actions( gdata );
1066}
1067
1068static void
1069on_drag_data_received( GtkWidget         * widget          UNUSED,
1070                       GdkDragContext    * drag_context,
1071                       gint                x               UNUSED,
1072                       gint                y               UNUSED,
1073                       GtkSelectionData  * selection_data,
1074                       guint               info            UNUSED,
1075                       guint               time_,
1076                       gpointer            gdata )
1077{
1078    int i;
1079    gboolean success = FALSE;
1080    GSList * files = NULL;
1081    struct cbdata * data = gdata;
1082    char ** uris = gtk_selection_data_get_uris( selection_data );
1083
1084    /* try to add the filename URIs... */
1085    for( i=0; uris && uris[i]; ++i )
1086    {
1087        const char * uri = uris[i];
1088        char * filename = g_filename_from_uri( uri, NULL, NULL );
1089
1090        if( filename && g_file_test( filename, G_FILE_TEST_EXISTS ) )
1091        {
1092            files = g_slist_append( files, g_strdup( filename ) );
1093            success = TRUE;
1094        }
1095        else if( tr_urlIsValid( uri, -1 ) || gtr_is_magnet_link( uri ) )
1096        {
1097            gtr_core_add_from_url( data->core, uri );
1098            success = TRUE;
1099        }
1100
1101        g_free( filename );
1102    }
1103
1104    if( files )
1105        gtr_core_add_list_defaults( data->core, g_slist_reverse( files ), TRUE );
1106
1107    gtr_core_torrents_added( data->core );
1108    gtk_drag_finish( drag_context, success, FALSE, time_ );
1109
1110    /* cleanup */
1111    g_strfreev( uris );
1112}
1113
1114static void
1115main_window_setup( struct cbdata * cbdata, TrWindow * wind )
1116{
1117    GtkWidget * w;
1118    GtkTreeModel * model;
1119    GtkTreeSelection * sel;
1120
1121    g_assert( NULL == cbdata->wind );
1122    cbdata->wind = GTK_WINDOW( wind );
1123    cbdata->sel = sel = GTK_TREE_SELECTION( gtr_window_get_selection( cbdata->wind ) );
1124
1125    g_signal_connect( sel, "changed", G_CALLBACK( on_selection_changed ), cbdata );
1126    on_selection_changed( sel, cbdata );
1127    model = gtr_core_model( cbdata->core );
1128    g_signal_connect( model, "row-changed", G_CALLBACK( rowChangedCB ), cbdata );
1129    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
1130    refresh_actions( cbdata );
1131
1132    /* register to handle URIs that get dragged onto our main window */
1133    w = GTK_WIDGET( wind );
1134    gtk_drag_dest_set( w, GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY );
1135    gtk_drag_dest_add_uri_targets( w );
1136    g_signal_connect( w, "drag-data-received", G_CALLBACK(on_drag_data_received), cbdata );
1137}
1138
1139static gboolean
1140on_session_closed( gpointer gdata )
1141{
1142    struct cbdata * cbdata = gdata;
1143
1144    /* shutdown the gui */
1145    while( cbdata->details != NULL ) {
1146        struct DetailsDialogHandle * h = cbdata->details->data;
1147        gtk_widget_destroy( h->dialog );
1148    }
1149
1150    if( cbdata->prefs )
1151        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
1152    if( cbdata->wind )
1153        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
1154    g_object_unref( cbdata->core );
1155    if( cbdata->icon )
1156        g_object_unref( cbdata->icon );
1157    g_slist_foreach( cbdata->error_list, (GFunc)g_free, NULL );
1158    g_slist_free( cbdata->error_list );
1159    g_slist_foreach( cbdata->duplicates_list, (GFunc)g_free, NULL );
1160    g_slist_free( cbdata->duplicates_list );
1161    g_free( cbdata );
1162
1163    gtk_main_quit( );
1164
1165    return FALSE;
1166}
1167
1168static gpointer
1169session_close_threadfunc( gpointer gdata )
1170{
1171    /* since tr_sessionClose() is a blocking function,
1172     * call it from another thread... when it's done,
1173     * punt the GUI teardown back to the GTK+ thread */
1174    struct cbdata * cbdata = gdata;
1175    gdk_threads_enter( );
1176    gtr_core_close( cbdata->core );
1177    gtr_idle_add( on_session_closed, gdata );
1178    gdk_threads_leave( );
1179    return NULL;
1180}
1181
1182static void
1183exit_now_cb( GtkWidget *w UNUSED, gpointer data UNUSED )
1184{
1185    exit( 0 );
1186}
1187
1188static void
1189on_app_exit( gpointer vdata )
1190{
1191    GtkWidget *r, *p, *b, *w, *c;
1192    struct cbdata *cbdata = vdata;
1193
1194    /* stop the update timer */
1195    if( cbdata->timer )
1196    {
1197        g_source_remove( cbdata->timer );
1198        cbdata->timer = 0;
1199    }
1200
1201    c = GTK_WIDGET( cbdata->wind );
1202    gtk_container_remove( GTK_CONTAINER( c ), gtk_bin_get_child( GTK_BIN( c ) ) );
1203
1204    r = gtk_alignment_new( 0.5, 0.5, 0.01, 0.01 );
1205    gtk_container_add( GTK_CONTAINER( c ), r );
1206
1207    p = gtk_table_new( 3, 2, FALSE );
1208    gtk_table_set_col_spacings( GTK_TABLE( p ), GUI_PAD_BIG );
1209    gtk_container_add( GTK_CONTAINER( r ), p );
1210
1211    w = gtk_image_new_from_stock( GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG );
1212    gtk_table_attach_defaults( GTK_TABLE( p ), w, 0, 1, 0, 2 );
1213
1214    w = gtk_label_new( NULL );
1215    gtk_label_set_markup( GTK_LABEL( w ), _( "<b>Closing Connections</b>" ) );
1216    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
1217    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 0, 1 );
1218
1219    w = gtk_label_new( _( "Sending upload/download totals to tracker..." ) );
1220    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
1221    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 1, 2 );
1222
1223    b = gtk_alignment_new( 0.0, 1.0, 0.01, 0.01 );
1224    w = gtk_button_new_with_mnemonic( _( "_Quit Now" ) );
1225    g_signal_connect( w, "clicked", G_CALLBACK( exit_now_cb ), NULL );
1226    gtk_container_add( GTK_CONTAINER( b ), w );
1227    gtk_table_attach( GTK_TABLE( p ), b, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 10 );
1228
1229    gtk_widget_show_all( r );
1230    gtk_widget_grab_focus( w );
1231
1232    /* clear the UI */
1233    gtr_core_clear( cbdata->core );
1234
1235    /* ensure the window is in its previous position & size.
1236     * this seems to be necessary because changing the main window's
1237     * child seems to unset the size */
1238    gtk_window_resize( cbdata->wind, gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_WIDTH ),
1239                                     gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_HEIGHT ) );
1240    gtk_window_move( cbdata->wind, gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_X ),
1241                                   gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_Y ) );
1242
1243    /* shut down libT */
1244    g_thread_create( session_close_threadfunc, vdata, TRUE, NULL );
1245}
1246
1247static void
1248show_torrent_errors( GtkWindow * window, const char * primary, GSList ** files )
1249{
1250    GSList * l;
1251    GtkWidget * w;
1252    GString * s = g_string_new( NULL );
1253    const char * leader = g_slist_length( *files ) > 1
1254                        ? gtr_get_unicode_string( GTR_UNICODE_BULLET )
1255                        : "";
1256
1257    for( l=*files; l!=NULL; l=l->next )
1258        g_string_append_printf( s, "%s %s\n", leader, (const char*)l->data );
1259
1260    w = gtk_message_dialog_new( window,
1261                                GTK_DIALOG_DESTROY_WITH_PARENT,
1262                                GTK_MESSAGE_ERROR,
1263                                GTK_BUTTONS_CLOSE,
1264                                "%s", primary );
1265    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
1266                                              "%s", s->str );
1267    g_signal_connect_swapped( w, "response",
1268                              G_CALLBACK( gtk_widget_destroy ), w );
1269    gtk_widget_show( w );
1270    g_string_free( s, TRUE );
1271
1272    g_slist_foreach( *files, (GFunc)g_free, NULL );
1273    g_slist_free( *files );
1274    *files = NULL;
1275}
1276
1277static void
1278flush_torrent_errors( struct cbdata * cbdata )
1279{
1280    if( cbdata->error_list )
1281        show_torrent_errors( cbdata->wind,
1282                              gtr_ngettext( "Couldn't add corrupt torrent",
1283                                            "Couldn't add corrupt torrents",
1284                                            g_slist_length( cbdata->error_list ) ),
1285                              &cbdata->error_list );
1286
1287    if( cbdata->duplicates_list )
1288        show_torrent_errors( cbdata->wind,
1289                              gtr_ngettext( "Couldn't add duplicate torrent",
1290                                            "Couldn't add duplicate torrents",
1291                                            g_slist_length( cbdata->duplicates_list ) ),
1292                              &cbdata->duplicates_list );
1293}
1294
1295static void
1296on_core_error( TrCore * core UNUSED, guint code, const char * msg, struct cbdata * c )
1297{
1298    switch( code )
1299    {
1300        case TR_PARSE_ERR:
1301            c->error_list =
1302                g_slist_append( c->error_list, g_path_get_basename( msg ) );
1303            break;
1304
1305        case TR_PARSE_DUPLICATE:
1306            c->duplicates_list = g_slist_append( c->duplicates_list, g_strdup( msg ) );
1307            break;
1308
1309        case TR_CORE_ERR_NO_MORE_TORRENTS:
1310            flush_torrent_errors( c );
1311            break;
1312
1313        default:
1314            g_assert_not_reached( );
1315            break;
1316    }
1317}
1318
1319static gboolean
1320on_main_window_focus_in( GtkWidget      * widget UNUSED,
1321                         GdkEventFocus  * event  UNUSED,
1322                         gpointer                gdata )
1323{
1324    struct cbdata * cbdata = gdata;
1325
1326    if( cbdata->wind )
1327        gtk_window_set_urgency_hint( cbdata->wind, FALSE );
1328    return FALSE;
1329}
1330
1331static void
1332on_add_torrent( TrCore * core, tr_ctor * ctor, gpointer gdata )
1333{
1334    struct cbdata * cbdata = gdata;
1335    GtkWidget * w = gtr_torrent_options_dialog_new( cbdata->wind, core, ctor );
1336
1337    g_signal_connect( w, "focus-in-event",
1338                      G_CALLBACK( on_main_window_focus_in ),  cbdata );
1339    if( cbdata->wind )
1340        gtk_window_set_urgency_hint( cbdata->wind, TRUE );
1341
1342    gtk_widget_show( w );
1343}
1344
1345static void
1346on_prefs_changed( TrCore * core UNUSED, const char * key, gpointer data )
1347{
1348    struct cbdata * cbdata = data;
1349    tr_session * tr = gtr_core_session( cbdata->core );
1350
1351    if( !strcmp( key, TR_PREFS_KEY_ENCRYPTION ) )
1352    {
1353        tr_sessionSetEncryption( tr, gtr_pref_int_get( key ) );
1354    }
1355    else if( !strcmp( key, TR_PREFS_KEY_DOWNLOAD_DIR ) )
1356    {
1357        tr_sessionSetDownloadDir( tr, gtr_pref_string_get( key ) );
1358    }
1359    else if( !strcmp( key, TR_PREFS_KEY_MSGLEVEL ) )
1360    {
1361        tr_setMessageLevel( gtr_pref_int_get( key ) );
1362    }
1363    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT ) )
1364    {
1365        tr_sessionSetPeerPort( tr, gtr_pref_int_get( key ) );
1366    }
1367    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_ENABLED ) )
1368    {
1369        tr_blocklistSetEnabled( tr, gtr_pref_flag_get( key ) );
1370    }
1371    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_URL ) )
1372    {
1373        tr_blocklistSetURL( tr, gtr_pref_string_get( key ) );
1374    }
1375    else if( !strcmp( key, PREF_KEY_SHOW_TRAY_ICON ) )
1376    {
1377        const int show = gtr_pref_flag_get( key );
1378        if( show && !cbdata->icon )
1379            cbdata->icon = gtr_icon_new( cbdata->core );
1380        else if( !show && cbdata->icon ) {
1381            g_object_unref( cbdata->icon );
1382            cbdata->icon = NULL;
1383        }
1384    }
1385    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_ENABLED ) )
1386    {
1387        tr_sessionLimitSpeed( tr, TR_DOWN, gtr_pref_flag_get( key ) );
1388    }
1389    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_KBps ) )
1390    {
1391        tr_sessionSetSpeedLimit_KBps( tr, TR_DOWN, gtr_pref_int_get( key ) );
1392    }
1393    else if( !strcmp( key, TR_PREFS_KEY_USPEED_ENABLED ) )
1394    {
1395        tr_sessionLimitSpeed( tr, TR_UP, gtr_pref_flag_get( key ) );
1396    }
1397    else if( !strcmp( key, TR_PREFS_KEY_USPEED_KBps ) )
1398    {
1399        tr_sessionSetSpeedLimit_KBps( tr, TR_UP, gtr_pref_int_get( key ) );
1400    }
1401    else if( !strcmp( key, TR_PREFS_KEY_RATIO_ENABLED ) )
1402    {
1403        tr_sessionSetRatioLimited( tr, gtr_pref_flag_get( key ) );
1404    }
1405    else if( !strcmp( key, TR_PREFS_KEY_RATIO ) )
1406    {
1407        tr_sessionSetRatioLimit( tr, gtr_pref_double_get( key ) );
1408    }
1409    else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT ) )
1410    {
1411        tr_sessionSetIdleLimit( tr, gtr_pref_int_get( key ) );
1412    }
1413    else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT_ENABLED ) )
1414    {
1415        tr_sessionSetIdleLimited( tr, gtr_pref_flag_get( key ) );
1416    }
1417    else if( !strcmp( key, TR_PREFS_KEY_PORT_FORWARDING ) )
1418    {
1419        tr_sessionSetPortForwardingEnabled( tr, gtr_pref_flag_get( key ) );
1420    }
1421    else if( !strcmp( key, TR_PREFS_KEY_PEX_ENABLED ) )
1422    {
1423        tr_sessionSetPexEnabled( tr, gtr_pref_flag_get( key ) );
1424    }
1425    else if( !strcmp( key, TR_PREFS_KEY_RENAME_PARTIAL_FILES ) )
1426    {
1427        tr_sessionSetIncompleteFileNamingEnabled( tr, gtr_pref_flag_get( key ) );
1428    }
1429    else if( !strcmp( key, TR_PREFS_KEY_DHT_ENABLED ) )
1430    {
1431        tr_sessionSetDHTEnabled( tr, gtr_pref_flag_get( key ) );
1432    }
1433    else if( !strcmp( key, TR_PREFS_KEY_UTP_ENABLED ) )
1434    {
1435        tr_sessionSetUTPEnabled( tr, gtr_pref_flag_get( key ) );
1436    }
1437    else if( !strcmp( key, TR_PREFS_KEY_LPD_ENABLED ) )
1438    {
1439        tr_sessionSetLPDEnabled( tr, gtr_pref_flag_get( key ) );
1440    }
1441    else if( !strcmp( key, TR_PREFS_KEY_RPC_PORT ) )
1442    {
1443        tr_sessionSetRPCPort( tr, gtr_pref_int_get( key ) );
1444    }
1445    else if( !strcmp( key, TR_PREFS_KEY_RPC_ENABLED ) )
1446    {
1447        tr_sessionSetRPCEnabled( tr, gtr_pref_flag_get( key ) );
1448    }
1449    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST ) )
1450    {
1451        tr_sessionSetRPCWhitelist( tr, gtr_pref_string_get( key ) );
1452    }
1453    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST_ENABLED ) )
1454    {
1455        tr_sessionSetRPCWhitelistEnabled( tr, gtr_pref_flag_get( key ) );
1456    }
1457    else if( !strcmp( key, TR_PREFS_KEY_RPC_USERNAME ) )
1458    {
1459        tr_sessionSetRPCUsername( tr, gtr_pref_string_get( key ) );
1460    }
1461    else if( !strcmp( key, TR_PREFS_KEY_RPC_PASSWORD ) )
1462    {
1463        tr_sessionSetRPCPassword( tr, gtr_pref_string_get( key ) );
1464    }
1465    else if( !strcmp( key, TR_PREFS_KEY_RPC_AUTH_REQUIRED ) )
1466    {
1467        tr_sessionSetRPCPasswordEnabled( tr, gtr_pref_flag_get( key ) );
1468    }
1469    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_UP_KBps ) )
1470    {
1471        tr_sessionSetAltSpeed_KBps( tr, TR_UP, gtr_pref_int_get( key ) );
1472    }
1473    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps ) )
1474    {
1475        tr_sessionSetAltSpeed_KBps( tr, TR_DOWN, gtr_pref_int_get( key ) );
1476    }
1477    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_ENABLED ) )
1478    {
1479        const gboolean b = gtr_pref_flag_get( key );
1480        tr_sessionUseAltSpeed( tr, b );
1481        gtr_action_set_toggled( key, b );
1482    }
1483    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN ) )
1484    {
1485        tr_sessionSetAltSpeedBegin( tr, gtr_pref_int_get( key ) );
1486    }
1487    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_END ) )
1488    {
1489        tr_sessionSetAltSpeedEnd( tr, gtr_pref_int_get( key ) );
1490    }
1491    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED ) )
1492    {
1493        tr_sessionUseAltSpeedTime( tr, gtr_pref_flag_get( key ) );
1494    }
1495    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_DAY ) )
1496    {
1497        tr_sessionSetAltSpeedDay( tr, gtr_pref_int_get( key ) );
1498    }
1499    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START ) )
1500    {
1501        tr_sessionSetPeerPortRandomOnStart( tr, gtr_pref_flag_get( key ) );
1502    }
1503    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR ) )
1504    {
1505        tr_sessionSetIncompleteDir( tr, gtr_pref_string_get( key ) );
1506    }
1507    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED ) )
1508    {
1509        tr_sessionSetIncompleteDirEnabled( tr, gtr_pref_flag_get( key ) );
1510    }
1511    else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED ) )
1512    {
1513        tr_sessionSetTorrentDoneScriptEnabled( tr, gtr_pref_flag_get( key ) );
1514    }
1515    else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME ) )
1516    {
1517        tr_sessionSetTorrentDoneScript( tr, gtr_pref_string_get( key ) );
1518    }
1519    else if( !strcmp( key, TR_PREFS_KEY_START) )
1520    {
1521        tr_sessionSetPaused( tr, !gtr_pref_flag_get( key ) );
1522    }
1523    else if( !strcmp( key, TR_PREFS_KEY_TRASH_ORIGINAL ) )
1524    {
1525        tr_sessionSetDeleteSource( tr, gtr_pref_flag_get( key ) );
1526    }
1527}
1528
1529static gboolean
1530update_model( gpointer gdata )
1531{
1532    struct cbdata *data = gdata;
1533    const gboolean done = global_sigcount;
1534
1535    if( !done )
1536    {
1537        /* update the torrent data in the model */
1538        gtr_core_update( data->core );
1539
1540        /* refresh the main window's statusbar and toolbar buttons */
1541        if( data->wind != NULL )
1542            gtr_window_refresh( data->wind );
1543
1544        /* update the actions */
1545        refresh_actions( data );
1546
1547        /* update the status tray icon */
1548        if( data->icon != NULL )
1549            gtr_icon_refresh( data->icon );
1550    }
1551
1552    return !done;
1553}
1554
1555/* GTK+ versions before 2.18.0 don't have a default URI hook... */
1556#if !GTK_CHECK_VERSION(2,18,0)
1557 #define NEED_URL_HOOK
1558#endif
1559
1560#ifdef NEED_URL_HOOK
1561static void
1562on_uri_clicked( GtkAboutDialog * u UNUSED, const gchar * uri, gpointer u2 UNUSED )
1563{
1564    gtr_open_uri( uri );
1565}
1566#endif
1567
1568static void
1569show_about_dialog( GtkWindow * parent )
1570{
1571    GtkWidget * d;
1572    const char * website_uri = "http://www.transmissionbt.com/";
1573    const char * authors[] = {
1574        "Jordan Lee (Backend; GTK+)",
1575        "Mitchell Livingston (Backend; OS X)",
1576        NULL
1577    };
1578
1579#ifdef NEED_URL_HOOK
1580    gtk_about_dialog_set_url_hook( on_uri_clicked, NULL, NULL );
1581#endif
1582
1583    d = g_object_new( GTK_TYPE_ABOUT_DIALOG,
1584                      "authors", authors,
1585                      "comments", _( "A fast and easy BitTorrent client" ),
1586                      "copyright", _( "Copyright (c) The Transmission Project" ),
1587                      "logo-icon-name", MY_CONFIG_NAME,
1588                      "name", g_get_application_name( ),
1589                      /* Translators: translate "translator-credits" as your name
1590                         to have it appear in the credits in the "About"
1591                         dialog */
1592                      "translator-credits", _( "translator-credits" ),
1593                      "version", LONG_VERSION_STRING,
1594                      "website", website_uri,
1595                      "website-label", website_uri,
1596#ifdef SHOW_LICENSE
1597                      "license", LICENSE,
1598                      "wrap-license", TRUE,
1599#endif
1600                      NULL );
1601    gtk_window_set_transient_for( GTK_WINDOW( d ), parent );
1602    g_signal_connect_swapped( d, "response", G_CALLBACK (gtk_widget_destroy), d );
1603    gtk_widget_show( d );
1604}
1605
1606static void
1607append_id_to_benc_list( GtkTreeModel * m, GtkTreePath * path UNUSED,
1608                        GtkTreeIter * iter, gpointer list )
1609{
1610    tr_torrent * tor = NULL;
1611    gtk_tree_model_get( m, iter, MC_TORRENT, &tor, -1 );
1612    tr_bencListAddInt( list, tr_torrentId( tor ) );
1613}
1614
1615static gboolean
1616call_rpc_for_selected_torrents( struct cbdata * data, const char * method )
1617{
1618    tr_benc top, *args, *ids;
1619    gboolean invoked = FALSE;
1620    GtkTreeSelection * s = data->sel;
1621    tr_session * session = gtr_core_session( data->core );
1622
1623    tr_bencInitDict( &top, 2 );
1624    tr_bencDictAddStr( &top, "method", method );
1625    args = tr_bencDictAddDict( &top, "arguments", 1 );
1626    ids = tr_bencDictAddList( args, "ids", 0 );
1627    gtk_tree_selection_selected_foreach( s, append_id_to_benc_list, ids );
1628
1629    if( tr_bencListSize( ids ) != 0 )
1630    {
1631        int json_len;
1632        char * json = tr_bencToStr( &top, TR_FMT_JSON_LEAN, &json_len );
1633        tr_rpc_request_exec_json( session, json, json_len, NULL, NULL );
1634        g_free( json );
1635        invoked = TRUE;
1636    }
1637
1638    tr_bencFree( &top );
1639    return invoked;
1640}
1641
1642static void
1643open_folder_foreach( GtkTreeModel * model, GtkTreePath * path UNUSED,
1644                     GtkTreeIter * iter, gpointer core )
1645{
1646    int id;
1647    gtk_tree_model_get( model, iter, MC_TORRENT_ID, &id, -1 );
1648    gtr_core_open_folder( core, id );
1649}
1650
1651static gboolean
1652on_message_window_closed( void )
1653{
1654    gtr_action_set_toggled( "toggle-message-log", FALSE );
1655    return FALSE;
1656}
1657
1658static void
1659accumulate_selected_torrents( GtkTreeModel  * model, GtkTreePath   * path UNUSED,
1660                              GtkTreeIter   * iter, gpointer        gdata )
1661{
1662    int id;
1663    GSList ** data = gdata;
1664
1665    gtk_tree_model_get( model, iter, MC_TORRENT_ID, &id, -1 );
1666    *data = g_slist_append( *data, GINT_TO_POINTER( id ) );
1667}
1668
1669static void
1670remove_selected( struct cbdata * data, gboolean delete_files )
1671{
1672    GSList * l = NULL;
1673
1674    gtk_tree_selection_selected_foreach( data->sel, accumulate_selected_torrents, &l );
1675
1676    if( l != NULL )
1677        gtr_confirm_remove( data->wind, data->core, l, delete_files );
1678}
1679
1680static void
1681start_all_torrents( struct cbdata * data )
1682{
1683    tr_session * session = gtr_core_session( data->core );
1684    const char * cmd = "{ \"method\": \"torrent-start\" }";
1685    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1686}
1687
1688static void
1689pause_all_torrents( struct cbdata * data )
1690{
1691    tr_session * session = gtr_core_session( data->core );
1692    const char * cmd = "{ \"method\": \"torrent-stop\" }";
1693    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1694}
1695
1696static tr_torrent*
1697get_first_selected_torrent( struct cbdata * data )
1698{
1699    tr_torrent * tor = NULL;
1700    GtkTreeModel * m;
1701    GList * l = gtk_tree_selection_get_selected_rows( data->sel, &m );
1702    if( l != NULL ) {
1703        GtkTreePath * p = l->data;
1704        GtkTreeIter i;
1705        if( gtk_tree_model_get_iter( m, &i, p ) )
1706            gtk_tree_model_get( m, &i, MC_TORRENT, &tor, -1 );
1707    }
1708    g_list_foreach( l, (GFunc)gtk_tree_path_free, NULL );
1709    g_list_free( l );
1710    return tor;
1711}
1712
1713static void
1714copy_magnet_link_to_clipboard( GtkWidget * w, tr_torrent * tor )
1715{
1716    char * magnet = tr_torrentGetMagnetLink( tor );
1717    GdkDisplay * display = gtk_widget_get_display( w );
1718    GdkAtom selection;
1719    GtkClipboard * clipboard;
1720
1721    /* this is The Right Thing for copy/paste... */
1722    selection = GDK_SELECTION_CLIPBOARD;
1723    clipboard = gtk_clipboard_get_for_display( display, selection );
1724    gtk_clipboard_set_text( clipboard, magnet, -1 );
1725
1726    /* ...but people using plain ol' X need this instead */
1727    selection = GDK_SELECTION_PRIMARY;
1728    clipboard = gtk_clipboard_get_for_display( display, selection );
1729    gtk_clipboard_set_text( clipboard, magnet, -1 );
1730
1731    /* cleanup */
1732    tr_free( magnet );
1733}
1734
1735void
1736gtr_actions_handler( const char * action_name, gpointer user_data )
1737{
1738    struct cbdata * data = user_data;
1739    gboolean        changed = FALSE;
1740
1741    if( !strcmp( action_name, "open-torrent-from-url" ) )
1742    {
1743        GtkWidget * w = gtr_torrent_open_from_url_dialog_new( data->wind, data->core );
1744        gtk_widget_show( w );
1745    }
1746    else if(  !strcmp( action_name, "open-torrent-menu" )
1747      || !strcmp( action_name, "open-torrent-toolbar" ) )
1748    {
1749        GtkWidget * w = gtr_torrent_open_from_file_dialog_new( data->wind, data->core );
1750        gtk_widget_show( w );
1751    }
1752    else if( !strcmp( action_name, "show-stats" ) )
1753    {
1754        GtkWidget * dialog = gtr_stats_dialog_new( data->wind, data->core );
1755        gtk_widget_show( dialog );
1756    }
1757    else if( !strcmp( action_name, "donate" ) )
1758    {
1759        gtr_open_uri( "http://www.transmissionbt.com/donate.php" );
1760    }
1761    else if( !strcmp( action_name, "pause-all-torrents" ) )
1762    {
1763        pause_all_torrents( data );
1764    }
1765    else if( !strcmp( action_name, "start-all-torrents" ) )
1766    {
1767        start_all_torrents( data );
1768    }
1769    else if( !strcmp( action_name, "copy-magnet-link-to-clipboard" ) )
1770    {
1771        tr_torrent * tor = get_first_selected_torrent( data );
1772        if( tor != NULL )
1773        {
1774            copy_magnet_link_to_clipboard( GTK_WIDGET( data->wind ), tor );
1775        }
1776    }
1777    else if( !strcmp( action_name, "relocate-torrent" ) )
1778    {
1779        GSList * ids = getSelectedTorrentIds( data );
1780        if( ids != NULL )
1781        {
1782            GtkWindow * parent = data->wind;
1783            GtkWidget * w = gtr_relocate_dialog_new( parent, data->core, ids );
1784            gtk_widget_show( w );
1785        }
1786    }
1787    else if( !strcmp( action_name, "start-torrent" ) )
1788    {
1789        changed |= call_rpc_for_selected_torrents( data, "torrent-start" );
1790    }
1791    else if( !strcmp( action_name, "pause-torrent" ) )
1792    {
1793        changed |= call_rpc_for_selected_torrents( data, "torrent-stop" );
1794    }
1795    else if( !strcmp( action_name, "verify-torrent" ) )
1796    {
1797        changed |= call_rpc_for_selected_torrents( data, "torrent-verify" );
1798    }
1799    else if( !strcmp( action_name, "update-tracker" ) )
1800    {
1801        changed |= call_rpc_for_selected_torrents( data, "torrent-reannounce" );
1802    }
1803    else if( !strcmp( action_name, "open-torrent-folder" ) )
1804    {
1805        gtk_tree_selection_selected_foreach( data->sel, open_folder_foreach, data->core );
1806    }
1807    else if( !strcmp( action_name, "show-torrent-properties" ) )
1808    {
1809        show_details_dialog_for_selected_torrents( data );
1810    }
1811    else if( !strcmp( action_name, "new-torrent" ) )
1812    {
1813        GtkWidget * w = gtr_torrent_creation_dialog_new( data->wind, data->core );
1814        gtk_widget_show( w );
1815    }
1816    else if( !strcmp( action_name, "remove-torrent" ) )
1817    {
1818        remove_selected( data, FALSE );
1819    }
1820    else if( !strcmp( action_name, "delete-torrent" ) )
1821    {
1822        remove_selected( data, TRUE );
1823    }
1824    else if( !strcmp( action_name, "quit" ) )
1825    {
1826        on_app_exit( data );
1827    }
1828    else if( !strcmp( action_name, "select-all" ) )
1829    {
1830        gtk_tree_selection_select_all( data->sel );
1831    }
1832    else if( !strcmp( action_name, "deselect-all" ) )
1833    {
1834        gtk_tree_selection_unselect_all( data->sel );
1835    }
1836    else if( !strcmp( action_name, "edit-preferences" ) )
1837    {
1838        if( NULL == data->prefs )
1839        {
1840            data->prefs = gtr_prefs_dialog_new( data->wind, G_OBJECT( data->core ) );
1841            g_signal_connect( data->prefs, "destroy",
1842                              G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
1843        }
1844        gtr_window_present( GTK_WINDOW( data->prefs ) );
1845    }
1846    else if( !strcmp( action_name, "toggle-message-log" ) )
1847    {
1848        if( !data->msgwin )
1849        {
1850            GtkWidget * win = gtr_message_log_window_new( data->wind, data->core );
1851            g_signal_connect( win, "destroy", G_CALLBACK( on_message_window_closed ), NULL );
1852            data->msgwin = win;
1853        }
1854        else
1855        {
1856            gtr_action_set_toggled( "toggle-message-log", FALSE );
1857            gtk_widget_destroy( data->msgwin );
1858            data->msgwin = NULL;
1859        }
1860    }
1861    else if( !strcmp( action_name, "show-about-dialog" ) )
1862    {
1863        show_about_dialog( data->wind );
1864    }
1865    else if( !strcmp ( action_name, "help" ) )
1866    {
1867        gtr_open_uri( gtr_get_help_uri( ) );
1868    }
1869    else if( !strcmp( action_name, "toggle-main-window" ) )
1870    {
1871        toggleMainWindow( data );
1872    }
1873    else g_error ( "Unhandled action: %s", action_name );
1874
1875    if( changed )
1876        update_model( data );
1877}
Note: See TracBrowser for help on using the repository browser.