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