source: trunk/gtk/main.c @ 12234

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

(trunk) fix a handful of small memory leaks that valgrind found.

  • Property svn:keywords set to Date Rev Author Id
File size: 59.9 KB
Line 
1/******************************************************************************
2 * $Id: main.c 12234 2011-03-25 17:42:47Z 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#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( !tr_strcmp0( scheme, "socks4" ) )
650                    curl_easy_setopt( easy, CURLOPT_PROXYTYPE, (long)CURLPROXY_SOCKS4 );
651                else if( !tr_strcmp0( scheme, "socks5" ) )
652                    curl_easy_setopt( easy, CURLOPT_PROXYTYPE, (long)CURLPROXY_SOCKS5 );
653                else if( !tr_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( !tr_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( !tr_strcmp0( mode, "manual" ))
698        {
699            char * scheme = NULL;
700            if( !tr_urlParse( destination, strlen( destination ), &scheme, NULL, NULL, NULL ) )
701            {
702                if( !tr_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( !tr_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        g_free( filename );
1108    }
1109
1110    if( files )
1111        gtr_core_add_list_defaults( data->core, g_slist_reverse( files ), TRUE );
1112
1113    gtr_core_torrents_added( data->core );
1114    gtk_drag_finish( drag_context, success, FALSE, time_ );
1115
1116    /* cleanup */
1117    g_strfreev( uris );
1118}
1119
1120static void
1121main_window_setup( struct cbdata * cbdata, TrWindow * wind )
1122{
1123    GtkWidget * w;
1124    GtkTreeModel * model;
1125    GtkTreeSelection * sel;
1126
1127    g_assert( NULL == cbdata->wind );
1128    cbdata->wind = GTK_WINDOW( wind );
1129    cbdata->sel = sel = GTK_TREE_SELECTION( gtr_window_get_selection( cbdata->wind ) );
1130
1131    g_signal_connect( sel, "changed", G_CALLBACK( on_selection_changed ), cbdata );
1132    on_selection_changed( sel, cbdata );
1133    model = gtr_core_model( cbdata->core );
1134    g_signal_connect( model, "row-changed", G_CALLBACK( rowChangedCB ), cbdata );
1135    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
1136    refresh_actions( cbdata );
1137
1138    /* register to handle URIs that get dragged onto our main window */
1139    w = GTK_WIDGET( wind );
1140    gtk_drag_dest_set( w, GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY );
1141    gtk_drag_dest_add_uri_targets( w );
1142    g_signal_connect( w, "drag-data-received", G_CALLBACK(on_drag_data_received), cbdata );
1143}
1144
1145static gboolean
1146on_session_closed( gpointer gdata )
1147{
1148    struct cbdata * cbdata = gdata;
1149
1150    /* shutdown the gui */
1151    while( cbdata->details != NULL ) {
1152        struct DetailsDialogHandle * h = cbdata->details->data;
1153        gtk_widget_destroy( h->dialog );
1154    }
1155
1156    if( cbdata->prefs )
1157        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
1158    if( cbdata->wind )
1159        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
1160    g_object_unref( cbdata->core );
1161    if( cbdata->icon )
1162        g_object_unref( cbdata->icon );
1163    g_slist_foreach( cbdata->error_list, (GFunc)g_free, NULL );
1164    g_slist_free( cbdata->error_list );
1165    g_slist_foreach( cbdata->duplicates_list, (GFunc)g_free, NULL );
1166    g_slist_free( cbdata->duplicates_list );
1167    g_free( cbdata );
1168
1169    gtk_main_quit( );
1170
1171    return FALSE;
1172}
1173
1174static gpointer
1175session_close_threadfunc( gpointer gdata )
1176{
1177    /* since tr_sessionClose() is a blocking function,
1178     * call it from another thread... when it's done,
1179     * punt the GUI teardown back to the GTK+ thread */
1180    struct cbdata * cbdata = gdata;
1181    gdk_threads_enter( );
1182    gtr_core_close( cbdata->core );
1183    gtr_idle_add( on_session_closed, gdata );
1184    gdk_threads_leave( );
1185    return NULL;
1186}
1187
1188static void
1189exit_now_cb( GtkWidget *w UNUSED, gpointer data UNUSED )
1190{
1191    exit( 0 );
1192}
1193
1194static void
1195on_app_exit( gpointer vdata )
1196{
1197    GtkWidget *r, *p, *b, *w, *c;
1198    struct cbdata *cbdata = vdata;
1199
1200    /* stop the update timer */
1201    if( cbdata->timer )
1202    {
1203        g_source_remove( cbdata->timer );
1204        cbdata->timer = 0;
1205    }
1206
1207    c = GTK_WIDGET( cbdata->wind );
1208    gtk_container_remove( GTK_CONTAINER( c ), gtk_bin_get_child( GTK_BIN( c ) ) );
1209
1210    r = gtk_alignment_new( 0.5, 0.5, 0.01, 0.01 );
1211    gtk_container_add( GTK_CONTAINER( c ), r );
1212
1213    p = gtk_table_new( 3, 2, FALSE );
1214    gtk_table_set_col_spacings( GTK_TABLE( p ), GUI_PAD_BIG );
1215    gtk_container_add( GTK_CONTAINER( r ), p );
1216
1217    w = gtk_image_new_from_stock( GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG );
1218    gtk_table_attach_defaults( GTK_TABLE( p ), w, 0, 1, 0, 2 );
1219
1220    w = gtk_label_new( NULL );
1221    gtk_label_set_markup( GTK_LABEL( w ), _( "<b>Closing Connections</b>" ) );
1222    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
1223    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 0, 1 );
1224
1225    w = gtk_label_new( _( "Sending upload/download totals to tracker..." ) );
1226    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
1227    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 1, 2 );
1228
1229    b = gtk_alignment_new( 0.0, 1.0, 0.01, 0.01 );
1230    w = gtk_button_new_with_mnemonic( _( "_Quit Now" ) );
1231    g_signal_connect( w, "clicked", G_CALLBACK( exit_now_cb ), NULL );
1232    gtk_container_add( GTK_CONTAINER( b ), w );
1233    gtk_table_attach( GTK_TABLE( p ), b, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 10 );
1234
1235    gtk_widget_show_all( r );
1236    gtk_widget_grab_focus( w );
1237
1238    /* clear the UI */
1239    gtr_core_clear( cbdata->core );
1240
1241    /* ensure the window is in its previous position & size.
1242     * this seems to be necessary because changing the main window's
1243     * child seems to unset the size */
1244    gtk_window_resize( cbdata->wind, gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_WIDTH ),
1245                                     gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_HEIGHT ) );
1246    gtk_window_move( cbdata->wind, gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_X ),
1247                                   gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_Y ) );
1248
1249    /* shut down libT */
1250    g_thread_create( session_close_threadfunc, vdata, TRUE, NULL );
1251}
1252
1253static void
1254show_torrent_errors( GtkWindow * window, const char * primary, GSList ** files )
1255{
1256    GSList * l;
1257    GtkWidget * w;
1258    GString * s = g_string_new( NULL );
1259    const char * leader = g_slist_length( *files ) > 1
1260                        ? gtr_get_unicode_string( GTR_UNICODE_BULLET )
1261                        : "";
1262
1263    for( l=*files; l!=NULL; l=l->next )
1264        g_string_append_printf( s, "%s %s\n", leader, (const char*)l->data );
1265
1266    w = gtk_message_dialog_new( window,
1267                                GTK_DIALOG_DESTROY_WITH_PARENT,
1268                                GTK_MESSAGE_ERROR,
1269                                GTK_BUTTONS_CLOSE,
1270                                "%s", primary );
1271    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
1272                                              "%s", s->str );
1273    g_signal_connect_swapped( w, "response",
1274                              G_CALLBACK( gtk_widget_destroy ), w );
1275    gtk_widget_show( w );
1276    g_string_free( s, TRUE );
1277
1278    g_slist_foreach( *files, (GFunc)g_free, NULL );
1279    g_slist_free( *files );
1280    *files = NULL;
1281}
1282
1283static void
1284flush_torrent_errors( struct cbdata * cbdata )
1285{
1286    if( cbdata->error_list )
1287        show_torrent_errors( cbdata->wind,
1288                              gtr_ngettext( "Couldn't add corrupt torrent",
1289                                            "Couldn't add corrupt torrents",
1290                                            g_slist_length( cbdata->error_list ) ),
1291                              &cbdata->error_list );
1292
1293    if( cbdata->duplicates_list )
1294        show_torrent_errors( cbdata->wind,
1295                              gtr_ngettext( "Couldn't add duplicate torrent",
1296                                            "Couldn't add duplicate torrents",
1297                                            g_slist_length( cbdata->duplicates_list ) ),
1298                              &cbdata->duplicates_list );
1299}
1300
1301static void
1302on_core_error( TrCore * core UNUSED, guint code, const char * msg, struct cbdata * c )
1303{
1304    switch( code )
1305    {
1306        case TR_PARSE_ERR:
1307            c->error_list =
1308                g_slist_append( c->error_list, g_path_get_basename( msg ) );
1309            break;
1310
1311        case TR_PARSE_DUPLICATE:
1312            c->duplicates_list = g_slist_append( c->duplicates_list, g_strdup( msg ) );
1313            break;
1314
1315        case TR_CORE_ERR_NO_MORE_TORRENTS:
1316            flush_torrent_errors( c );
1317            break;
1318
1319        default:
1320            g_assert_not_reached( );
1321            break;
1322    }
1323}
1324
1325#if GTK_CHECK_VERSION( 2, 8, 0 )
1326static gboolean
1327on_main_window_focus_in( GtkWidget      * widget UNUSED,
1328                         GdkEventFocus  * event  UNUSED,
1329                         gpointer                gdata )
1330{
1331    struct cbdata * cbdata = gdata;
1332
1333    if( cbdata->wind )
1334        gtk_window_set_urgency_hint( cbdata->wind, FALSE );
1335    return FALSE;
1336}
1337
1338#endif
1339
1340static void
1341on_add_torrent( TrCore * core, tr_ctor * ctor, gpointer gdata )
1342{
1343    struct cbdata * cbdata = gdata;
1344    GtkWidget * w = gtr_torrent_options_dialog_new( cbdata->wind, core, ctor );
1345
1346#if GTK_CHECK_VERSION( 2, 8, 0 )
1347    g_signal_connect( w, "focus-in-event",
1348                      G_CALLBACK( on_main_window_focus_in ),  cbdata );
1349    if( cbdata->wind )
1350        gtk_window_set_urgency_hint( cbdata->wind, TRUE );
1351#endif
1352
1353    gtk_widget_show( w );
1354}
1355
1356static void
1357on_prefs_changed( TrCore * core UNUSED, const char * key, gpointer data )
1358{
1359    struct cbdata * cbdata = data;
1360    tr_session * tr = gtr_core_session( cbdata->core );
1361
1362    if( !strcmp( key, TR_PREFS_KEY_ENCRYPTION ) )
1363    {
1364        tr_sessionSetEncryption( tr, gtr_pref_int_get( key ) );
1365    }
1366    else if( !strcmp( key, TR_PREFS_KEY_DOWNLOAD_DIR ) )
1367    {
1368        tr_sessionSetDownloadDir( tr, gtr_pref_string_get( key ) );
1369    }
1370    else if( !strcmp( key, TR_PREFS_KEY_MSGLEVEL ) )
1371    {
1372        tr_setMessageLevel( gtr_pref_int_get( key ) );
1373    }
1374    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT ) )
1375    {
1376        tr_sessionSetPeerPort( tr, gtr_pref_int_get( key ) );
1377    }
1378    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_ENABLED ) )
1379    {
1380        tr_blocklistSetEnabled( tr, gtr_pref_flag_get( key ) );
1381    }
1382    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_URL ) )
1383    {
1384        tr_blocklistSetURL( tr, gtr_pref_string_get( key ) );
1385    }
1386    else if( !strcmp( key, PREF_KEY_SHOW_TRAY_ICON ) )
1387    {
1388        const int show = gtr_pref_flag_get( key );
1389        if( show && !cbdata->icon )
1390            cbdata->icon = gtr_icon_new( cbdata->core );
1391        else if( !show && cbdata->icon ) {
1392            g_object_unref( cbdata->icon );
1393            cbdata->icon = NULL;
1394        }
1395    }
1396    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_ENABLED ) )
1397    {
1398        tr_sessionLimitSpeed( tr, TR_DOWN, gtr_pref_flag_get( key ) );
1399    }
1400    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_KBps ) )
1401    {
1402        tr_sessionSetSpeedLimit_KBps( tr, TR_DOWN, gtr_pref_int_get( key ) );
1403    }
1404    else if( !strcmp( key, TR_PREFS_KEY_USPEED_ENABLED ) )
1405    {
1406        tr_sessionLimitSpeed( tr, TR_UP, gtr_pref_flag_get( key ) );
1407    }
1408    else if( !strcmp( key, TR_PREFS_KEY_USPEED_KBps ) )
1409    {
1410        tr_sessionSetSpeedLimit_KBps( tr, TR_UP, gtr_pref_int_get( key ) );
1411    }
1412    else if( !strcmp( key, TR_PREFS_KEY_RATIO_ENABLED ) )
1413    {
1414        tr_sessionSetRatioLimited( tr, gtr_pref_flag_get( key ) );
1415    }
1416    else if( !strcmp( key, TR_PREFS_KEY_RATIO ) )
1417    {
1418        tr_sessionSetRatioLimit( tr, gtr_pref_double_get( key ) );
1419    }
1420    else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT ) )
1421    {
1422        tr_sessionSetIdleLimit( tr, gtr_pref_int_get( key ) );
1423    }
1424    else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT_ENABLED ) )
1425    {
1426        tr_sessionSetIdleLimited( tr, gtr_pref_flag_get( key ) );
1427    }
1428    else if( !strcmp( key, TR_PREFS_KEY_PORT_FORWARDING ) )
1429    {
1430        tr_sessionSetPortForwardingEnabled( tr, gtr_pref_flag_get( key ) );
1431    }
1432    else if( !strcmp( key, TR_PREFS_KEY_PEX_ENABLED ) )
1433    {
1434        tr_sessionSetPexEnabled( tr, gtr_pref_flag_get( key ) );
1435    }
1436    else if( !strcmp( key, TR_PREFS_KEY_RENAME_PARTIAL_FILES ) )
1437    {
1438        tr_sessionSetIncompleteFileNamingEnabled( tr, gtr_pref_flag_get( key ) );
1439    }
1440    else if( !strcmp( key, TR_PREFS_KEY_DHT_ENABLED ) )
1441    {
1442        tr_sessionSetDHTEnabled( tr, gtr_pref_flag_get( key ) );
1443    }
1444    else if( !strcmp( key, TR_PREFS_KEY_UTP_ENABLED ) )
1445    {
1446        tr_sessionSetUTPEnabled( tr, gtr_pref_flag_get( key ) );
1447    }
1448    else if( !strcmp( key, TR_PREFS_KEY_LPD_ENABLED ) )
1449    {
1450        tr_sessionSetLPDEnabled( tr, gtr_pref_flag_get( key ) );
1451    }
1452    else if( !strcmp( key, TR_PREFS_KEY_RPC_PORT ) )
1453    {
1454        tr_sessionSetRPCPort( tr, gtr_pref_int_get( key ) );
1455    }
1456    else if( !strcmp( key, TR_PREFS_KEY_RPC_ENABLED ) )
1457    {
1458        tr_sessionSetRPCEnabled( tr, gtr_pref_flag_get( key ) );
1459    }
1460    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST ) )
1461    {
1462        tr_sessionSetRPCWhitelist( tr, gtr_pref_string_get( key ) );
1463    }
1464    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST_ENABLED ) )
1465    {
1466        tr_sessionSetRPCWhitelistEnabled( tr, gtr_pref_flag_get( key ) );
1467    }
1468    else if( !strcmp( key, TR_PREFS_KEY_RPC_USERNAME ) )
1469    {
1470        tr_sessionSetRPCUsername( tr, gtr_pref_string_get( key ) );
1471    }
1472    else if( !strcmp( key, TR_PREFS_KEY_RPC_PASSWORD ) )
1473    {
1474        tr_sessionSetRPCPassword( tr, gtr_pref_string_get( key ) );
1475    }
1476    else if( !strcmp( key, TR_PREFS_KEY_RPC_AUTH_REQUIRED ) )
1477    {
1478        tr_sessionSetRPCPasswordEnabled( tr, gtr_pref_flag_get( key ) );
1479    }
1480    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_UP_KBps ) )
1481    {
1482        tr_sessionSetAltSpeed_KBps( tr, TR_UP, gtr_pref_int_get( key ) );
1483    }
1484    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps ) )
1485    {
1486        tr_sessionSetAltSpeed_KBps( tr, TR_DOWN, gtr_pref_int_get( key ) );
1487    }
1488    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_ENABLED ) )
1489    {
1490        const gboolean b = gtr_pref_flag_get( key );
1491        tr_sessionUseAltSpeed( tr, b );
1492        gtr_action_set_toggled( key, b );
1493    }
1494    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN ) )
1495    {
1496        tr_sessionSetAltSpeedBegin( tr, gtr_pref_int_get( key ) );
1497    }
1498    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_END ) )
1499    {
1500        tr_sessionSetAltSpeedEnd( tr, gtr_pref_int_get( key ) );
1501    }
1502    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED ) )
1503    {
1504        tr_sessionUseAltSpeedTime( tr, gtr_pref_flag_get( key ) );
1505    }
1506    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_DAY ) )
1507    {
1508        tr_sessionSetAltSpeedDay( tr, gtr_pref_int_get( key ) );
1509    }
1510    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START ) )
1511    {
1512        tr_sessionSetPeerPortRandomOnStart( tr, gtr_pref_flag_get( key ) );
1513    }
1514    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR ) )
1515    {
1516        tr_sessionSetIncompleteDir( tr, gtr_pref_string_get( key ) );
1517    }
1518    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED ) )
1519    {
1520        tr_sessionSetIncompleteDirEnabled( tr, gtr_pref_flag_get( key ) );
1521    }
1522    else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED ) )
1523    {
1524        tr_sessionSetTorrentDoneScriptEnabled( tr, gtr_pref_flag_get( key ) );
1525    }
1526    else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME ) )
1527    {
1528        tr_sessionSetTorrentDoneScript( tr, gtr_pref_string_get( key ) );
1529    }
1530    else if( !strcmp( key, TR_PREFS_KEY_START) )
1531    {
1532        tr_sessionSetPaused( tr, !gtr_pref_flag_get( key ) );
1533    }
1534    else if( !strcmp( key, TR_PREFS_KEY_TRASH_ORIGINAL ) )
1535    {
1536        tr_sessionSetDeleteSource( tr, gtr_pref_flag_get( key ) );
1537    }
1538}
1539
1540static gboolean
1541update_model( gpointer gdata )
1542{
1543    struct cbdata *data = gdata;
1544    const gboolean done = global_sigcount;
1545
1546    if( !done )
1547    {
1548        /* update the torrent data in the model */
1549        gtr_core_update( data->core );
1550
1551        /* refresh the main window's statusbar and toolbar buttons */
1552        if( data->wind != NULL )
1553            gtr_window_refresh( data->wind );
1554
1555        /* update the actions */
1556        refresh_actions( data );
1557
1558        /* update the status tray icon */
1559        if( data->icon != NULL )
1560            gtr_icon_refresh( data->icon );
1561    }
1562
1563    return !done;
1564}
1565
1566/* GTK+ versions before 2.18.0 don't have a default URI hook... */
1567#if !GTK_CHECK_VERSION(2,18,0)
1568 #define NEED_URL_HOOK
1569#endif
1570
1571#ifdef NEED_URL_HOOK
1572static void
1573on_uri_clicked( GtkAboutDialog * u UNUSED, const gchar * uri, gpointer u2 UNUSED )
1574{
1575    gtr_open_uri( uri );
1576}
1577#endif
1578
1579static void
1580show_about_dialog( GtkWindow * parent )
1581{
1582    GtkWidget * d;
1583    const char * website_uri = "http://www.transmissionbt.com/";
1584    const char * authors[] = {
1585        "Jordan Lee (Backend; GTK+)",
1586        "Mitchell Livingston (Backend; OS X)",
1587        NULL
1588    };
1589
1590#ifdef NEED_URL_HOOK
1591    gtk_about_dialog_set_url_hook( on_uri_clicked, NULL, NULL );
1592#endif
1593
1594    d = g_object_new( GTK_TYPE_ABOUT_DIALOG,
1595                      "authors", authors,
1596                      "comments", _( "A fast and easy BitTorrent client" ),
1597                      "copyright", _( "Copyright (c) The Transmission Project" ),
1598                      "logo-icon-name", MY_CONFIG_NAME,
1599                      "name", g_get_application_name( ),
1600                      /* Translators: translate "translator-credits" as your name
1601                         to have it appear in the credits in the "About"
1602                         dialog */
1603                      "translator-credits", _( "translator-credits" ),
1604                      "version", LONG_VERSION_STRING,
1605                      "website", website_uri,
1606                      "website-label", website_uri,
1607#ifdef SHOW_LICENSE
1608                      "license", LICENSE,
1609                      "wrap-license", TRUE,
1610#endif
1611                      NULL );
1612    gtk_window_set_transient_for( GTK_WINDOW( d ), parent );
1613    g_signal_connect_swapped( d, "response", G_CALLBACK (gtk_widget_destroy), d );
1614    gtk_widget_show( d );
1615}
1616
1617static void
1618append_id_to_benc_list( GtkTreeModel * m, GtkTreePath * path UNUSED,
1619                        GtkTreeIter * iter, gpointer list )
1620{
1621    tr_torrent * tor = NULL;
1622    gtk_tree_model_get( m, iter, MC_TORRENT, &tor, -1 );
1623    tr_bencListAddInt( list, tr_torrentId( tor ) );
1624}
1625
1626static gboolean
1627call_rpc_for_selected_torrents( struct cbdata * data, const char * method )
1628{
1629    tr_benc top, *args, *ids;
1630    gboolean invoked = FALSE;
1631    GtkTreeSelection * s = data->sel;
1632    tr_session * session = gtr_core_session( data->core );
1633
1634    tr_bencInitDict( &top, 2 );
1635    tr_bencDictAddStr( &top, "method", method );
1636    args = tr_bencDictAddDict( &top, "arguments", 1 );
1637    ids = tr_bencDictAddList( args, "ids", 0 );
1638    gtk_tree_selection_selected_foreach( s, append_id_to_benc_list, ids );
1639
1640    if( tr_bencListSize( ids ) != 0 )
1641    {
1642        int json_len;
1643        char * json = tr_bencToStr( &top, TR_FMT_JSON_LEAN, &json_len );
1644        tr_rpc_request_exec_json( session, json, json_len, NULL, NULL );
1645        g_free( json );
1646        invoked = TRUE;
1647    }
1648
1649    tr_bencFree( &top );
1650    return invoked;
1651}
1652
1653static void
1654open_folder_foreach( GtkTreeModel * model, GtkTreePath * path UNUSED,
1655                     GtkTreeIter * iter, gpointer core )
1656{
1657    int id;
1658    gtk_tree_model_get( model, iter, MC_TORRENT_ID, &id, -1 );
1659    gtr_core_open_folder( core, id );
1660}
1661
1662static gboolean
1663on_message_window_closed( void )
1664{
1665    gtr_action_set_toggled( "toggle-message-log", FALSE );
1666    return FALSE;
1667}
1668
1669static void
1670accumulate_selected_torrents( GtkTreeModel  * model, GtkTreePath   * path UNUSED,
1671                              GtkTreeIter   * iter, gpointer        gdata )
1672{
1673    int id;
1674    GSList ** data = gdata;
1675
1676    gtk_tree_model_get( model, iter, MC_TORRENT_ID, &id, -1 );
1677    *data = g_slist_append( *data, GINT_TO_POINTER( id ) );
1678}
1679
1680static void
1681remove_selected( struct cbdata * data, gboolean delete_files )
1682{
1683    GSList * l = NULL;
1684
1685    gtk_tree_selection_selected_foreach( data->sel, accumulate_selected_torrents, &l );
1686
1687    if( l != NULL )
1688        gtr_confirm_remove( data->wind, data->core, l, delete_files );
1689}
1690
1691static void
1692start_all_torrents( struct cbdata * data )
1693{
1694    tr_session * session = gtr_core_session( data->core );
1695    const char * cmd = "{ \"method\": \"torrent-start\" }";
1696    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1697}
1698
1699static void
1700pause_all_torrents( struct cbdata * data )
1701{
1702    tr_session * session = gtr_core_session( data->core );
1703    const char * cmd = "{ \"method\": \"torrent-stop\" }";
1704    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1705}
1706
1707static tr_torrent*
1708get_first_selected_torrent( struct cbdata * data )
1709{
1710    tr_torrent * tor = NULL;
1711    GtkTreeModel * m;
1712    GList * l = gtk_tree_selection_get_selected_rows( data->sel, &m );
1713    if( l != NULL ) {
1714        GtkTreePath * p = l->data;
1715        GtkTreeIter i;
1716        if( gtk_tree_model_get_iter( m, &i, p ) )
1717            gtk_tree_model_get( m, &i, MC_TORRENT, &tor, -1 );
1718    }
1719    g_list_foreach( l, (GFunc)gtk_tree_path_free, NULL );
1720    g_list_free( l );
1721    return tor;
1722}
1723
1724static void
1725copy_magnet_link_to_clipboard( GtkWidget * w, tr_torrent * tor )
1726{
1727    char * magnet = tr_torrentGetMagnetLink( tor );
1728    GdkDisplay * display = gtk_widget_get_display( w );
1729    GdkAtom selection;
1730    GtkClipboard * clipboard;
1731
1732    /* this is The Right Thing for copy/paste... */
1733    selection = GDK_SELECTION_CLIPBOARD;
1734    clipboard = gtk_clipboard_get_for_display( display, selection );
1735    gtk_clipboard_set_text( clipboard, magnet, -1 );
1736
1737    /* ...but people using plain ol' X need this instead */
1738    selection = GDK_SELECTION_PRIMARY;
1739    clipboard = gtk_clipboard_get_for_display( display, selection );
1740    gtk_clipboard_set_text( clipboard, magnet, -1 );
1741
1742    /* cleanup */
1743    tr_free( magnet );
1744}
1745
1746void
1747gtr_actions_handler( const char * action_name, gpointer user_data )
1748{
1749    struct cbdata * data = user_data;
1750    gboolean        changed = FALSE;
1751
1752    if( !strcmp( action_name, "open-torrent-from-url" ) )
1753    {
1754        GtkWidget * w = gtr_torrent_open_from_url_dialog_new( data->wind, data->core );
1755        gtk_widget_show( w );
1756    }
1757    else if(  !strcmp( action_name, "open-torrent-menu" )
1758      || !strcmp( action_name, "open-torrent-toolbar" ) )
1759    {
1760        GtkWidget * w = gtr_torrent_open_from_file_dialog_new( data->wind, data->core );
1761        gtk_widget_show( w );
1762    }
1763    else if( !strcmp( action_name, "show-stats" ) )
1764    {
1765        GtkWidget * dialog = gtr_stats_dialog_new( data->wind, data->core );
1766        gtk_widget_show( dialog );
1767    }
1768    else if( !strcmp( action_name, "donate" ) )
1769    {
1770        gtr_open_uri( "http://www.transmissionbt.com/donate.php" );
1771    }
1772    else if( !strcmp( action_name, "pause-all-torrents" ) )
1773    {
1774        pause_all_torrents( data );
1775    }
1776    else if( !strcmp( action_name, "start-all-torrents" ) )
1777    {
1778        start_all_torrents( data );
1779    }
1780    else if( !strcmp( action_name, "copy-magnet-link-to-clipboard" ) )
1781    {
1782        tr_torrent * tor = get_first_selected_torrent( data );
1783        if( tor != NULL )
1784        {
1785            copy_magnet_link_to_clipboard( GTK_WIDGET( data->wind ), tor );
1786        }
1787    }
1788    else if( !strcmp( action_name, "relocate-torrent" ) )
1789    {
1790        GSList * ids = getSelectedTorrentIds( data );
1791        if( ids != NULL )
1792        {
1793            GtkWindow * parent = data->wind;
1794            GtkWidget * w = gtr_relocate_dialog_new( parent, data->core, ids );
1795            gtk_widget_show( w );
1796        }
1797    }
1798    else if( !strcmp( action_name, "start-torrent" ) )
1799    {
1800        changed |= call_rpc_for_selected_torrents( data, "torrent-start" );
1801    }
1802    else if( !strcmp( action_name, "pause-torrent" ) )
1803    {
1804        changed |= call_rpc_for_selected_torrents( data, "torrent-stop" );
1805    }
1806    else if( !strcmp( action_name, "verify-torrent" ) )
1807    {
1808        changed |= call_rpc_for_selected_torrents( data, "torrent-verify" );
1809    }
1810    else if( !strcmp( action_name, "update-tracker" ) )
1811    {
1812        changed |= call_rpc_for_selected_torrents( data, "torrent-reannounce" );
1813    }
1814    else if( !strcmp( action_name, "open-torrent-folder" ) )
1815    {
1816        gtk_tree_selection_selected_foreach( data->sel, open_folder_foreach, data->core );
1817    }
1818    else if( !strcmp( action_name, "show-torrent-properties" ) )
1819    {
1820        show_details_dialog_for_selected_torrents( data );
1821    }
1822    else if( !strcmp( action_name, "new-torrent" ) )
1823    {
1824        GtkWidget * w = gtr_torrent_creation_dialog_new( data->wind, data->core );
1825        gtk_widget_show( w );
1826    }
1827    else if( !strcmp( action_name, "remove-torrent" ) )
1828    {
1829        remove_selected( data, FALSE );
1830    }
1831    else if( !strcmp( action_name, "delete-torrent" ) )
1832    {
1833        remove_selected( data, TRUE );
1834    }
1835    else if( !strcmp( action_name, "quit" ) )
1836    {
1837        on_app_exit( data );
1838    }
1839    else if( !strcmp( action_name, "select-all" ) )
1840    {
1841        gtk_tree_selection_select_all( data->sel );
1842    }
1843    else if( !strcmp( action_name, "deselect-all" ) )
1844    {
1845        gtk_tree_selection_unselect_all( data->sel );
1846    }
1847    else if( !strcmp( action_name, "edit-preferences" ) )
1848    {
1849        if( NULL == data->prefs )
1850        {
1851            data->prefs = gtr_prefs_dialog_new( data->wind, G_OBJECT( data->core ) );
1852            g_signal_connect( data->prefs, "destroy",
1853                              G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
1854        }
1855        gtr_window_present( GTK_WINDOW( data->prefs ) );
1856    }
1857    else if( !strcmp( action_name, "toggle-message-log" ) )
1858    {
1859        if( !data->msgwin )
1860        {
1861            GtkWidget * win = gtr_message_log_window_new( data->wind, data->core );
1862            g_signal_connect( win, "destroy", G_CALLBACK( on_message_window_closed ), NULL );
1863            data->msgwin = win;
1864        }
1865        else
1866        {
1867            gtr_action_set_toggled( "toggle-message-log", FALSE );
1868            gtk_widget_destroy( data->msgwin );
1869            data->msgwin = NULL;
1870        }
1871    }
1872    else if( !strcmp( action_name, "show-about-dialog" ) )
1873    {
1874        show_about_dialog( data->wind );
1875    }
1876    else if( !strcmp ( action_name, "help" ) )
1877    {
1878        gtr_open_uri( gtr_get_help_uri( ) );
1879    }
1880    else if( !strcmp( action_name, "toggle-main-window" ) )
1881    {
1882        toggleMainWindow( data );
1883    }
1884    else g_error ( "Unhandled action: %s", action_name );
1885
1886    if( changed )
1887        update_model( data );
1888}
Note: See TracBrowser for help on using the repository browser.