source: trunk/gtk/main.c @ 12204

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

(trunk) #4138 "use stdbool.h instead of tr_bool" -- done.

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