source: trunk/gtk/main.c @ 12355

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

(trunk gtk) more heap pruning:

querying gconf2 each time the curl callack function is called is expensive, so query it once -- then again later, if the proxy settings change -- and remember the values in a local struct.

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