Install icons in the correct place
[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
40         private const string GCONF_DIR_PROXY_HTTP         = "/system/http_proxy";
41         private const string GCONF_KEY_PROXY_HTTP_ENABLED = GCONF_DIR_PROXY_HTTP + "/use_http_proxy";
42         private const string GCONF_KEY_PROXY_HTTP_HOST    = GCONF_DIR_PROXY_HTTP + "/host";
43         private const string GCONF_KEY_PROXY_HTTP_PORT    = GCONF_DIR_PROXY_HTTP + "/port";
44
45         private const string GCONF_DIR_PROXY             = "/system/proxy";
46         private const string GCONF_KEY_PROXY_MODE        = GCONF_DIR_PROXY + "/mode";
47         private const string GCONF_KEY_PROXY_SOCKS_HOST  = GCONF_DIR_PROXY + "/socks_host";
48         private const string GCONF_KEY_PROXY_SOCKS_PORT  = GCONF_DIR_PROXY + "/socks_port";
49         private const string GCONF_KEY_PROXY_SECURE_HOST = GCONF_DIR_PROXY + "/secure_host";
50         private const string GCONF_KEY_PROXY_SECURE_PORT = GCONF_DIR_PROXY + "/secure_port";
51
52         // Widgets
53         Hildon.Button button;
54
55         // Icons
56         Gdk.Pixbuf icon_connecting;
57         Gdk.Pixbuf icon_connected;
58
59         // ConIc, GConf and Osso context
60         Osso.Context osso;
61         GConf.Client gconf;
62         ConIc.Connection conic;
63         bool conic_connected;
64
65         // Internal state
66         bool tor_enabled;
67         bool tor_connected;
68         Pid tor_pid;
69         int tor_stdout;
70         Pid polipo_pid;
71         ProxyBackup backup;
72
73         /**
74          * Update status area icon and status menu button value
75          */
76         private void update_status () {
77                 Gtk.IconTheme icon_theme;
78                 Gdk.Pixbuf pixbuf;
79
80                 if (tor_enabled && tor_connected && icon_connected == null) try {
81                         icon_theme = Gtk.IconTheme.get_default ();
82                         pixbuf = icon_theme.load_icon ("statusarea_tor_connected",
83                                                        STATUS_AREA_ICON_SIZE,
84                                                        Gtk.IconLookupFlags.NO_SVG);
85                         icon_connected = pixbuf;
86                 } catch (Error e) {
87                         error (e.message);
88                 }
89                 if (tor_enabled && !tor_connected && icon_connecting == null) try {
90                         icon_theme = Gtk.IconTheme.get_default ();
91                         pixbuf = icon_theme.load_icon ("statusarea_tor_connecting",
92                                                        STATUS_AREA_ICON_SIZE,
93                                                        Gtk.IconLookupFlags.NO_SVG);
94                         icon_connecting = pixbuf;
95                 } catch (Error e) {
96                         error (e.message);
97                 }
98
99                 if (conic_connected && tor_enabled) {
100                         pixbuf = tor_connected ? icon_connected : icon_connecting;
101                         button.set_value (tor_connected ? _("Connected") : _("Connecting ..."));
102                 } else {
103                         pixbuf = null;
104                         button.set_value (tor_enabled ? _("Disconnected") : _("Disabled"));
105                 }
106                 set_status_area_icon (pixbuf);
107         }
108
109         /**
110          * Callback for Tor daemon line output
111          */
112         private bool tor_io_func (IOChannel source, IOCondition condition) {
113
114                 if ((condition & (IOCondition.IN | IOCondition.PRI)) != 0) {
115                         string line = null;
116                         size_t length;
117                         try {
118                                 /* var status = */ source.read_line (out line, out length, null);
119
120                                 if ("[notice]" in line) {
121                                         if ("Bootstrapped 100%" in line) {
122                                                 tor_connected = true;
123                                                 proxy_setup ();
124                                                 update_status ();
125                                         }
126                                 } else {
127                                         // FIXME
128                                         Hildon.Banner.show_information (null, null, "DEBUG: %s".printf (line));
129                                 }
130                         } catch (Error e) {
131                                 // FIXME
132                                 Hildon.Banner.show_information (null, null, "Error: %s".printf (e.message));
133                         }
134                 }
135                 if ((condition & (IOCondition.ERR | IOCondition.HUP | IOCondition.NVAL)) != 0) {
136                         return false;
137                 }
138                 return true;
139         }
140
141         /**
142          * Start Tor and setup proxy settings
143          */
144         private void start_tor () {
145                 try {
146                         if (tor_pid == (Pid) 0) {
147                                 Process.spawn_async_with_pipes ("/tmp",
148                                                                 { "/usr/sbin/tor" },
149                                                                 null,
150                                                                 SpawnFlags.SEARCH_PATH,
151                                                                 null,
152                                                                 out tor_pid,
153                                                                 null,
154                                                                 out tor_stdout);
155
156                                 var channel = new IOChannel.unix_new (tor_stdout);
157                                 channel.add_watch (IOCondition.IN | IOCondition.PRI | IOCondition.ERR | IOCondition.HUP | IOCondition.NVAL, tor_io_func);
158                         }
159                         if (polipo_pid == (Pid) 0) {
160                                 Process.spawn_async_with_pipes ("/tmp",
161                                                                 { "/usr/bin/polipo" },
162                                                                 null,
163                                                                 SpawnFlags.SEARCH_PATH,
164                                                                 null,
165                                                                 out polipo_pid);
166                         }
167
168                         /* --> proxy settings and will be set up and tor_connected will
169                          * be set to true once Tor signals 100%
170                          */
171                 } catch (SpawnError e) {
172                         error ("Failed to spawn polipo and tor: %s", e.message);
173                         return;
174                 }
175
176                 update_status ();
177         }
178
179         /**
180          * Stop Tor and revert proxy settings
181          */
182         private void stop_tor () {
183                 proxy_restore ();
184                 tor_connected = false;
185                 if (polipo_pid != (Pid) 0) {
186                         Process.close_pid (polipo_pid);
187                         Posix.kill ((Posix.pid_t) polipo_pid, Posix.SIGKILL);
188                         polipo_pid = (Pid) 0;
189                 }
190                 if (tor_pid != (Pid) 0) {
191                         Process.close_pid (tor_pid);
192                         Posix.kill ((Posix.pid_t) tor_pid, Posix.SIGKILL);
193                         tor_pid = (Pid) 0;
194                 }
195
196                 update_status ();
197         }
198
199         /**
200          * Setup proxy settings to route through the Tor network
201          */
202         private void proxy_setup () {
203                 if (backup == null) try {
204                         backup = new ProxyBackup ();
205                         backup.use_http_proxy = gconf.get_bool (GCONF_KEY_PROXY_HTTP_ENABLED);
206
207                         backup.http_host = gconf.get_string (GCONF_KEY_PROXY_HTTP_HOST);
208                         backup.socks_host = gconf.get_string (GCONF_KEY_PROXY_SOCKS_HOST);
209                         backup.secure_host = gconf.get_string (GCONF_KEY_PROXY_SECURE_HOST);
210                         backup.http_port = gconf.get_int (GCONF_KEY_PROXY_HTTP_PORT);
211                         backup.socks_port = gconf.get_int (GCONF_KEY_PROXY_SOCKS_PORT);
212                         backup.secure_port = gconf.get_int (GCONF_KEY_PROXY_SECURE_PORT);
213
214                         backup.mode = gconf.get_string (GCONF_KEY_PROXY_MODE);
215                 } catch (Error e) {
216                         error ("Error saving proxy settings: %s", e.message);
217                         backup = new ProxyBackup ();
218                         backup.use_http_proxy = false;
219
220                         backup.http_host = "";
221                         backup.socks_host = "";
222                         backup.secure_host = "";
223                         backup.http_port = 8080;
224                         backup.socks_port = 0;
225                         backup.secure_port = 0;
226
227                         backup.mode = "none";
228                 }
229                 try {
230                 //      Hildon.Banner.show_information (null, null, "DEBUG: Proxy setup");
231                         gconf.set_bool (GCONF_KEY_PROXY_HTTP_ENABLED, true);
232
233                         gconf.set_string (GCONF_KEY_PROXY_HTTP_HOST, "127.0.0.1");
234                         gconf.set_string (GCONF_KEY_PROXY_SOCKS_HOST, "127.0.0.1");
235                         gconf.set_string (GCONF_KEY_PROXY_SECURE_HOST, "127.0.0.1");
236                         gconf.set_int (GCONF_KEY_PROXY_HTTP_PORT, 8118);
237                         gconf.set_int (GCONF_KEY_PROXY_SOCKS_PORT, 9050);
238                         gconf.set_int (GCONF_KEY_PROXY_SECURE_PORT, 8118);
239
240                         gconf.set_string (GCONF_KEY_PROXY_MODE, "manual");
241                 } catch (Error e) {
242                         error ("Error changing proxy settings: %s", e.message);
243                 }
244         }
245
246         /**
247          * Revert proxy settings
248          */
249         private void proxy_restore () {
250                 if (backup != null) try {
251                 //      Hildon.Banner.show_information (null, null, "DEBUG: Restoring proxy settings");
252                         gconf.set_bool (GCONF_KEY_PROXY_HTTP_ENABLED, backup.use_http_proxy);
253
254                         gconf.set_string (GCONF_KEY_PROXY_HTTP_HOST, backup.http_host);
255                         gconf.set_string (GCONF_KEY_PROXY_SOCKS_HOST, backup.socks_host);
256                         gconf.set_string (GCONF_KEY_PROXY_SECURE_HOST, backup.secure_host);
257                         gconf.set_int (GCONF_KEY_PROXY_HTTP_PORT, backup.http_port);
258                         gconf.set_int (GCONF_KEY_PROXY_SOCKS_PORT, backup.socks_port);
259                         gconf.set_int (GCONF_KEY_PROXY_SECURE_PORT, backup.secure_port);
260
261                         gconf.set_string (GCONF_KEY_PROXY_MODE, backup.mode);
262                         backup = null;
263                 } catch (Error e) {
264                         error ("Error restoring proxy: %s", e.message);
265                 }
266         }
267
268         /**
269          * Callback for the status menu button clicked signal
270          */
271         private void button_clicked_cb () {
272                 var dialog = new Gtk.Dialog ();
273                 var content = (Gtk.VBox) dialog.get_content_area ();
274
275                 dialog.set_title (_("Tor: anonymity online"));
276
277                 var check = new Hildon.CheckButton (Hildon.SizeType.FINGER_HEIGHT);
278                 check.set_label (_("Enable onion routing"));
279                 check.set_active (tor_enabled);
280                 content.pack_start (check, true, true, 0);
281
282                 dialog.add_button (_("Save"), Gtk.ResponseType.ACCEPT);
283                 dialog.response.connect ((response_id) => {
284                         if (response_id == Gtk.ResponseType.ACCEPT) {
285                                 if (!tor_enabled && check.get_active ()) {
286                                         tor_enabled = true;
287
288                                         if (conic_connected) {
289                                                 start_tor ();
290                                         } else {
291                                                 conic.connect (ConIc.ConnectFlags.NONE);
292                                         }
293                                 } else if (tor_enabled && !check.get_active ()) {
294                                         tor_enabled = false;
295
296                                         stop_tor ();
297                                         if (conic_connected)
298                                                 conic.disconnect ();
299                                 }
300                         }
301                         dialog.destroy ();
302                 });
303
304                 dialog.show_all ();
305         }
306
307         /**
308          * Callback for the ConIc connection-event signal
309          */
310         private void conic_connection_event_cb (ConIc.Connection conic, ConIc.ConnectionEvent event) {
311                 var status = event.get_status ();
312                 switch (status) {
313                 case ConIc.ConnectionStatus.CONNECTED:
314                         conic_connected = true;
315                         if (tor_enabled) {
316                                 start_tor ();
317                         } else {
318                                 update_status ();
319                         }
320                         break;
321                 case ConIc.ConnectionStatus.DISCONNECTING:
322                         conic_connected = false;
323                         stop_tor ();
324                         break;
325                 case ConIc.ConnectionStatus.DISCONNECTED:
326                 case ConIc.ConnectionStatus.NETWORK_UP:
327                         // ignore
328                         break;
329                 }
330
331                 var error = event.get_error ();
332                 switch (error) {
333                 case ConIc.ConnectionError.CONNECTION_FAILED:
334                         Hildon.Banner.show_information (null, null, "DEBUG: ConIc connection failed");
335                         break;
336                 case ConIc.ConnectionError.USER_CANCELED:
337                         Hildon.Banner.show_information (null, null, "DEBUG: ConIc user canceled");
338                         break;
339                 case ConIc.ConnectionError.NONE:
340                 case ConIc.ConnectionError.INVALID_IAP:
341                         // ignore
342                         break;
343                 }
344         }
345
346         private void create_widgets () {
347                 Gtk.IconTheme icon_theme;
348                 Gdk.Pixbuf pixbuf;
349                 Gtk.Image image;
350
351                 // Status menu button
352                 button = new Hildon.Button.with_text (Hildon.SizeType.FINGER_HEIGHT,
353                                                       Hildon.ButtonArrangement.VERTICAL,
354                                                       _("The Onion Router"),
355                                                       tor_enabled ? _("Enabled") : _("Disabled"));
356                 icon_theme = Gtk.IconTheme.get_default();
357                 try {
358                         pixbuf = icon_theme.load_icon ("tor_onion",
359                                                        STATUS_MENU_ICON_SIZE,
360                                                        Gtk.IconLookupFlags.NO_SVG);
361                         image = new Gtk.Image.from_pixbuf (pixbuf);
362                         button.set_image (image);
363                 } catch (Error e) {
364                         error (e.message);
365                 }
366                 button.set_alignment (0.0f, 0.5f, 1.0f, 1.0f);
367                 button.set_style (Hildon.ButtonStyle.PICKER);
368                 button.clicked.connect (button_clicked_cb);
369
370                 add (button);
371
372                 // Status area icon
373                 update_status ();
374
375                 show_all ();
376         }
377
378         construct {
379                 // Gettext hook-up
380                 Intl.setlocale (LocaleCategory.ALL, "");
381                 Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
382                 Intl.textdomain (Config.GETTEXT_PACKAGE);
383
384                 // GConf hook-up
385                 gconf = GConf.Client.get_default ();
386                 try {
387                         tor_enabled = gconf.get_bool (GCONF_KEY_TOR_ENABLED);
388                 } catch (Error e) {
389                         error ("Failed to get GConf setting: %s", e.message);
390                 }
391                 tor_connected = false;
392
393                 // ConIc hook-up
394                 conic = new ConIc.Connection ();
395                 if (conic == null) {
396                         Hildon.Banner.show_information (null, null, "DEBUG: ConIc hook-up failed");
397                 }
398                 conic_connected = false;
399                 conic.automatic_connection_events = true;
400                 if (tor_enabled)
401                         conic.connect (ConIc.ConnectFlags.AUTOMATICALLY_TRIGGERED);
402                 conic.connection_event.connect (conic_connection_event_cb);
403
404                 // Osso hook-up
405                 osso = new Osso.Context (STATUSMENU_TOR_LIBOSSO_SERVICE_NAME,
406                                          Config.VERSION,
407                                          true,
408                                          null);
409
410                 create_widgets ();
411         }
412 }
413
414 /**
415  * Vala code can't use the HD_DEFINE_PLUGIN_MODULE macro, but it handles
416  * most of the class registration issues itself. Only this code from
417  * HD_PLUGIN_MODULE_SYMBOLS_CODE has to be has to be included manually
418  * to register with hildon-desktop:
419  */
420 [ModuleInit]
421 public void hd_plugin_module_load (TypeModule plugin) {
422         // [ModuleInit] registers types automatically
423         ((HD.PluginModule) plugin).add_type (typeof (TorStatusMenuItem));
424 }
425
426 public void hd_plugin_module_unload (HD.PluginModule plugin) {
427 }