source: trunk/gtk/main.c @ 12670

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

(trunk gtk) remove unnecessary #includes

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