Update the log while the viewer is opened
[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                                         store.remove (iter);
469                                         string bridge;
470                                         if (store.get_iter_first (out iter)) do {
471                                                 store.@get (iter, 0, out bridge);
472                                                 bridges.append (bridge);
473                                         } while (store.iter_next (ref iter));
474                                         try {
475                                                 gconf.set_list (GCONF_KEY_BRIDGES,
476                                                                 GConf.ValueType.STRING,
477                                                                 bridges);
478                                         } catch (Error e) {
479                                                 Hildon.Banner.show_information (dialog, null,
480                                                                                 "Failed to save bridge relay list: %s".printf (e.message));
481                                         }
482                                 }
483                                 dialog.destroy ();
484                         }
485                         if (response_id == Gtk.ResponseType.OK) {
486                                 if (!is_valid_ip_address (ip_entry.get_text ())) {
487                                         Hildon.Banner.show_information (dialog, null,
488                                                                         _("Invalid IP address"));
489                                         return;
490                                 }
491                                 int port = port_entry.get_text ().to_int ();
492                                 if (port < 0 || port > 65565) {
493                                         Hildon.Banner.show_information (dialog, null,
494                                                                         _("Invalid port number"));
495                                         return;
496                                 }
497                                 if (path == null) {
498                                         store.append (out iter);
499                                 }
500                                 store.@set (iter, 0, "%s:%d".printf (ip_entry.get_text (), port));
501                                 try {
502                                         bridges = gconf.get_list (GCONF_KEY_BRIDGES,
503                                                                   GConf.ValueType.STRING);
504                                 } catch (Error e) {
505                                         Hildon.Banner.show_information (null, null,
506                                                                         "Error loading bridges: %s".printf (e.message));
507                                 }
508                                 if (path == null) {
509                                         bridges.append ("%s:%d".printf (ip_entry.get_text (), port));
510                                 } else {
511                                         bridges = null;
512                                         string bridge;
513                                         if (store.get_iter_first (out iter)) do {
514                                                 store.@get (iter, 0, out bridge);
515                                                 bridges.append (bridge);
516                                         } while (store.iter_next (ref iter));
517                                 }
518                                 try {
519                                         gconf.set_list (GCONF_KEY_BRIDGES,
520                                                         GConf.ValueType.STRING,
521                                                         bridges);
522                                 } catch (Error e) {
523                                                 Hildon.Banner.show_information (dialog, null,
524                                                                                 "Failed to save bridge relay list: %s".printf (e.message));
525                                 }
526
527                                 dialog.destroy ();
528                         }
529                 });
530
531                 dialog.show_all ();
532         }
533
534         /**
535          * Check whether the IP address consists of four numbers in the 0..255 range
536          */
537         bool is_valid_ip_address (string address) {
538                 string[] ip = address.split (".");
539
540                 if (ip.length != 4)
541                         return false;
542
543                 for (int i = 0; i < ip.length; i++) {
544                         int n = ip[i].to_int ();
545                         if (n < 0 || n > 255)
546                                 return false;
547                 }
548
549                 return true;
550         }
551
552         /**
553          * Show the Tor log dialog
554          */
555         private void show_tor_log () {
556                 var dialog = new Gtk.Dialog ();
557                 var content = (Gtk.VBox) dialog.get_content_area ();
558                 content.set_size_request (-1, 5*70);
559
560                 dialog.set_title (_("Log"));
561
562                 var pannable = new Hildon.PannableArea ();
563                 pannable.mov_mode = Hildon.MovementMode.BOTH;
564                 log_label = new Gtk.Label (tor_log);
565                 log_label.set_alignment (0, 0);
566                 pannable.add_with_viewport (log_label);
567                 content.pack_start (pannable, true, true, 0);
568
569                 dialog.response.connect (() => {
570                         log_label = null;
571                 });
572
573                 dialog.show_all ();
574         }
575
576         /**
577          * Callback for the status menu button clicked signal
578          */
579         private const int RESPONSE_LOG = 1;
580         private void button_clicked_cb () {
581                 var dialog = new Gtk.Dialog ();
582                 var content = (Gtk.VBox) dialog.get_content_area ();
583                 content.set_size_request (-1, 2*70);
584
585                 dialog.set_title (_("Tor: anonymity online"));
586
587                 var check = new Hildon.CheckButton (Hildon.SizeType.FINGER_HEIGHT);
588                 check.set_label (_("Enable onion routing"));
589                 check.set_active (tor_enabled);
590                 content.pack_start (check, true, true, 0);
591
592                 var button = new Hildon.Button.with_text (Hildon.SizeType.FINGER_HEIGHT,
593                                                           Hildon.ButtonArrangement.VERTICAL,
594                                                           _("Bridge relays"),
595                                                           get_bridge_list ());
596                 button.set_style (Hildon.ButtonStyle.PICKER);
597                 button.set_alignment (0, 0.5f, 0, 0.5f);
598                 button.clicked.connect (bridges_clicked_cb);
599                 content.pack_start (button, true, true, 0);
600
601                 dialog.add_button (_("Log"), RESPONSE_LOG);
602
603                 dialog.add_button (_("Save"), Gtk.ResponseType.ACCEPT);
604                 dialog.response.connect ((response_id) => {
605                         if (response_id == RESPONSE_LOG) {
606                                 show_tor_log ();
607                                 return;
608                         }
609                         if (response_id == Gtk.ResponseType.ACCEPT) {
610                                 if (!tor_enabled && check.get_active ()) {
611                                         tor_enabled = true;
612
613                                         if (conic_connected) {
614                                                 start_tor ();
615                                         } else {
616                                                 conic.connect (ConIc.ConnectFlags.NONE);
617                                         }
618                                 } else if (tor_enabled && !check.get_active ()) {
619                                         tor_enabled = false;
620
621                                         stop_tor ();
622                                         if (conic_connected)
623                                                 conic.disconnect ();
624                                 }
625                         }
626                         dialog.destroy ();
627                 });
628
629                 dialog.show_all ();
630         }
631
632         private string get_bridge_list () {
633                 string list = null;
634                 var bridges = new SList<string> ();
635                 try {
636                         bridges = gconf.get_list (GCONF_KEY_BRIDGES, GConf.ValueType.STRING);
637                 } catch (Error e) {
638                         error ("Error loading bridges: %s", e.message);
639                 }
640                 foreach (string bridge in bridges) {
641                         if (list == null)
642                                 list = bridge;
643                         else
644                                 list += ", " + bridge;
645                 }
646                 if (list == null)
647                         list = _("None");
648
649                 return list;
650         }
651
652         /**
653          * Callback for the ConIc connection-event signal
654          */
655         private void conic_connection_event_cb (ConIc.Connection conic, ConIc.ConnectionEvent event) {
656                 var status = event.get_status ();
657                 switch (status) {
658                 case ConIc.ConnectionStatus.CONNECTED:
659                         conic_connected = true;
660                         if (tor_enabled) {
661                                 start_tor ();
662                         } else {
663                                 update_status ();
664                         }
665                         break;
666                 case ConIc.ConnectionStatus.DISCONNECTING:
667                         conic_connected = false;
668                         stop_tor ();
669                         break;
670                 case ConIc.ConnectionStatus.DISCONNECTED:
671                 case ConIc.ConnectionStatus.NETWORK_UP:
672                         // ignore
673                         break;
674                 }
675
676                 var error = event.get_error ();
677                 switch (error) {
678                 case ConIc.ConnectionError.CONNECTION_FAILED:
679                         Hildon.Banner.show_information (null, null, "DEBUG: ConIc connection failed");
680                         break;
681                 case ConIc.ConnectionError.USER_CANCELED:
682                         Hildon.Banner.show_information (null, null, "DEBUG: ConIc user canceled");
683                         break;
684                 case ConIc.ConnectionError.NONE:
685                 case ConIc.ConnectionError.INVALID_IAP:
686                         // ignore
687                         break;
688                 }
689         }
690
691         private void create_widgets () {
692                 // Status menu button
693                 button = new Hildon.Button.with_text (Hildon.SizeType.FINGER_HEIGHT,
694                                                       Hildon.ButtonArrangement.VERTICAL,
695                                                       _("The Onion Router"),
696                                                       tor_enabled ? _("Enabled") : _("Disabled"));
697                 button.set_alignment (0.0f, 0.5f, 1.0f, 1.0f);
698                 button.set_style (Hildon.ButtonStyle.PICKER);
699                 button.clicked.connect (button_clicked_cb);
700
701                 add (button);
702
703                 log_label = null;
704
705                 // Status area icon
706                 update_status ();
707
708                 show_all ();
709         }
710
711         construct {
712                 // Gettext hook-up
713                 Intl.setlocale (LocaleCategory.ALL, "");
714                 Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
715                 Intl.textdomain (Config.GETTEXT_PACKAGE);
716
717                 // GConf hook-up
718                 gconf = GConf.Client.get_default ();
719                 try {
720                         tor_enabled = gconf.get_bool (GCONF_KEY_TOR_ENABLED);
721                 } catch (Error e) {
722                         error ("Failed to get GConf setting: %s", e.message);
723                 }
724                 tor_connected = false;
725
726                 // ConIc hook-up
727                 conic = new ConIc.Connection ();
728                 if (conic == null) {
729                         Hildon.Banner.show_information (null, null, "DEBUG: ConIc hook-up failed");
730                 }
731                 conic_connected = false;
732                 conic.automatic_connection_events = true;
733                 if (tor_enabled)
734                         conic.connect (ConIc.ConnectFlags.AUTOMATICALLY_TRIGGERED);
735                 conic.connection_event.connect (conic_connection_event_cb);
736
737                 // Osso hook-up
738                 osso = new Osso.Context (STATUSMENU_TOR_LIBOSSO_SERVICE_NAME,
739                                          Config.VERSION,
740                                          true,
741                                          null);
742
743                 create_widgets ();
744         }
745 }
746
747 /**
748  * Vala code can't use the HD_DEFINE_PLUGIN_MODULE macro, but it handles
749  * most of the class registration issues itself. Only this code from
750  * HD_PLUGIN_MODULE_SYMBOLS_CODE has to be has to be included manually
751  * to register with hildon-desktop:
752  */
753 [ModuleInit]
754 public void hd_plugin_module_load (TypeModule plugin) {
755         // [ModuleInit] registers types automatically
756         ((HD.PluginModule) plugin).add_type (typeof (TorStatusMenuItem));
757 }
758
759 public void hd_plugin_module_unload (HD.PluginModule plugin) {
760 }