source: trunk/gtk/main.c @ 12672

Last change on this file since 12672 was 12672, checked in by jordan, 10 years ago

(trunk gtk) move function declarations to where they're needed

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