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