source: trunk/gtk/tr-prefs.c @ 6162

Last change on this file since 6162 was 6162, checked in by charles, 14 years ago

unify the daemon and gtk client's config files so that you can easily swap back and forth between clients and keep the same torrents and preferences.

  • Property svn:keywords set to Date Rev Author Id
File size: 38.2 KB
Line 
1/*
2 * This file Copyright (C) 2007-2008 Charles Kerr <charles@rebelbase.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: tr-prefs.c 6162 2008-06-12 16:25:36Z charles $
11 */
12
13#include <ctype.h> /* isspace */
14#include <errno.h>
15#include <stdarg.h>
16#include <stdlib.h> /* free() */
17#include <unistd.h>
18#include <glib/gi18n.h>
19#include <gtk/gtk.h>
20#include <libtransmission/transmission.h>
21#include <libtransmission/utils.h>
22#include <libtransmission/version.h>
23#include <libtransmission/web.h>
24#include "conf.h"
25#include "hig.h"
26#include "tr-core.h"
27#include "tr-prefs.h"
28#include "util.h"
29
30/**
31 * This is where we initialize the preferences file with the default values.
32 * If you add a new preferences key, you /must/ add a default value here.
33 */
34void
35tr_prefs_init_global( void )
36{
37    int i;
38    char pw[32];
39    const char * str;
40    const char * pool = "abcdefghijklmnopqrstuvwxyz"
41                        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
42                        "1234567890";
43
44    cf_check_older_configs( );
45
46#if HAVE_GIO
47    str = NULL;
48    if( !str ) str = g_get_user_special_dir( G_USER_DIRECTORY_DESKTOP );
49    if( !str ) str = g_get_home_dir( );
50    pref_string_set_default ( PREF_KEY_DIR_WATCH, str );
51    pref_flag_set_default   ( PREF_KEY_DIR_WATCH_ENABLED, FALSE );
52#endif
53
54    pref_int_set_default    ( PREF_KEY_PEER_SOCKET_TOS, TR_DEFAULT_PEER_SOCKET_TOS );
55    pref_flag_set_default   ( PREF_KEY_ALLOW_HIBERNATION, TRUE );
56    pref_flag_set_default   ( PREF_KEY_BLOCKLIST_ENABLED, TR_DEFAULT_BLOCKLIST_ENABLED );
57
58    pref_string_set_default ( PREF_KEY_OPEN_DIALOG_FOLDER, g_get_home_dir( ) );
59
60    pref_int_set_default    ( PREF_KEY_MAX_PEERS_GLOBAL, TR_DEFAULT_GLOBAL_PEER_LIMIT );
61    pref_int_set_default    ( PREF_KEY_MAX_PEERS_PER_TORRENT, 50 );
62
63    pref_flag_set_default   ( PREF_KEY_TOOLBAR, TRUE );
64    pref_flag_set_default   ( PREF_KEY_FILTERBAR, TRUE );
65    pref_flag_set_default   ( PREF_KEY_STATUSBAR, TRUE );
66    pref_flag_set_default   ( PREF_KEY_SHOW_TRAY_ICON, FALSE );
67    pref_string_set_default ( PREF_KEY_STATUSBAR_STATS, "total-ratio" );
68
69    pref_flag_set_default   ( PREF_KEY_DL_LIMIT_ENABLED, FALSE );
70    pref_int_set_default    ( PREF_KEY_DL_LIMIT, 100 );
71    pref_flag_set_default   ( PREF_KEY_UL_LIMIT_ENABLED, FALSE );
72    pref_int_set_default    ( PREF_KEY_UL_LIMIT, 50 );
73    pref_flag_set_default   ( PREF_KEY_OPTIONS_PROMPT, TRUE );
74
75    pref_int_set_default    ( PREF_KEY_MAIN_WINDOW_HEIGHT, 500 );
76    pref_int_set_default    ( PREF_KEY_MAIN_WINDOW_WIDTH, 300 );
77    pref_int_set_default    ( PREF_KEY_MAIN_WINDOW_X, 50 );
78    pref_int_set_default    ( PREF_KEY_MAIN_WINDOW_Y, 50 );
79
80    pref_string_set_default ( PREF_KEY_PROXY_SERVER, "" );
81    pref_int_set_default    ( PREF_KEY_PROXY_TYPE, TR_PROXY_HTTP );
82    pref_flag_set_default   ( PREF_KEY_PROXY_SERVER_ENABLED, FALSE );
83    pref_flag_set_default   ( PREF_KEY_PROXY_AUTH_ENABLED, FALSE );
84    pref_string_set_default ( PREF_KEY_PROXY_USERNAME, "" );
85    pref_string_set_default ( PREF_KEY_PROXY_PASSWORD, "" );
86
87    str = NULL;
88#if GLIB_CHECK_VERSION(2,14,0)
89    if( !str ) str = g_get_user_special_dir( G_USER_DIRECTORY_DOWNLOAD );
90#endif
91    if( !str ) str = g_get_home_dir( );
92    pref_string_set_default ( PREF_KEY_DOWNLOAD_DIR, str );
93
94    pref_int_set_default    ( PREF_KEY_PORT, TR_DEFAULT_PORT );
95
96    pref_flag_set_default   ( PREF_KEY_PORT_FORWARDING, TRUE );
97    pref_flag_set_default   ( PREF_KEY_PEX, TR_DEFAULT_PEX_ENABLED );
98    pref_flag_set_default   ( PREF_KEY_ASKQUIT, TRUE );
99    pref_flag_set_default   ( PREF_KEY_ENCRYPTION, TR_ENCRYPTION_PREFERRED );
100
101    pref_int_set_default    ( PREF_KEY_MSGLEVEL, TR_MSG_INF );
102
103    pref_string_set_default ( PREF_KEY_SORT_MODE, "sort-by-name" );
104    pref_flag_set_default   ( PREF_KEY_SORT_REVERSED, FALSE );
105    pref_flag_set_default   ( PREF_KEY_MINIMAL_VIEW, FALSE );
106
107    pref_flag_set_default   ( PREF_KEY_START, TRUE );
108    pref_flag_set_default   ( PREF_KEY_TRASH_ORIGINAL, FALSE );
109
110    pref_flag_set_default   ( PREF_KEY_RPC_ENABLED, TR_DEFAULT_RPC_ENABLED );
111    pref_int_set_default    ( PREF_KEY_RPC_PORT, TR_DEFAULT_RPC_PORT );
112    pref_string_set_default ( PREF_KEY_RPC_ACL, TR_DEFAULT_RPC_ACL );
113
114    for( i=0; i<16; ++i )
115        pw[i] = pool[ tr_rand( strlen( pool ) ) ];
116    pw[16] = '\0';
117    pref_string_set_default( PREF_KEY_RPC_USERNAME, "transmission" );
118    pref_string_set_default( PREF_KEY_RPC_PASSWORD, pw );
119    pref_flag_set_default  ( PREF_KEY_RPC_AUTH_ENABLED, FALSE );
120
121    pref_save( );
122}
123
124/**
125***
126**/
127
128#define PREF_KEY "pref-key"
129
130static void
131response_cb( GtkDialog * dialog, int response, gpointer unused UNUSED )
132{
133    if( response == GTK_RESPONSE_HELP ) {
134        char * base = gtr_get_help_url( );
135        char * url = g_strdup_printf( "%s/html/preferences.html", base );
136        gtr_open_file( url );
137        g_free( url );
138        g_free( base );
139    }
140
141    if( response == GTK_RESPONSE_CLOSE )
142        gtk_widget_destroy( GTK_WIDGET(dialog) );
143}
144
145static void
146toggled_cb( GtkToggleButton * w, gpointer core )
147{
148    const char * key = g_object_get_data( G_OBJECT(w), PREF_KEY );
149    const gboolean flag = gtk_toggle_button_get_active( w );
150    tr_core_set_pref_bool( TR_CORE(core), key, flag );
151}
152
153static GtkWidget*
154new_check_button( const char * mnemonic, const char * key, gpointer core )
155{
156    GtkWidget * w = gtk_check_button_new_with_mnemonic( mnemonic );
157    g_object_set_data_full( G_OBJECT(w), PREF_KEY, g_strdup(key), g_free );
158    gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(w), pref_flag_get(key) );
159    g_signal_connect( w, "toggled", G_CALLBACK(toggled_cb), core );
160    return w;
161}
162
163static void
164spun_cb( GtkSpinButton * w, gpointer core )
165{
166    const char * key = g_object_get_data( G_OBJECT(w), PREF_KEY );
167    const int value = gtk_spin_button_get_value_as_int( w );
168    tr_core_set_pref_int( TR_CORE(core), key, value );
169}
170
171static GtkWidget*
172new_spin_button( const char * key, gpointer core, int low, int high, int step )
173{
174    GtkWidget * w = gtk_spin_button_new_with_range( low, high, step );
175    g_object_set_data_full( G_OBJECT(w), PREF_KEY, g_strdup(key), g_free );
176    gtk_spin_button_set_digits( GTK_SPIN_BUTTON(w), 0 );
177    gtk_spin_button_set_value( GTK_SPIN_BUTTON(w), pref_int_get(key) );
178    g_signal_connect( w, "value-changed", G_CALLBACK(spun_cb), core );
179    return w;
180}
181
182static void
183entry_changed_cb( GtkEntry * w, gpointer core )
184{
185    const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
186    const char * value = gtk_entry_get_text( w );
187    tr_core_set_pref( TR_CORE( core ), key, value );
188}
189
190static GtkWidget*
191new_entry( const char * key, gpointer core )
192{
193    GtkWidget * w = gtk_entry_new( );
194    const char * value = pref_string_get( key );
195    if( value )
196        gtk_entry_set_text( GTK_ENTRY( w ), value );
197    g_object_set_data_full( G_OBJECT(w), PREF_KEY, g_strdup(key), g_free );
198    g_signal_connect( w, "changed", G_CALLBACK(entry_changed_cb), core );
199    return w;
200}
201
202static void
203chosen_cb( GtkFileChooser * w, gpointer core )
204{
205    const char * key = g_object_get_data( G_OBJECT(w), PREF_KEY );
206    char * value = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(w) );
207    tr_core_set_pref( TR_CORE(core), key, value );
208    g_free( value );
209}
210
211static GtkWidget*
212new_path_chooser_button( const char * key, gpointer core )
213{
214    GtkWidget * w = gtk_file_chooser_button_new( NULL,
215                                    GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER );
216    const char * path = pref_string_get( key );
217    g_object_set_data_full( G_OBJECT(w), PREF_KEY, g_strdup(key), g_free );
218    g_signal_connect( w, "selection-changed", G_CALLBACK(chosen_cb), core );
219    gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(w), path );
220    return w;
221}
222
223static void
224target_cb( GtkWidget * tb, gpointer target )
225{
226    const gboolean b = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( tb ) );
227    gtk_widget_set_sensitive( GTK_WIDGET(target), b );
228}
229
230struct test_port_data
231{
232    GtkWidget * label;
233    gboolean * alive;
234};
235
236static void
237testing_port_done( tr_handle   * handle         UNUSED,
238                   long          response_code  UNUSED,
239                   const void  * response,
240                   size_t        response_len,
241                   void        * gdata )
242{
243    struct test_port_data * data = gdata;
244    if( *data->alive )
245    {
246        const int isOpen = response_len && *(char*)response=='1';
247        gtk_label_set_markup( GTK_LABEL( data->label ), isOpen
248                              ? _("Port is <b>open</b>")
249                              : _("Port is <b>closed</b>") );
250    }
251}
252
253static gboolean
254testing_port_begin( gpointer gdata )
255{
256    struct test_port_data * data = gdata;
257    if( *data->alive )
258    {
259        GtkSpinButton * spin = g_object_get_data( G_OBJECT( data->label ), "tr-port-spin" );
260        tr_handle * handle = g_object_get_data( G_OBJECT( data->label ), "handle" );
261        const int port = gtk_spin_button_get_value_as_int( spin );
262        char url[256];
263        snprintf( url, sizeof(url), "http://portcheck.transmissionbt.com/%d", port );
264        tr_webRun( handle, url, NULL, testing_port_done, data );
265    }
266    return FALSE;
267}
268
269static void
270testing_port_cb( GtkWidget * unused UNUSED, gpointer l )
271{
272    gtk_label_set_markup( GTK_LABEL( l ), _( "<i>Testing port...</i>" ) );
273    /* wait three seconds to give the port forwarding time to kick in */
274    struct test_port_data * data = g_new0( struct test_port_data, 1 );
275    data->label = l;
276    data->alive = g_object_get_data( G_OBJECT( l ), "alive" );
277    g_timeout_add( 3000, testing_port_begin, data );
278}
279
280static void
281dialogDestroyed( gpointer alive, GObject * dialog UNUSED )
282{
283    *(gboolean*)alive = FALSE;
284}
285
286static GtkWidget*
287torrentPage( GObject * core )
288{
289    int row = 0;
290    const char * s;
291    GtkWidget * t;
292    GtkWidget * w;
293#ifdef HAVE_GIO
294    GtkWidget * l;
295#endif
296
297    t = hig_workarea_create( );
298    hig_workarea_add_section_title( t, &row, _( "Adding Torrents" ) );
299
300#ifdef HAVE_GIO
301        s = _( "Automatically add torrents from:" );
302        l = new_check_button( s, PREF_KEY_DIR_WATCH_ENABLED, core );
303        w = new_path_chooser_button( PREF_KEY_DIR_WATCH, core );
304        gtk_widget_set_sensitive( GTK_WIDGET(w), pref_flag_get( PREF_KEY_DIR_WATCH_ENABLED ) );
305        g_signal_connect( l, "toggled", G_CALLBACK(target_cb), w );
306        hig_workarea_add_row_w( t, &row, l, w, NULL );
307#endif
308
309        s = _( "Display _options dialog" );
310        w = new_check_button( s, PREF_KEY_OPTIONS_PROMPT, core );
311        hig_workarea_add_wide_control( t, &row, w );
312
313        s = _( "_Start when added" );
314        w = new_check_button( s, PREF_KEY_START, core );
315        hig_workarea_add_wide_control( t, &row, w );
316
317        s = _( "Mo_ve source files to Trash" );
318        w = new_check_button( s, PREF_KEY_TRASH_ORIGINAL, core ); 
319        hig_workarea_add_wide_control( t, &row, w );
320
321        w = new_path_chooser_button( PREF_KEY_DOWNLOAD_DIR, core );
322        hig_workarea_add_row( t, &row, _( "_Destination folder:" ), w, NULL );
323
324    hig_workarea_finish( t, &row );
325    return t;
326}
327
328/***
329****
330***/
331
332struct blocklist_data
333{
334    GtkWidget * check;
335    GtkWidget * dialog;
336    TrCore * core;
337    int abortFlag;
338    char secondary[256];
339};
340
341static void
342updateBlocklistText( GtkWidget * w, TrCore * core )
343{
344    const int n = tr_blocklistGetRuleCount( tr_core_handle( core ) );
345    char buf[512];
346    g_snprintf( buf, sizeof( buf ),
347                ngettext( "Ignore the %'d _blocklisted peer",
348                          "Ignore the %'d _blocklisted peers", n ), n );
349    gtk_button_set_label( GTK_BUTTON( w ), buf );
350}
351static gboolean
352updateBlocklistTextFromData( gpointer gdata )
353{
354    struct blocklist_data * data = gdata;
355    updateBlocklistText( data->check, data->core );
356    return FALSE;
357}
358
359static gboolean
360blocklistDialogSetSecondary( gpointer gdata )
361{
362    struct blocklist_data * data = gdata;
363    GtkMessageDialog * md = GTK_MESSAGE_DIALOG( data->dialog );
364    gtk_message_dialog_format_secondary_text( md, data->secondary );
365    return FALSE;
366}
367
368static gboolean
369blocklistDialogAllowClose( gpointer dialog )
370{
371    GtkDialog * d = GTK_DIALOG( dialog );
372    gtk_dialog_set_response_sensitive( GTK_DIALOG( d ), GTK_RESPONSE_CANCEL, FALSE );
373    gtk_dialog_set_response_sensitive( GTK_DIALOG( d ), GTK_RESPONSE_CLOSE, TRUE );
374    return FALSE;
375}
376
377static void
378got_blocklist( tr_handle   * handle         UNUSED,
379               long          response_code  UNUSED,
380               const void  * response,
381               size_t        response_len,
382               void        * gdata )
383{
384    struct blocklist_data * data = gdata;
385    const char * text = response;
386    int size = response_len;
387    int rules = 0;
388    gchar * filename = NULL;
389    gchar * filename2 = NULL;
390    int fd = -1;
391    int ok = 1;
392
393    if( !data->abortFlag && ( !text || !size ) )
394    {
395        ok = FALSE;
396        g_snprintf( data->secondary, sizeof( data->secondary ),
397                    _( "Unable to get blocklist." ) );
398        g_message( data->secondary );
399        g_idle_add( blocklistDialogSetSecondary, data );
400    }     
401
402    if( ok && !data->abortFlag )
403    {
404        GError * err = NULL;
405        fd = g_file_open_tmp( "transmission-blockfile-XXXXXX", &filename, &err );
406        if( err ) {
407            g_snprintf( data->secondary, sizeof( data->secondary ),
408                        _( "Unable to get blocklist: %s" ), err->message );
409            g_warning( data->secondary );
410            g_idle_add( blocklistDialogSetSecondary, data );
411            g_clear_error( &err );
412            ok = FALSE;
413        } else {
414            write( fd, text, size );
415            close( fd );
416        }
417    }
418    if( ok && !data->abortFlag )
419    {
420        filename2 = g_strdup_printf( "%s.txt", filename );
421        g_snprintf( data->secondary, sizeof( data->secondary ),
422                    _( "Uncompressing blocklist..." ) );
423        g_idle_add( blocklistDialogSetSecondary, data );
424        char * cmd = g_strdup_printf( "zcat %s > %s ", filename, filename2 );
425        tr_dbg( "%s", cmd );
426        system( cmd );
427        g_free( cmd );
428    }
429    if( ok && !data->abortFlag )
430    {
431        g_snprintf( data->secondary, sizeof( data->secondary ),
432                    _( "Parsing blocklist..." ) );
433        g_idle_add( blocklistDialogSetSecondary, data );
434        rules = tr_blocklistSetContent( tr_core_handle( data->core ), filename2 );
435    }
436    if( ok && !data->abortFlag )
437    {
438        g_snprintf( data->secondary, sizeof( data->secondary ),
439                    _( "Blocklist updated with %'d entries" ), rules );
440        g_idle_add( blocklistDialogSetSecondary, data );
441        g_idle_add( blocklistDialogAllowClose, data->dialog );
442        g_idle_add( updateBlocklistTextFromData, data );
443    }
444
445    /* g_free( data ); */
446    if( filename2 ) {
447        unlink( filename2 );
448        g_free( filename2 );
449    }
450    if( filename ) {
451        unlink( filename );
452        g_free( filename );
453    }
454}
455
456static void
457onUpdateBlocklistResponseCB( GtkDialog * dialog, int response, gpointer vdata )
458{
459    struct blocklist_data * data = vdata;
460
461    if( response == GTK_RESPONSE_CANCEL )
462        data->abortFlag = 1;
463
464    data->dialog = NULL;
465    gtk_widget_destroy( GTK_WIDGET( dialog ) );
466}
467
468static void
469onUpdateBlocklistCB( GtkButton * w, gpointer gdata )
470{
471    GtkWidget * d;
472    struct blocklist_data * data = gdata;
473    tr_handle * handle = g_object_get_data( G_OBJECT( w ), "handle" );
474    const char * url = "http://download.m0k.org/transmission/files/level1.gz";
475   
476    d = gtk_message_dialog_new( GTK_WINDOW( gtk_widget_get_toplevel( GTK_WIDGET( w ) ) ),
477                                GTK_DIALOG_DESTROY_WITH_PARENT,
478                                GTK_MESSAGE_INFO,
479                                GTK_BUTTONS_NONE,
480                                _( "Updating Blocklist" ) );
481    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( d ),
482                                              _( "Retrieving blocklist..." ) );
483    gtk_dialog_add_buttons( GTK_DIALOG( d ),
484                            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
485                            GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
486                            NULL );
487    gtk_dialog_set_response_sensitive( GTK_DIALOG( d ), GTK_RESPONSE_CLOSE, FALSE );
488
489    data->dialog = d;
490
491    g_signal_connect( d, "response", G_CALLBACK(onUpdateBlocklistResponseCB), data );
492    gtk_widget_show( d );
493
494    tr_webRun( handle, url, NULL, got_blocklist, data );
495}
496
497static void
498onEncryptionToggled( GtkToggleButton * w, gpointer core )
499{
500    const int val = gtk_toggle_button_get_active( w )
501                  ? TR_ENCRYPTION_REQUIRED
502                  : TR_ENCRYPTION_PREFERRED;
503    tr_core_set_pref_int( TR_CORE( core ), PREF_KEY_ENCRYPTION, val );
504}
505
506static GtkWidget*
507peerPage( GObject * core, gboolean * alive )
508{
509    int row = 0;
510    const char * s;
511    GtkWidget * t;
512    GtkWidget * w;
513    GtkWidget * w2;
514    GtkWidget * b;
515    GtkWidget * h;
516    GtkWidget * l;
517    struct blocklist_data * data;
518
519    t = hig_workarea_create( );
520    hig_workarea_add_section_title (t, &row, _("Options"));
521
522        w = new_check_button( "", PREF_KEY_BLOCKLIST_ENABLED, core );
523        updateBlocklistText( w, TR_CORE( core ) );
524        h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
525        gtk_box_pack_start_defaults( GTK_BOX(h), w );
526        b = gtk_button_new_with_mnemonic( _( "_Update Blocklist" ) );
527
528        data = g_new0( struct blocklist_data, 1 );
529        data->core = TR_CORE( core );
530        data->check = w;
531
532        g_object_set_data( G_OBJECT( b ), "handle", tr_core_handle( TR_CORE( core ) ) );
533        g_signal_connect( b, "clicked", G_CALLBACK(onUpdateBlocklistCB), data );
534        gtk_box_pack_start( GTK_BOX(h), b, FALSE, FALSE, 0 );
535        g_signal_connect( w, "toggled", G_CALLBACK(target_cb), b );
536        target_cb( w, b );
537        hig_workarea_add_wide_control( t, &row, h );
538       
539        s = _("_Ignore unencrypted peers");
540        w = gtk_check_button_new_with_mnemonic( s );
541        gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(w),
542                                      pref_int_get(PREF_KEY_ENCRYPTION)==TR_ENCRYPTION_REQUIRED );
543        g_signal_connect( w, "toggled", G_CALLBACK(onEncryptionToggled), core );
544        hig_workarea_add_wide_control( t, &row, w );
545
546        s = _("Use peer e_xchange");
547        w = new_check_button( s, PREF_KEY_PEX, core );
548        hig_workarea_add_wide_control( t, &row, w );
549
550        h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
551        w2 = new_spin_button( PREF_KEY_PORT, core, 1, INT_MAX, 1 );
552        gtk_box_pack_start( GTK_BOX(h), w2, FALSE, FALSE, 0 );
553        l = gtk_label_new( NULL );
554        gtk_misc_set_alignment( GTK_MISC(l), 0.0f, 0.5f );
555        gtk_box_pack_start( GTK_BOX(h), l, FALSE, FALSE, 0 );
556        hig_workarea_add_row( t, &row, _("Listening _port:"), h, w2 );
557
558        g_object_set_data( G_OBJECT(l), "tr-port-spin", w2 );
559        g_object_set_data( G_OBJECT(l), "alive", alive );
560        g_object_set_data( G_OBJECT(l), "handle", tr_core_handle( TR_CORE( core ) ) );
561        testing_port_cb( NULL, l );
562        g_signal_connect( w2, "value-changed", G_CALLBACK(testing_port_cb), l );
563       
564    hig_workarea_add_section_divider( t, &row );
565    hig_workarea_add_section_title( t, &row, _( "Limits" ) );
566 
567        w = new_spin_button( PREF_KEY_MAX_PEERS_GLOBAL, core, 1, 3000, 5 );
568        hig_workarea_add_row( t, &row, _( "Maximum peers _overall:" ), w, NULL );
569        w = new_spin_button( PREF_KEY_MAX_PEERS_PER_TORRENT, core, 1, 300, 5 );
570        hig_workarea_add_row( t, &row, _( "Maximum peers per _torrent:" ), w, NULL );
571
572    hig_workarea_finish( t, &row );
573    return t;
574}
575
576static GtkTreeModel*
577allow_deny_model_new( void )
578{
579    GtkTreeIter iter;
580    GtkListStore * store = gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_CHAR );
581    gtk_list_store_append( store, &iter );
582    gtk_list_store_set( store, &iter, 0, _( "Allow" ), 1, '+', -1 );
583    gtk_list_store_append( store, &iter );
584    gtk_list_store_set( store, &iter, 0, _( "Deny" ), 1, '-', -1 );
585    return GTK_TREE_MODEL( store );
586}
587
588enum
589{
590    COL_ADDRESS,
591    COL_PERMISSION,
592    N_COLS
593};
594
595static GtkTreeModel*
596acl_tree_model_new( const char * acl )
597{
598    int i;
599    char ** rules;
600    GtkListStore * store = gtk_list_store_new( N_COLS,
601                                               G_TYPE_STRING,
602                                               G_TYPE_STRING );
603    rules = g_strsplit( acl, ",", 0 );
604
605    for( i=0; rules && rules[i]; ++i )
606    {
607        const char * s = rules[i];
608        while( isspace( *s ) ) ++s;
609        if( *s=='+' || *s=='-' )
610        {
611            GtkTreeIter iter;
612            gtk_list_store_append( store, &iter );
613            gtk_list_store_set( store, &iter,
614                COL_PERMISSION, *s=='+' ? _( "Allow" ) : _( "Deny" ) ,
615                COL_ADDRESS, s+1,
616                -1 );
617        }
618    }
619
620    g_strfreev( rules );
621    return GTK_TREE_MODEL( store );
622}
623
624struct remote_page
625{
626    TrCore * core;
627    GtkTreeView * view;
628    GtkListStore * store;
629    GtkWidget * remove_button;
630    GSList * widgets;
631    GSList * auth_widgets;
632    GtkToggleButton * rpc_tb;
633    GtkToggleButton * auth_tb;
634};
635
636static void
637refreshACL( struct remote_page * page )
638{
639    GtkTreeIter iter;
640    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
641    GString * gstr = g_string_new( NULL );
642
643    if( gtk_tree_model_get_iter_first( model, &iter ) ) do
644    {
645        char * permission;
646        char * address;
647        gtk_tree_model_get( model, &iter, COL_PERMISSION, &permission,
648                                          COL_ADDRESS, &address,
649                                          -1 );
650        g_string_append_c( gstr, strcmp(permission,_("Allow")) ? '-' : '+' );
651        g_string_append( gstr, address );
652        g_string_append( gstr, ", " );
653        g_free( address );
654        g_free( permission );
655    }
656    while( gtk_tree_model_iter_next( model, &iter ) );
657
658    g_string_truncate( gstr, gstr->len-2 ); /* remove the trailing ", " */
659
660    tr_core_set_pref( page->core, PREF_KEY_RPC_ACL, gstr->str );
661
662    g_string_free( gstr, TRUE );
663}
664
665static void
666onPermissionEdited( GtkCellRendererText  * renderer UNUSED,
667                    gchar                * path_string,
668                    gchar                * new_text,
669                    gpointer               gpage )
670{
671    GtkTreeIter iter;
672    GtkTreePath * path = gtk_tree_path_new_from_string( path_string );
673    struct remote_page * page = gpage;
674    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
675    if( gtk_tree_model_get_iter( model, &iter, path ) )
676        gtk_list_store_set( page->store, &iter, COL_PERMISSION, new_text, -1 );
677    gtk_tree_path_free( path );
678    refreshACL( page );
679}
680
681static void
682onAddressEdited( GtkCellRendererText  * r UNUSED,
683                 gchar                * path_string,
684                 gchar                * new_text,
685                 gpointer               gpage )
686{
687    char * acl;
688    GtkTreeIter iter;
689    struct remote_page * page = gpage;
690    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
691    tr_handle * session = tr_core_handle( page->core );
692    GtkTreePath * path = gtk_tree_path_new_from_string( path_string );
693
694    acl = g_strdup_printf( "+%s", new_text );
695    if( !tr_sessionTestRPCACL( session, acl, NULL ) )
696        if( gtk_tree_model_get_iter( model, &iter, path ) )
697            gtk_list_store_set( page->store, &iter, COL_ADDRESS, new_text, -1 );
698
699    g_free( acl );
700    gtk_tree_path_free( path );
701    refreshACL( page );
702}
703
704static void
705onAddACLClicked( GtkButton * b UNUSED, gpointer gpage )
706{
707    GtkTreeIter iter;
708    GtkTreePath * path;
709    struct remote_page * page = gpage;
710    gtk_list_store_append( page->store, &iter );
711    gtk_list_store_set( page->store, &iter,
712                        COL_PERMISSION, _( "Allow" ),
713                        COL_ADDRESS, _( "0.0.0.0" ),
714                        -1 );
715
716    path = gtk_tree_model_get_path( GTK_TREE_MODEL( page->store ), &iter );
717    gtk_tree_view_set_cursor(
718        page->view, path,
719        gtk_tree_view_get_column( page->view, COL_ADDRESS ),
720        TRUE );
721    gtk_tree_path_free( path );
722}
723
724static void
725onRemoveACLClicked( GtkButton * b UNUSED, gpointer gpage )
726{
727    struct remote_page * page = gpage;
728    GtkTreeSelection * sel = gtk_tree_view_get_selection( page->view );
729    GtkTreeIter iter;
730    if( gtk_tree_selection_get_selected( sel, NULL, &iter ) )
731    {
732        gtk_list_store_remove( page->store, &iter );
733        refreshACL( page );
734    }
735}
736
737static void
738refreshRPCSensitivity( struct remote_page * page )
739{
740    GSList * l;
741    const int rpc_active = gtk_toggle_button_get_active( page->rpc_tb );
742    const int auth_active = gtk_toggle_button_get_active( page->auth_tb );
743    GtkTreeSelection * sel = gtk_tree_view_get_selection( page->view );
744    const int have_addr = gtk_tree_selection_get_selected( sel, NULL, NULL );
745    const int n_rules = gtk_tree_model_iter_n_children(
746                                       GTK_TREE_MODEL( page->store ), NULL );
747
748    for( l=page->widgets; l!=NULL; l=l->next )
749        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), rpc_active );
750
751    for( l=page->auth_widgets; l!=NULL; l=l->next )
752        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), rpc_active && auth_active);
753
754    gtk_widget_set_sensitive( page->remove_button,
755                              rpc_active && have_addr && n_rules>1 );
756}
757
758static void
759onRPCToggled( GtkToggleButton * tb UNUSED, gpointer page )
760{
761    refreshRPCSensitivity( page );
762}
763static void
764onACLSelectionChanged( GtkTreeSelection * sel UNUSED, gpointer page )
765{
766    refreshRPCSensitivity( page );
767}
768
769static GtkWidget*
770remotePage( GObject * core )
771{
772    const char  * s;
773    int row = 0;
774    GtkWidget * t;
775    GtkWidget * w;
776    struct remote_page * page = g_new0( struct remote_page, 1 );
777
778    page->core = TR_CORE( core );
779
780    t = hig_workarea_create( );
781    g_object_set_data_full( G_OBJECT( t ), "page", page, g_free );
782
783    hig_workarea_add_section_title( t, &row, _( "Remote Access" ) );
784
785        /* "enabled" checkbutton */
786        s = _( "A_llow requests from transmission-remote, Clutch, etc." );
787        w = new_check_button( s, PREF_KEY_RPC_ENABLED, core );
788        hig_workarea_add_wide_control( t, &row, w );
789        page->rpc_tb = GTK_TOGGLE_BUTTON( w );
790        g_signal_connect( w, "clicked", G_CALLBACK(onRPCToggled), page );
791
792        /* require authentication */
793        s = _( "Require _authentication" );
794        w = new_check_button( s, PREF_KEY_RPC_AUTH_ENABLED, core );
795        hig_workarea_add_wide_control( t, &row, w );
796        page->auth_tb = GTK_TOGGLE_BUTTON( w );
797        page->widgets = g_slist_append( page->widgets, w );
798        g_signal_connect( w, "clicked", G_CALLBACK(onRPCToggled), page );
799
800        /* username */
801        s = _( "_Username:" );
802        w = new_entry( PREF_KEY_RPC_USERNAME, core );
803        page->auth_widgets = g_slist_append( page->auth_widgets, w );
804        w = hig_workarea_add_row( t, &row, s, w, NULL );
805        page->auth_widgets = g_slist_append( page->auth_widgets, w );
806
807        /* password */
808        s = _( "_Password:" );
809        w = new_entry( PREF_KEY_RPC_PASSWORD, core );
810        gtk_entry_set_visibility( GTK_ENTRY( w ), FALSE );
811        page->auth_widgets = g_slist_append( page->auth_widgets, w );
812        w = hig_workarea_add_row( t, &row, s, w, NULL );
813        page->auth_widgets = g_slist_append( page->auth_widgets, w );
814
815        /* port */
816        w = new_spin_button( PREF_KEY_RPC_PORT, core, 0, 65535, 1 );
817        page->widgets = g_slist_append( page->widgets, w );
818        w = hig_workarea_add_row( t, &row, _( "Listening _port:" ), w, NULL );
819        page->widgets = g_slist_append( page->widgets, w );
820
821        /* access control list */
822        {
823        const char * val = pref_string_get( PREF_KEY_RPC_ACL );
824        GtkTreeModel * m = acl_tree_model_new( val );
825        GtkTreeViewColumn * c;
826        GtkCellRenderer * r;
827        GtkTreeSelection * sel;
828        GtkTreeView * v;
829        GtkWidget * w;
830        GtkWidget * h;
831        GtkTooltips * tips = gtk_tooltips_new( );
832
833        s = _( "Access control list:" );
834        page->store = GTK_LIST_STORE( m );
835        w = gtk_tree_view_new_with_model( m );
836
837        page->widgets = g_slist_append( page->widgets, w );
838        v = page->view = GTK_TREE_VIEW( w );
839        gtk_tooltips_set_tip( tips, w,
840            _( "IP addresses may use wildcards, such as 192.168.*.*" ),
841            NULL );
842        sel = gtk_tree_view_get_selection( v );
843        g_signal_connect( sel, "changed",
844                          G_CALLBACK( onACLSelectionChanged ), page );
845        g_object_unref( G_OBJECT( m ) );
846        gtk_tree_view_set_headers_visible( v, TRUE );
847        w = gtk_frame_new( NULL );
848        gtk_frame_set_shadow_type( GTK_FRAME( w ), GTK_SHADOW_IN );
849        gtk_container_add( GTK_CONTAINER( w ), GTK_WIDGET( v ) );
850
851        /* ip address column */
852        r = gtk_cell_renderer_text_new( );
853        g_signal_connect( r, "edited",
854                          G_CALLBACK( onAddressEdited ), page );
855        g_object_set( G_OBJECT( r ), "editable", TRUE, NULL );
856        c = gtk_tree_view_column_new_with_attributes( _( "IP Address" ), r,
857                "text", COL_ADDRESS,
858                NULL );
859        gtk_tree_view_column_set_expand( c, TRUE );
860        gtk_tree_view_append_column( v, c );
861
862        w = hig_workarea_add_row( t, &row, s, w, NULL );
863        gtk_misc_set_alignment( GTK_MISC( w ), 0.0f, 0.1f );
864        page->widgets = g_slist_append( page->widgets, w );
865
866        /* permission column */
867        m = allow_deny_model_new( );
868        r = gtk_cell_renderer_combo_new( );
869        g_object_set( G_OBJECT( r ), "model", m,
870                                     "editable", TRUE,
871                                     "has-entry", FALSE,
872                                     "text-column", 0,
873                                     NULL );
874        c = gtk_tree_view_column_new_with_attributes( _( "Permission" ), r,
875                "text", COL_PERMISSION,
876                NULL );
877        g_signal_connect( r, "edited",
878                          G_CALLBACK( onPermissionEdited ), page );
879        gtk_tree_view_append_column( v, c );
880
881        h = gtk_hbox_new( TRUE, GUI_PAD );
882        w = gtk_button_new_from_stock( GTK_STOCK_REMOVE );
883        g_signal_connect( w, "clicked", G_CALLBACK(onRemoveACLClicked), page );
884        page->remove_button = w;
885        onACLSelectionChanged( sel, page );
886        gtk_box_pack_start_defaults( GTK_BOX( h ), w );
887        w = gtk_button_new_from_stock( GTK_STOCK_ADD );
888        page->widgets = g_slist_append( page->widgets, w );
889        g_signal_connect( w, "clicked", G_CALLBACK(onAddACLClicked), page );
890        gtk_box_pack_start_defaults( GTK_BOX( h ), w );
891        w = gtk_hbox_new( FALSE, 0 );
892        gtk_box_pack_start_defaults( GTK_BOX( w ), gtk_alignment_new( 0, 0, 0, 0 ) );
893        gtk_box_pack_start( GTK_BOX( w ), h, FALSE, FALSE, 0 );
894        hig_workarea_add_wide_control( t, &row, w );
895        }
896
897    refreshRPCSensitivity( page );
898    hig_workarea_finish( t, &row );
899    return t;
900}
901
902struct ProxyPage
903{
904    TrCore * core;
905    GSList * proxy_widgets;
906    GSList * proxy_auth_widgets;
907};
908
909static void
910refreshProxySensitivity( struct ProxyPage * p )
911{
912    GSList * l;
913    const gboolean proxy_enabled = pref_flag_get( PREF_KEY_PROXY_SERVER_ENABLED );
914    const gboolean proxy_auth_enabled = pref_flag_get( PREF_KEY_PROXY_AUTH_ENABLED );
915
916    for( l=p->proxy_widgets; l!=NULL; l=l->next )
917        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), proxy_enabled );
918
919    for( l=p->proxy_auth_widgets; l!=NULL; l=l->next )
920        gtk_widget_set_sensitive( GTK_WIDGET( l->data ),
921                                  proxy_enabled && proxy_auth_enabled);
922}
923
924static void
925onProxyToggled( GtkToggleButton * tb UNUSED, gpointer user_data )
926{
927    refreshProxySensitivity( user_data );
928}
929
930static void
931proxyPageFree( gpointer gpage )
932{
933    struct ProxyPage * page = gpage;
934    g_slist_free( page->proxy_widgets );
935    g_slist_free( page->proxy_auth_widgets );
936    g_free( page );
937}
938
939static GtkTreeModel*
940proxyTypeModelNew( void )
941{
942    GtkTreeIter iter;
943    GtkListStore * store = gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_INT );
944    gtk_list_store_append( store, &iter );
945    gtk_list_store_set( store, &iter, 0, _( "HTTP" ), 1, TR_PROXY_HTTP, -1 );
946    gtk_list_store_append( store, &iter );
947    gtk_list_store_set( store, &iter, 0, _( "SOCKS4" ), 1, TR_PROXY_SOCKS4, -1 );
948    gtk_list_store_append( store, &iter );
949    gtk_list_store_set( store, &iter, 0, _( "SOCKS5" ), 1, TR_PROXY_SOCKS5, -1 );
950    return GTK_TREE_MODEL( store );
951}
952
953static void
954onProxyTypeChanged( GtkComboBox * w, gpointer gpage )
955{
956    GtkTreeIter iter;
957    if( gtk_combo_box_get_active_iter( w, &iter ) )
958    {
959        struct ProxyPage * page = gpage;
960        int type = TR_PROXY_HTTP;
961        gtk_tree_model_get( gtk_combo_box_get_model( w ), &iter, 1, &type, -1 );
962        tr_core_set_pref_int( TR_CORE( page->core ), PREF_KEY_PROXY_TYPE, type );
963    }
964}
965
966static GtkWidget*
967networkPage( GObject * core )
968{
969    int row = 0;
970    const char * s;
971    GtkWidget * t;
972    GtkWidget * w, * w2;
973    GtkTreeModel * m;
974    GtkCellRenderer * r;
975    struct ProxyPage * page = tr_new0( struct ProxyPage, 1 );
976
977    page->core = TR_CORE( core );
978
979    t = hig_workarea_create( );
980    hig_workarea_add_section_title (t, &row, _( "Router" ) );
981
982        s = _("Use port _forwarding from my router" );
983        w = new_check_button( s, PREF_KEY_PORT_FORWARDING, core );
984        hig_workarea_add_wide_control( t, &row, w );
985
986    hig_workarea_add_section_divider( t, &row );
987    hig_workarea_add_section_title (t, &row, _("Bandwidth"));
988
989        s = _("Limit _download speed (KB/s):");
990        w = new_check_button( s, PREF_KEY_DL_LIMIT_ENABLED, core );
991        w2 = new_spin_button( PREF_KEY_DL_LIMIT, core, 0, INT_MAX, 5 );
992        gtk_widget_set_sensitive( GTK_WIDGET(w2), pref_flag_get( PREF_KEY_DL_LIMIT_ENABLED ) );
993        g_signal_connect( w, "toggled", G_CALLBACK(target_cb), w2 );
994        hig_workarea_add_row_w( t, &row, w, w2, NULL );
995
996        s = _("Limit _upload speed (KB/s):");
997        w = new_check_button( s, PREF_KEY_UL_LIMIT_ENABLED, core );
998        w2 = new_spin_button( PREF_KEY_UL_LIMIT, core, 0, INT_MAX, 5 );
999        gtk_widget_set_sensitive( GTK_WIDGET(w2), pref_flag_get( PREF_KEY_UL_LIMIT_ENABLED ) );
1000        g_signal_connect( w, "toggled", G_CALLBACK(target_cb), w2 );
1001        hig_workarea_add_row_w( t, &row, w, w2, NULL );
1002
1003    hig_workarea_add_section_divider( t, &row );
1004    hig_workarea_add_section_title (t, &row, _( "Tracker Proxy" ) );
1005
1006        s = _( "Connect to tracker with HTTP proxy" );
1007        w = new_check_button( s, PREF_KEY_PROXY_SERVER_ENABLED, core );
1008        g_signal_connect( w, "toggled", G_CALLBACK(onProxyToggled), page );
1009        hig_workarea_add_wide_control( t, &row, w );
1010
1011        s = _( "Proxy server:" );
1012        w = new_entry( PREF_KEY_PROXY_SERVER, core );
1013        page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1014        w = hig_workarea_add_row( t, &row, s, w, NULL );
1015        page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1016
1017        s = _( "Proxy type:" );
1018        m = proxyTypeModelNew( );
1019        w = gtk_combo_box_new_with_model( m );
1020        r = gtk_cell_renderer_text_new( );
1021        gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
1022        gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( w ), r, "text", 0, NULL );
1023        gtk_combo_box_set_active( GTK_COMBO_BOX( w ), pref_int_get( PREF_KEY_PROXY_TYPE ) );
1024        g_signal_connect( w, "changed", G_CALLBACK(onProxyTypeChanged), page );
1025        g_object_unref( G_OBJECT( m ) );
1026        page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1027        w = hig_workarea_add_row( t, &row, s, w, NULL );
1028        page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1029
1030        s = _( "_Authentication is required" );
1031        w = new_check_button( s, PREF_KEY_PROXY_AUTH_ENABLED, core );
1032        g_signal_connect( w, "toggled", G_CALLBACK(onProxyToggled), page );
1033        hig_workarea_add_wide_control( t, &row, w );
1034        page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1035
1036        s = _( "_Username:" );
1037        w = new_entry( PREF_KEY_PROXY_USERNAME, core );
1038        page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1039        w = hig_workarea_add_row( t, &row, s, w, NULL );
1040        page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1041
1042        s = _( "_Password:" );
1043        w = new_entry( PREF_KEY_PROXY_PASSWORD, core );
1044        gtk_entry_set_visibility( GTK_ENTRY( w ), FALSE );
1045        page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1046        w = hig_workarea_add_row( t, &row, s, w, NULL );
1047        page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1048
1049    hig_workarea_finish( t, &row );
1050    g_object_set_data_full( G_OBJECT( t ), "page", page, proxyPageFree );
1051
1052    refreshProxySensitivity( page );
1053    return t;
1054}
1055
1056GtkWidget *
1057tr_prefs_dialog_new( GObject * core, GtkWindow * parent )
1058{
1059    GtkWidget * d;
1060    GtkWidget * n;
1061    gboolean * alive;
1062
1063    alive = g_new( gboolean, 1 );
1064    *alive = TRUE;
1065
1066    d = gtk_dialog_new_with_buttons( _( "Transmission Preferences" ), parent,
1067                                     GTK_DIALOG_DESTROY_WITH_PARENT,
1068                                     GTK_STOCK_HELP, GTK_RESPONSE_HELP,
1069                                     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1070                                     NULL );
1071    gtk_window_set_role( GTK_WINDOW(d), "transmission-preferences-dialog" );
1072    gtk_dialog_set_has_separator( GTK_DIALOG( d ), FALSE );
1073    gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
1074    g_object_weak_ref( G_OBJECT( d ), dialogDestroyed, alive );
1075
1076    n = gtk_notebook_new( );
1077    gtk_container_set_border_width ( GTK_CONTAINER ( n ), GUI_PAD );
1078
1079    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1080                              torrentPage( core ),
1081                              gtk_label_new (_("Torrents")) );
1082    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1083                              peerPage( core, alive ),
1084                              gtk_label_new (_("Peers")) );
1085    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1086                              networkPage( core ),
1087                              gtk_label_new (_("Network")) );
1088    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1089                              remotePage( core ),
1090                              gtk_label_new (_("Remote")) );
1091
1092    g_signal_connect( d, "response", G_CALLBACK(response_cb), core );
1093    gtk_box_pack_start_defaults( GTK_BOX(GTK_DIALOG(d)->vbox), n );
1094    gtk_widget_show_all( GTK_DIALOG(d)->vbox );
1095    return d;
1096}
Note: See TracBrowser for help on using the repository browser.