Update for Vala 0.8.0
[tor-status] / src / status-area-applet-tor.vala
1 /* This file is part of status-area-applet-tor.
2  *
3  * Copyright (C) 2010 Philipp Zabel
4  *
5  * status-area-applet-tor is free software: you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as published
7  * by the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * status-area-applet-tor is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with status-area-applet-tor. If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 [Compact]
20 class ProxyBackup {
21         public bool use_http_proxy;
22         public string http_host;
23         public string socks_host;
24         public string secure_host;
25         public int http_port;
26         public int socks_port;
27         public int secure_port;
28         public string mode;
29 }
30
31 class TorStatusMenuItem : HD.StatusMenuItem {
32         private const string STATUSMENU_TOR_LIBOSSO_SERVICE_NAME = "tor_status_menu_item";
33
34         private const int STATUS_MENU_ICON_SIZE = 48;
35         private const int STATUS_AREA_ICON_SIZE = 18;
36
37         private const string GCONF_DIR_TOR         = "/apps/maemo/tor";
38         private const string GCONF_KEY_TOR_ENABLED = GCONF_DIR_TOR + "/enabled";
39         private const string GCONF_KEY_BRIDGES     = GCONF_DIR_TOR + "/bridges";
40
41         private const string GCONF_DIR_PROXY_HTTP         = "/system/http_proxy";
42         private const string GCONF_KEY_PROXY_HTTP_ENABLED = GCONF_DIR_PROXY_HTTP + "/use_http_proxy";
43         private const string GCONF_KEY_PROXY_HTTP_HOST    = GCONF_DIR_PROXY_HTTP + "/host";
44         private const string GCONF_KEY_PROXY_HTTP_PORT    = GCONF_DIR_PROXY_HTTP + "/port";
45
46         private const string GCONF_DIR_PROXY             = "/system/proxy";
47         private const string GCONF_KEY_PROXY_MODE        = GCONF_DIR_PROXY + "/mode";
48         private const string GCONF_KEY_PROXY_SOCKS_HOST  = GCONF_DIR_PROXY + "/socks_host";
49         private const string GCONF_KEY_PROXY_SOCKS_PORT  = GCONF_DIR_PROXY + "/socks_port";
50         private const string GCONF_KEY_PROXY_SECURE_HOST = GCONF_DIR_PROXY + "/secure_host";
51         private const string GCONF_KEY_PROXY_SECURE_PORT = GCONF_DIR_PROXY + "/secure_port";
52
53         // Widgets
54         Hildon.Button button;
55         Gtk.Label log_label;
56
57         // Icons
58         Gdk.Pixbuf icon_connecting;
59         Gdk.Pixbuf icon_connected;
60         Gtk.Image icon_enabled;
61         Gtk.Image icon_disabled;
62
63         // ConIc, GConf and Osso context
64         Osso.Context osso;
65         GConf.Client gconf;
66         ConIc.Connection conic;
67         bool conic_connected;
68
69         // Internal state
70         bool tor_enabled;
71         bool tor_connected;
72         Pid tor_pid;
73         int tor_stdout;
74         Pid polipo_pid;
75         ProxyBackup backup;
76         string tor_log;
77         TorControl.Connection tor_control;
78         string password;
79
80         /**
81          * Update status area icon and status menu button value
82          */
83         private void update_status () {
84                 if (tor_enabled && tor_connected && icon_connected == null) try {
85                         var icon_theme = Gtk.IconTheme.get_default ();
86                         var pixbuf = icon_theme.load_icon ("statusarea_tor_connected",
87                                                            STATUS_AREA_ICON_SIZE,
88                                                            Gtk.IconLookupFlags.NO_SVG);
89                         icon_connected = pixbuf;
90                 } catch (Error e) {
91                         error (e.message);
92                 }
93                 if (tor_enabled && !tor_connected && icon_connecting == null) try {
94                         var icon_theme = Gtk.IconTheme.get_default ();
95                         var pixbuf = icon_theme.load_icon ("statusarea_tor_connecting",
96                                                            STATUS_AREA_ICON_SIZE,
97                                                            Gtk.IconLookupFlags.NO_SVG);
98                         icon_connecting = pixbuf;
99                 } catch (Error e) {
100                         error (e.message);
101                 }
102                 if (tor_enabled && icon_enabled == null) try {
103                         var icon_theme = Gtk.IconTheme.get_default();
104                         var pixbuf = icon_theme.load_icon ("statusarea_tor_enabled",
105                                                            STATUS_MENU_ICON_SIZE,
106                                                            Gtk.IconLookupFlags.NO_SVG);
107                         icon_enabled = new Gtk.Image.from_pixbuf (pixbuf);
108                 } catch (Error e) {
109                         error (e.message);
110                 }
111                 if (!tor_enabled && icon_disabled == null) try {
112                         var icon_theme = Gtk.IconTheme.get_default();
113                         var pixbuf = icon_theme.load_icon ("statusarea_tor_disabled",
114                                                            STATUS_MENU_ICON_SIZE,
115                                                            Gtk.IconLookupFlags.NO_SVG);
116                         icon_disabled = new Gtk.Image.from_pixbuf (pixbuf);
117                 } catch (Error e) {
118                         error (e.message);
119                 }
120
121                 if (conic_connected && tor_enabled) {
122                         set_status_area_icon (tor_connected ? icon_connected : icon_connecting);
123                         button.set_value (tor_connected ? _("Connected") : _("Connecting ..."));
124                 } else {
125                         set_status_area_icon (null);
126                         button.set_value (tor_enabled ? _("Disconnected") : _("Disabled"));
127                 }
128                 button.set_image (tor_enabled ? icon_enabled : icon_disabled);
129         }
130
131         /**
132          * Callback for Tor daemon line output
133          */
134         private bool tor_io_func (IOChannel source, IOCondition condition) {
135
136                 if ((condition & (IOCondition.IN | IOCondition.PRI)) != 0) {
137                         string line = null;
138                         size_t length;
139                         try {
140                                 /* var status = */ source.read_line (out line, out length, null);
141
142                                 tor_log += line;
143                                 if (log_label != null)
144                                         log_label.label = tor_log;
145
146                                 if ("[notice]" in line) {
147                                         if ("Bootstrapped 100%" in line) {
148                                                 tor_connected = true;
149                                                 proxy_setup ();
150                                                 update_status ();
151                                         }
152                                         if ("Opening Control listener on 127.0.0.1:9051" in line) {
153                                                 tor_control = new TorControl.Connection ();
154                                                 tor_control_auth.begin ();
155                                         }
156                                 } else {
157                                         // FIXME
158                                         Hildon.Banner.show_information (null, null, "DEBUG: %s".printf (line));
159                                 }
160                         } catch (Error e) {
161                                 // FIXME
162                                 Hildon.Banner.show_information (null, null, "Error: %s".printf (e.message));
163                         }
164                 }
165                 if ((condition & (IOCondition.ERR | IOCondition.HUP | IOCondition.NVAL)) != 0) {
166                         return false;
167                 }
168                 return true;
169         }
170
171         /**
172          * Authenticate with Tor on the control channel
173          */
174         private async void tor_control_auth () throws Error {
175                 yield tor_control.authenticate_async (password);
176
177                 var bridges = new SList<string> ();
178                 try {
179                         bridges = gconf.get_list (GCONF_KEY_BRIDGES, GConf.ValueType.STRING);
180                 } catch (Error e) {
181                         error ("Error loading bridges: %s", e.message);
182                         return;
183                 }
184
185                 if (bridges.length () <= 0)
186                         return;
187
188                 // Enable bridge relays
189                 tor_control.set_conf_list ("Bridge", bridges);
190                 tor_control.set_conf_bool ("UseBridges", true);
191
192                 bool use = yield tor_control.get_conf_bool_async ("UseBridges");
193                 if (!use) {
194                         Hildon.Banner.show_information (null, null,
195                                                         "Failed to set up bridge relays");
196                 }
197         }
198
199         /**
200          * Start Tor and setup proxy settings
201          */
202         private void start_tor () {
203                 try {
204                         if (tor_pid == (Pid) 0) {
205                                 string[] tor_hash_argv = {
206                                         "/usr/sbin/tor",
207                                         "--hash-password", "",
208                                         null
209                                 };
210                                 var tv = TimeVal ();
211                                 Random.set_seed ((uint32) tv.tv_usec);
212                                 password = "tor-status-%8x".printf (Random.next_int ());
213                                 tor_hash_argv[2] = password;
214                                 string hash;
215                                 Process.spawn_sync ("/tmp", tor_hash_argv, null, 0, null, out hash);
216                                 hash = hash.str ("16:").replace ("\n", "");
217
218                                 if (hash == null) {
219                                         Hildon.Banner.show_information (null, null,
220                                                                         "Failed to get hash");
221                                         return;
222                                 }
223
224                                 string[] tor_argv = {
225                                         "/usr/sbin/tor",
226                                         "--ControlPort", "9051",
227                                         "--HashedControlPassword", "",
228                                         null
229                                 };
230                                 tor_argv[4] = hash;
231                                 Process.spawn_async_with_pipes ("/tmp",
232                                                                 tor_argv,
233                                                                 null,
234                                                                 SpawnFlags.SEARCH_PATH,
235                                                                 null,
236                                                                 out tor_pid,
237                                                                 null,
238                                                                 out tor_stdout);
239
240                                 var channel = new IOChannel.unix_new (tor_stdout);
241                                 channel.add_watch (IOCondition.IN | IOCondition.PRI | IOCondition.ERR | IOCondition.HUP | IOCondition.NVAL, tor_io_func);
242                         }
243                         if (polipo_pid == (Pid) 0) {
244                                 Process.spawn_async_with_pipes ("/tmp",
245                                                                 { "/usr/bin/polipo" },
246                                                                 null,
247                                                                 SpawnFlags.SEARCH_PATH,
248                                                                 null,
249                                                                 out polipo_pid);
250                         }
251
252                         /* --> proxy settings and will be set up and tor_connected will
253                          * be set to true once Tor signals 100%
254                          */
255                 } catch (SpawnError e) {
256                         Hildon.Banner.show_information (null, null, "DEBUG: Failed to spawn polipo and tor: %s".printf (e.message));
257                         return;
258                 }
259
260                 tor_log = "";
261                 if (log_label != null)
262                         log_label.label = tor_log;
263                 update_status ();
264         }
265
266         /**
267          * Stop Tor and revert proxy settings
268          */
269         private void stop_tor () {
270                 proxy_restore ();
271                 tor_connected = false;
272                 if (polipo_pid != (Pid) 0) {
273                         Process.close_pid (polipo_pid);
274                         Posix.kill ((Posix.pid_t) polipo_pid, Posix.SIGKILL);
275                         polipo_pid = (Pid) 0;
276                 }
277                 if (tor_pid != (Pid) 0) {
278                         Process.close_pid (tor_pid);
279                         Posix.kill ((Posix.pid_t) tor_pid, Posix.SIGKILL);
280                         tor_pid = (Pid) 0;
281                 }
282
283                 update_status ();
284         }
285
286         /**
287          * Setup proxy settings to route through the Tor network
288          */
289         private void proxy_setup () {
290                 if (backup == null) try {
291                         backup = new ProxyBackup ();
292                         backup.use_http_proxy = gconf.get_bool (GCONF_KEY_PROXY_HTTP_ENABLED);
293
294                         backup.http_host = gconf.get_string (GCONF_KEY_PROXY_HTTP_HOST);
295                         backup.socks_host = gconf.get_string (GCONF_KEY_PROXY_SOCKS_HOST);
296                         backup.secure_host = gconf.get_string (GCONF_KEY_PROXY_SECURE_HOST);
297                         backup.http_port = gconf.get_int (GCONF_KEY_PROXY_HTTP_PORT);
298                         backup.socks_port = gconf.get_int (GCONF_KEY_PROXY_SOCKS_PORT);
299                         backup.secure_port = gconf.get_int (GCONF_KEY_PROXY_SECURE_PORT);
300
301                         backup.mode = gconf.get_string (GCONF_KEY_PROXY_MODE);
302                 } catch (Error e) {
303                         error ("Error saving proxy settings: %s", e.message);
304                         backup = new ProxyBackup ();
305                         backup.use_http_proxy = false;
306
307                         backup.http_host = "";
308                         backup.socks_host = "";
309                         backup.secure_host = "";
310                         backup.http_port = 8080;
311                         backup.socks_port = 0;
312                         backup.secure_port = 0;
313
314                         backup.mode = "none";
315                 }
316                 try {
317                 //      Hildon.Banner.show_information (null, null, "DEBUG: Proxy setup");
318                         gconf.set_bool (GCONF_KEY_PROXY_HTTP_ENABLED, true);
319
320                         gconf.set_string (GCONF_KEY_PROXY_HTTP_HOST, "127.0.0.1");
321                         gconf.set_string (GCONF_KEY_PROXY_SOCKS_HOST, "127.0.0.1");
322                         gconf.set_string (GCONF_KEY_PROXY_SECURE_HOST, "127.0.0.1");
323                         gconf.set_int (GCONF_KEY_PROXY_HTTP_PORT, 8118);
324                         gconf.set_int (GCONF_KEY_PROXY_SOCKS_PORT, 9050);
325                         gconf.set_int (GCONF_KEY_PROXY_SECURE_PORT, 8118);
326
327                         gconf.set_string (GCONF_KEY_PROXY_MODE, "manual");
328                 } catch (Error e) {
329                         error ("Error changing proxy settings: %s", e.message);
330                 }
331         }
332
333         /**
334          * Revert proxy settings
335          */
336         private void proxy_restore () {
337                 if (backup != null) try {
338                 //      Hildon.Banner.show_information (null, null, "DEBUG: Restoring proxy settings");
339                         gconf.set_bool (GCONF_KEY_PROXY_HTTP_ENABLED, backup.use_http_proxy);
340
341                         gconf.set_string (GCONF_KEY_PROXY_HTTP_HOST, backup.http_host);
342                         gconf.set_string (GCONF_KEY_PROXY_SOCKS_HOST, backup.socks_host);
343                         gconf.set_string (GCONF_KEY_PROXY_SECURE_HOST, backup.secure_host);
344                         gconf.set_int (GCONF_KEY_PROXY_HTTP_PORT, backup.http_port);
345                         gconf.set_int (GCONF_KEY_PROXY_SOCKS_PORT, backup.socks_port);
346                         gconf.set_int (GCONF_KEY_PROXY_SECURE_PORT, backup.secure_port);
347
348                         gconf.set_string (GCONF_KEY_PROXY_MODE, backup.mode);
349                         backup = null;
350                 } catch (Error e) {
351                         error ("Error restoring proxy: %s", e.message);
352                 }
353         }
354
355         /**
356          * Show the bridge relay configuration dialog
357          */
358         private const int RESPONSE_NEW = 1;
359         private void bridges_clicked_cb () {
360                 var dialog = new Gtk.Dialog ();
361                 var content = (Gtk.VBox) dialog.get_content_area ();
362                 content.set_size_request (-1, 5*70);
363
364                 dialog.set_title (_("Bridge relays"));
365
366                 var bridges = new SList<string> ();
367                 try {
368                         bridges = gconf.get_list (GCONF_KEY_BRIDGES, GConf.ValueType.STRING);
369                 } catch (Error e) {
370                         Hildon.Banner.show_information (null, null, "Error loading bridges: %s".printf (e.message));
371                 }
372
373                 var list_store = new Gtk.ListStore (1, typeof (string));
374                 Gtk.TreeIter iter;
375                 foreach (string bridge in bridges) {
376                         list_store.append (out iter);
377                         list_store.@set (iter, 0, bridge);
378                 }
379
380                 var pannable_area = new Hildon.PannableArea ();
381                 var tree_view = new Gtk.TreeView.with_model (list_store);
382                 var renderer = new Gtk.CellRendererText ();
383                 var column = new Gtk.TreeViewColumn.with_attributes ("IP", renderer, "text", 0);
384                 tree_view.append_column (column);
385                 pannable_area.add (tree_view);
386                 content.pack_start (pannable_area, true, true, 0);
387
388                 tree_view.row_activated.connect ((path, column) => {
389                         bridge_edit_dialog (list_store, path);
390                 });
391
392                 dialog.add_button (_("New"), RESPONSE_NEW);
393                 dialog.response.connect ((response_id) => {
394                         if (response_id == RESPONSE_NEW) {
395                                 bridge_edit_dialog (list_store, null);
396                         }
397                 });
398
399                 dialog.show_all ();
400         }
401
402         /**
403          * Show the bridge relay edit dialog
404          */
405         private const int RESPONSE_DELETE = 1;
406         private void bridge_edit_dialog (Gtk.ListStore store, Gtk.TreePath? path) {
407                 var dialog = new Gtk.Dialog ();
408                 var content = (Gtk.VBox) dialog.get_content_area ();
409
410                 if (path == null)
411                         dialog.set_title (_("New bridge relay"));
412                 else
413                         dialog.set_title (_("Edit bridge relay"));
414
415                 var size_group = new Gtk.SizeGroup (Gtk.SizeGroupMode.HORIZONTAL);
416
417                 var hbox = new Gtk.HBox (false, Hildon.MARGIN_DOUBLE);
418                 var label = new Gtk.Label (_("IP address"));
419                 label.set_alignment (0, 0.5f);
420                 size_group.add_widget (label);
421                 hbox.pack_start (label, false, false, 0);
422                 var ip_entry = new Hildon.Entry (Hildon.SizeType.FINGER_HEIGHT);
423                 ip_entry.set ("hildon-input-mode", Hildon.GtkInputMode.NUMERIC |
424                                                    Hildon.GtkInputMode.SPECIAL);
425                 hbox.pack_start (ip_entry, true, true, 0);
426                 content.pack_start (hbox, false, false, 0);
427
428                 hbox = new Gtk.HBox (false, Hildon.MARGIN_DOUBLE);
429                 label = new Gtk.Label (_("Port"));
430                 label.set_alignment (0, 0.5f);
431                 size_group.add_widget (label);
432                 hbox.pack_start (label, false, false, 0);
433                 var port_entry = new Hildon.Entry (Hildon.SizeType.FINGER_HEIGHT);
434                 port_entry.set ("hildon-input-mode", Hildon.GtkInputMode.NUMERIC);
435                 hbox.pack_start (port_entry, true, true, 0);
436                 content.pack_start (hbox, true, true, 0);
437
438                 hbox = new Gtk.HBox (false, Hildon.MARGIN_DOUBLE);
439                 label = new Gtk.Label (_("Fingerprint"));
440                 label.set_alignment (0, 0.5f);
441                 size_group.add_widget (label);
442                 hbox.pack_start (label, false, false, 0);
443                 var fingerprint_entry = new Hildon.Entry (Hildon.SizeType.FINGER_HEIGHT);
444                 fingerprint_entry.set ("hildon-input-mode", Hildon.GtkInputMode.HEXA);
445                 hbox.pack_start (fingerprint_entry, true, true, 0);
446                 content.pack_start (hbox, true, true, 0);
447
448                 var iter = Gtk.TreeIter ();
449                 if (path == null) {
450                         port_entry.set_text ("443");
451                 } else if (store.get_iter (out iter, path)) {
452                         string tmp;
453                         store.@get (iter, 0, out tmp);
454                         string[] ip_port = tmp.split (":");
455                         if (ip_port.length == 2) {
456                                 ip_entry.set_text (ip_port[0]);
457                                 port_entry.set_text (ip_port[1]);
458                         }
459
460                         dialog.add_button (_("Delete"), RESPONSE_DELETE);
461                 }
462                 dialog.add_button (_("Save"), Gtk.ResponseType.OK);
463                 dialog.response.connect ((response_id) => {
464                         var bridges = new SList<string> ();
465
466                         if (response_id == RESPONSE_DELETE) {
467                                 if (path != null) {
468                                         Gtk.TreeIter iter2;
469                                         store.get_iter (out iter2, path);
470                                         store.remove (iter2);
471                                         string bridge;
472                                         if (store.get_iter_first (out iter2)) do {
473                                                 store.@get (iter2, 0, out bridge);
474                                                 bridges.append (bridge);
475                                         } while (store.iter_next (ref iter2));
476                                         try {
477                                                 gconf.set_list (GCONF_KEY_BRIDGES,
478                                                                 GConf.ValueType.STRING,
479                                                                 bridges);
480                                         } catch (Error e) {
481                                                 Hildon.Banner.show_information (dialog, null,
482                                                                                 "Failed to save bridge relay list: %s".printf (e.message));
483                                         }
484                                 }
485                                 dialog.destroy ();
486                         }
487                         if (response_id == Gtk.ResponseType.OK) {
488                                 if (!is_valid_ip_address (ip_entry.get_text ())) {
489                                         Hildon.Banner.show_information (dialog, null,
490                                                                         _("Invalid IP address"));
491                                         return;
492                                 }
493                                 int port = port_entry.get_text ().to_int ();
494                                 if (port < 0 || port > 65565) {
495                                         Hildon.Banner.show_information (dialog, null,
496                                                                         _("Invalid port number"));
497                                         return;
498                                 }
499                                 Gtk.TreeIter iter2;
500                                 if (path == null) {
501                                         store.append (out iter2);
502                                 } else {
503                                         store.get_iter (out iter2, path);
504                                 }
505                                 store.@set (iter2, 0, "%s:%d".printf (ip_entry.get_text (), port));
506                                 try {
507                                         bridges = gconf.get_list (GCONF_KEY_BRIDGES,
508                                                                   GConf.ValueType.STRING);
509                                 } catch (Error e) {
510                                         Hildon.Banner.show_information (null, null,
511                                                                         "Error loading bridges: %s".printf (e.message));
512                                 }
513                                 if (path == null) {
514                                         bridges.append ("%s:%d".printf (ip_entry.get_text (), port));
515                                 } else {
516                                         bridges = null;
517                                         string bridge;
518                                         if (store.get_iter_first (out iter2)) do {
519                                                 store.@get (iter2, 0, out bridge);
520                                                 bridges.append (bridge);
521                                         } while (store.iter_next (ref iter2));
522                                 }
523                                 try {
524                                         gconf.set_list (GCONF_KEY_BRIDGES,
525                                                         GConf.ValueType.STRING,
526                                                         bridges);
527                                 } catch (Error e) {
528                                                 Hildon.Banner.show_information (dialog, null,
529                                                                                 "Failed to save bridge relay list: %s".printf (e.message));
530                                 }
531
532                                 dialog.destroy ();
533                         }
534                 });
535
536                 dialog.show_all ();
537         }
538
539         /**
540          * Check whether the IP address consists of four numbers in the 0..255 range
541          */
542         bool is_valid_ip_address (string address) {
543                 string[] ip = address.split (".");
544
545                 if (ip.length != 4)
546                         return false;
547
548                 for (int i = 0; i < ip.length; i++) {
549                         int n = ip[i].to_int ();
550                         if (n < 0 || n > 255)
551                                 return false;
552                 }
553
554                 return true;
555         }
556
557         /**
558          * Show the Tor log dialog
559          */
560         private void show_tor_log () {
561                 var dialog = new Gtk.Dialog ();
562                 var content = (Gtk.VBox) dialog.get_content_area ();
563                 content.set_size_request (-1, 5*70);
564
565                 dialog.set_title (_("Log"));
566
567                 var pannable = new Hildon.PannableArea ();
568                 pannable.mov_mode = Hildon.MovementMode.BOTH;
569                 log_label = new Gtk.Label (tor_log);
570                 log_label.set_alignment (0, 0);
571                 pannable.add_with_viewport (log_label);
572                 content.pack_start (pannable, true, true, 0);
573
574                 dialog.response.connect (() => {
575                         log_label = null;
576                 });
577
578                 dialog.show_all ();
579         }
580
581         /**
582          * Callback for the status menu button clicked signal
583          */
584         private const int RESPONSE_LOG = 1;
585         private void button_clicked_cb () {
586                 var dialog = new Gtk.Dialog ();
587                 var content = (Gtk.VBox) dialog.get_content_area ();
588                 content.set_size_request (-1, 2*70);
589
590                 dialog.set_title (_("Tor: anonymity online"));
591
592                 var check = new Hildon.CheckButton (Hildon.SizeType.FINGER_HEIGHT);
593                 check.set_label (_("Enable onion routing"));
594                 check.set_active (tor_enabled);
595                 content.pack_start (check, true, true, 0);
596
597                 var button = new Hildon.Button.with_text (Hildon.SizeType.FINGER_HEIGHT,
598                                                           Hildon.ButtonArrangement.VERTICAL,
599                                                           _("Bridge relays"),
600                                                           get_bridge_list ());
601                 button.set_style (Hildon.ButtonStyle.PICKER);
602                 button.set_alignment (0, 0.5f, 0, 0.5f);
603                 button.clicked.connect (bridges_clicked_cb);
604                 content.pack_start (button, true, true, 0);
605
606                 dialog.add_button (_("Log"), RESPONSE_LOG);
607
608                 dialog.add_button (_("Save"), Gtk.ResponseType.ACCEPT);
609                 dialog.response.connect ((response_id) => {
610                         if (response_id == RESPONSE_LOG) {
611                                 show_tor_log ();
612                                 return;
613                         }
614                         if (response_id == Gtk.ResponseType.ACCEPT) {
615                                 if (!tor_enabled && check.get_active ()) {
616                                         tor_enabled = true;
617
618                                         if (conic_connected) {
619                                                 start_tor ();
620                                         } else {
621                                                 conic.connect (ConIc.ConnectFlags.NONE);
622                                         }
623                                 } else if (tor_enabled && !check.get_active ()) {
624                                         tor_enabled = false;
625
626                                         stop_tor ();
627                                         if (conic_connected)
628                                                 conic.disconnect ();
629                                 }
630                         }
631                         dialog.destroy ();
632                 });
633
634                 dialog.show_all ();
635         }
636
637         private string get_bridge_list () {
638                 string list = null;
639                 var bridges = new SList<string> ();
640                 try {
641                         bridges = gconf.get_list (GCONF_KEY_BRIDGES, GConf.ValueType.STRING);
642                 } catch (Error e) {
643                         error ("Error loading bridges: %s", e.message);
644                 }
645                 foreach (string bridge in bridges) {
646                         if (list == null)
647                                 list = bridge;
648                         else
649                                 list += ", " + bridge;
650                 }
651                 if (list == null)
652                         list = _("None");
653
654                 return list;
655         }
656
657         /**
658          * Callback for the ConIc connection-event signal
659          */
660         private void conic_connection_event_cb (ConIc.Connection conic, ConIc.ConnectionEvent event) {
661                 var status = event.get_status ();
662                 switch (status) {
663                 case ConIc.ConnectionStatus.CONNECTED:
664                         conic_connected = true;
665                         if (tor_enabled) {
666                                 start_tor ();
667                         } else {
668                                 update_status ();
669                         }
670                         break;
671                 case ConIc.ConnectionStatus.DISCONNECTING:
672                         conic_connected = false;
673                         stop_tor ();
674                         break;
675                 case ConIc.ConnectionStatus.DISCONNECTED:
676                 case ConIc.ConnectionStatus.NETWORK_UP:
677                         // ignore
678                         break;
679                 }
680
681                 var error = event.get_error ();
682                 switch (error) {
683                 case ConIc.ConnectionError.CONNECTION_FAILED:
684                         Hildon.Banner.show_information (null, null, "DEBUG: ConIc connection failed");
685                         break;
686                 case ConIc.ConnectionError.USER_CANCELED:
687                         Hildon.Banner.show_information (null, null, "DEBUG: ConIc user canceled");
688                         break;
689                 case ConIc.ConnectionError.NONE:
690                 case ConIc.ConnectionError.INVALID_IAP:
691                         // ignore
692                         break;
693                 }
694         }
695
696         private void create_widgets () {
697                 // Status menu button
698                 button = new Hildon.Button.with_text (Hildon.SizeType.FINGER_HEIGHT,
699                                                       Hildon.ButtonArrangement.VERTICAL,
700                                                       _("The Onion Router"),
701                                                       tor_enabled ? _("Enabled") : _("Disabled"));
702                 button.set_alignment (0.0f, 0.5f, 1.0f, 1.0f);
703                 button.set_style (Hildon.ButtonStyle.PICKER);
704                 button.clicked.connect (button_clicked_cb);
705
706                 add (button);
707
708                 log_label = null;
709
710                 // Status area icon
711                 update_status ();
712
713                 show_all ();
714         }
715
716         construct {
717                 // Gettext hook-up
718                 Intl.setlocale (LocaleCategory.ALL, "");
719                 Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
720                 Intl.textdomain (Config.GETTEXT_PACKAGE);
721
722                 // GConf hook-up
723                 gconf = GConf.Client.get_default ();
724                 try {
725                         tor_enabled = gconf.get_bool (GCONF_KEY_TOR_ENABLED);
726                 } catch (Error e) {
727                         error ("Failed to get GConf setting: %s", e.message);
728                 }
729                 tor_connected = false;
730
731                 // ConIc hook-up
732                 conic = new ConIc.Connection ();
733                 if (conic == null) {
734                         Hildon.Banner.show_information (null, null, "DEBUG: ConIc hook-up failed");
735                 }
736                 conic_connected = false;
737                 conic.automatic_connection_events = true;
738                 if (tor_enabled)
739                         conic.connect (ConIc.ConnectFlags.AUTOMATICALLY_TRIGGERED);
740                 conic.connection_event.connect (conic_connection_event_cb);
741
742                 // Osso hook-up
743                 osso = new Osso.Context (STATUSMENU_TOR_LIBOSSO_SERVICE_NAME,
744                                          Config.VERSION,
745                                          true,
746                                          null);
747
748                 create_widgets ();
749         }
750 }
751
752 /**
753  * Vala code can't use the HD_DEFINE_PLUGIN_MODULE macro, but it handles
754  * most of the class registration issues itself. Only this code from
755  * HD_PLUGIN_MODULE_SYMBOLS_CODE has to be has to be included manually
756  * to register with hildon-desktop:
757  */
758 [ModuleInit]
759 public void hd_plugin_module_load (TypeModule plugin) {
760         // [ModuleInit] registers types automatically
761         ((HD.PluginModule) plugin).add_type (typeof (TorStatusMenuItem));
762 }
763
764 public void hd_plugin_module_unload (HD.PluginModule plugin) {
765 }