source: trunk/gtk/main.c @ 12675

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

rename 'torrent_idle_data' as 'rpc_idle_data'

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