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

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

(daemon) let file download flags and priorities be set by transmission-remote. Add examples to the man page and --help.
(libT) minor tweaks to tr-getopt
(gtk) tweak the preference dialog's "port forwarding" text for clarity as suggested in the forums

  • 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 6316 2008-07-10 20:59:15Z 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, FALSE );
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 UPnP or NAT-PMP 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.