Initial commit: 0.0.1
authorPhilipp Zabel <philipp.zabel@gmail.com>
Tue, 19 Jan 2010 16:03:30 +0000 (17:03 +0100)
committerPhilipp Zabel <philipp.zabel@gmail.com>
Tue, 19 Jan 2010 16:03:30 +0000 (17:03 +0100)
Makefile [new file with mode: 0644]
data/status-area-applet-tor.desktop [new file with mode: 0644]
data/tor_onion.png [new file with mode: 0644]
data/tor_status_connected.png [new file with mode: 0644]
data/tor_status_connecting.png [new file with mode: 0644]
data/tor_status_disabled.png [new file with mode: 0644]
src/status-area-applet-tor.vala [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..0c312a1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,37 @@
+VERSION=0.0.1
+CFLAGS= -Wall -pedantic
+
+all: status-area-applet-tor.so
+
+ICON_DIR=$(DESTDIR)`pkg-config libhildondesktop-1 --variable=prefix`/share/icons/hicolor
+SM_LIB_DIR=$(DESTDIR)`pkg-config libhildondesktop-1 --variable=hildondesktoplibdir`
+SM_DESKTOP_DIR=$(DESTDIR)`pkg-config libhildondesktop-1 --variable=hildonstatusmenudesktopentrydir`
+
+install:
+       mkdir -p $(ICON_DIR)/18x18/hildon/
+       mkdir -p $(ICON_DIR)/48x48/hildon/
+       mkdir -p $(SM_LIB_DIR)
+       mkdir -p $(SM_DESKTOP_DIR)
+       cp data/tor_status_connected.png $(ICON_DIR)/18x18/hildon/
+       cp data/tor_status_disabled.png $(ICON_DIR)/18x18/hildon/
+       cp data/tor_status_connecting.png $(ICON_DIR)/18x18/hildon/
+       cp data/tor_onion.png $(ICON_DIR)/48x48/hildon/tor_onion.png
+       cp data/status-area-applet-tor.desktop $(SM_DESKTOP_DIR)
+       cp status-area-applet-tor.so $(SM_LIB_DIR)
+
+uninstall:
+       rm $(SM_LIB_DIR)/status-area-applet-tor.so
+       rm $(SM_DESKTOP_DIR)/status-area-applet-tor.desktop
+       rm $(ICON_DIR)/18x18/hildon/tor_status_connected.png
+       rm $(ICON_DIR)/18x18/hildon/tor_status_disabled.png
+       rm $(ICON_DIR)/18x18/hildon/tor_status_connecting.png
+       rm $(ICON_DIR)/48x48/hildon/tor_onion.png
+
+status-area-applet-tor.so: src/status-area-applet-tor.c
+       $(CC) -shared -Wall `pkg-config libhildondesktop-1 gconf-2.0 conic --cflags --libs` -o $@ $^
+
+src/status-area-applet-tor.c: src/status-area-applet-tor.vala
+       valac -C --vapidir ./vapi --pkg conic --pkg gconf-2.0 --pkg hildon-1 --pkg libhildondesktop-1 --pkg libosso --pkg posix $^
+
+clean:
+       -rm -f status-area-applet-tor.so src/status-area-applet-tor.c
diff --git a/data/status-area-applet-tor.desktop b/data/status-area-applet-tor.desktop
new file mode 100644 (file)
index 0000000..550f95b
--- /dev/null
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Name=Tor
+Comment=Tor Status applet
+Type=default
+X-Path=status-area-applet-tor.so
diff --git a/data/tor_onion.png b/data/tor_onion.png
new file mode 100644 (file)
index 0000000..29935de
Binary files /dev/null and b/data/tor_onion.png differ
diff --git a/data/tor_status_connected.png b/data/tor_status_connected.png
new file mode 100644 (file)
index 0000000..24ef83d
Binary files /dev/null and b/data/tor_status_connected.png differ
diff --git a/data/tor_status_connecting.png b/data/tor_status_connecting.png
new file mode 100644 (file)
index 0000000..a9333fe
Binary files /dev/null and b/data/tor_status_connecting.png differ
diff --git a/data/tor_status_disabled.png b/data/tor_status_disabled.png
new file mode 100644 (file)
index 0000000..30e71ca
Binary files /dev/null and b/data/tor_status_disabled.png differ
diff --git a/src/status-area-applet-tor.vala b/src/status-area-applet-tor.vala
new file mode 100644 (file)
index 0000000..51972b3
--- /dev/null
@@ -0,0 +1,431 @@
+/* This file is part of status-area-applet-tor.
+ *
+ * Copyright (C) 2010 Philipp Zabel
+ *
+ * status-area-applet-tor is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * status-area-applet-tor is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with status-area-applet-tor. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+[Compact]
+class ProxyBackup {
+       public bool use_http_proxy;
+       public string http_host;
+       public string socks_host;
+       public string secure_host;
+       public int http_port;
+       public int socks_port;
+       public int secure_port;
+       public string mode;
+}
+
+class TorStatusMenuItem : HD.StatusMenuItem {
+       private const string STATUSMENU_TOR_LIBOSSO_SERVICE_NAME = "tor_status_menu_item";
+
+       private const int STATUS_MENU_ICON_SIZE = 48;
+       private const int STATUS_AREA_ICON_SIZE = 18;
+
+       private const string GCONF_DIR_TOR         = "/apps/maemo/tor";
+       private const string GCONF_KEY_TOR_ENABLED = GCONF_DIR_TOR + "/enabled";
+
+       private const string GCONF_DIR_PROXY_HTTP         = "/system/http_proxy";
+       private const string GCONF_KEY_PROXY_HTTP_ENABLED = GCONF_DIR_PROXY_HTTP + "/use_http_proxy";
+       private const string GCONF_KEY_PROXY_HTTP_HOST    = GCONF_DIR_PROXY_HTTP + "/host";
+       private const string GCONF_KEY_PROXY_HTTP_PORT    = GCONF_DIR_PROXY_HTTP + "/port";
+
+       private const string GCONF_DIR_PROXY             = "/system/proxy";
+       private const string GCONF_KEY_PROXY_MODE        = GCONF_DIR_PROXY + "/mode";
+       private const string GCONF_KEY_PROXY_SOCKS_HOST  = GCONF_DIR_PROXY + "/socks_host";
+       private const string GCONF_KEY_PROXY_SOCKS_PORT  = GCONF_DIR_PROXY + "/socks_port";
+       private const string GCONF_KEY_PROXY_SECURE_HOST = GCONF_DIR_PROXY + "/secure_host";
+       private const string GCONF_KEY_PROXY_SECURE_PORT = GCONF_DIR_PROXY + "/secure_port";
+
+       // Widgets
+       Hildon.Button button;
+
+       // Icons
+       Gdk.Pixbuf icon_disabled;
+       Gdk.Pixbuf icon_connecting;
+       Gdk.Pixbuf icon_connected;
+
+       // ConIc, GConf and Osso context
+       Osso.Context osso;
+       GConf.Client gconf;
+       ConIc.Connection conic;
+       bool conic_connected;
+
+       // Internal state
+       bool tor_enabled;
+       bool tor_connected;
+       Pid tor_pid;
+       int tor_stdout;
+       Pid polipo_pid;
+       ProxyBackup backup;
+
+       /**
+        * Update status area icon and status menu button value
+        */
+       private void update_status () {
+               Gtk.IconTheme icon_theme;
+               Gdk.Pixbuf pixbuf;
+
+               if (tor_enabled && tor_connected && icon_connected == null) try {
+                       icon_theme = Gtk.IconTheme.get_default ();
+                       pixbuf = icon_theme.load_icon ("tor_status_connected",
+                                                      STATUS_AREA_ICON_SIZE,
+                                                      Gtk.IconLookupFlags.NO_SVG);
+                       icon_connected = pixbuf;
+               } catch (Error e) {
+                       error (e.message);
+               }
+               if (!tor_enabled && icon_disabled == null) try {
+                       icon_theme = Gtk.IconTheme.get_default ();
+                       pixbuf = icon_theme.load_icon ("tor_status_disabled",
+                                                      STATUS_AREA_ICON_SIZE,
+                                                      Gtk.IconLookupFlags.NO_SVG);
+                       icon_disabled = pixbuf;
+               } catch (Error e) {
+                       error (e.message);
+               }
+               if (tor_enabled && !tor_connected && icon_connecting == null) try {
+                       icon_theme = Gtk.IconTheme.get_default ();
+                       pixbuf = icon_theme.load_icon ("tor_status_connecting",
+                                                      STATUS_AREA_ICON_SIZE,
+                                                      Gtk.IconLookupFlags.NO_SVG);
+                       icon_connecting = pixbuf;
+               } catch (Error e) {
+                       error (e.message);
+               }
+
+               if (conic_connected && tor_enabled) {
+                       pixbuf = tor_connected ? icon_connected : icon_connecting;
+                       button.set_value (tor_connected ? "Connected" : "Connecting ...");
+               } else {
+                       pixbuf = conic_connected ? icon_disabled : null;
+                       button.set_value (tor_enabled ? "Disconnected" : "Disabled");
+               }
+               set_status_area_icon (pixbuf);
+       }
+
+       /**
+        * Callback for Tor daemon line output
+        */
+       private bool tor_io_func (IOChannel source, IOCondition condition) {
+
+               if ((condition & (IOCondition.IN | IOCondition.PRI)) != 0) {
+                       string line = null;
+                       size_t length;
+                       try {
+                               /* var status = */ source.read_line (out line, out length, null);
+
+                               if ("[notice]" in line) {
+                                       if ("Bootstrapped 100%" in line) {
+                                               tor_connected = true;
+                                               proxy_setup ();
+                                               update_status ();
+                                       }
+                               } else {
+                                       // FIXME
+                                       Hildon.Banner.show_information (null, null, "DEBUG: %s".printf (line));
+                               }
+                       } catch (Error e) {
+                               // FIXME
+                               Hildon.Banner.show_information (null, null, "Error: %s".printf (e.message));
+                       }
+               }
+               if ((condition & (IOCondition.ERR | IOCondition.HUP | IOCondition.NVAL)) != 0) {
+                       return false;
+               }
+               return true;
+       }
+
+       /**
+        * Start Tor and setup proxy settings
+        */
+       private void start_tor () {
+               try {
+                       if (tor_pid == (Pid) 0) {
+                               Process.spawn_async_with_pipes ("/tmp",
+                                                               { "/usr/sbin/tor" },
+                                                               null,
+                                                               SpawnFlags.SEARCH_PATH,
+                                                               null,
+                                                               out tor_pid,
+                                                               null,
+                                                               out tor_stdout);
+
+                               var channel = new IOChannel.unix_new (tor_stdout);
+                               channel.add_watch (IOCondition.IN | IOCondition.PRI | IOCondition.ERR | IOCondition.HUP | IOCondition.NVAL, tor_io_func);
+                       }
+                       if (polipo_pid == (Pid) 0) {
+                               Process.spawn_async_with_pipes ("/tmp",
+                                                               { "/usr/bin/polipo" },
+                                                               null,
+                                                               SpawnFlags.SEARCH_PATH,
+                                                               null,
+                                                               out polipo_pid);
+                       }
+
+                       /* --> proxy settings and will be set up and tor_connected will
+                        * be set to true once Tor signals 100%
+                        */
+               } catch (SpawnError e) {
+                       error ("Failed to spawn polipo and tor: %s", e.message);
+                       return;
+               }
+
+               update_status ();
+       }
+
+       /**
+        * Stop Tor and revert proxy settings
+        */
+       private void stop_tor () {
+               proxy_restore ();
+               tor_connected = false;
+               if (polipo_pid != (Pid) 0) {
+                       Process.close_pid (polipo_pid);
+                       Posix.kill ((Posix.pid_t) polipo_pid, Posix.SIGKILL);
+                       polipo_pid = (Pid) 0;
+               }
+               if (tor_pid != (Pid) 0) {
+                       Process.close_pid (tor_pid);
+                       Posix.kill ((Posix.pid_t) tor_pid, Posix.SIGKILL);
+                       tor_pid = (Pid) 0;
+               }
+
+               update_status ();
+       }
+
+       /**
+        * Setup proxy settings to route through the Tor network
+        */
+       private void proxy_setup () {
+               if (backup == null) try {
+                       backup = new ProxyBackup ();
+                       backup.use_http_proxy = gconf.get_bool (GCONF_KEY_PROXY_HTTP_ENABLED);
+
+                       backup.http_host = gconf.get_string (GCONF_KEY_PROXY_HTTP_HOST);
+                       backup.socks_host = gconf.get_string (GCONF_KEY_PROXY_SOCKS_HOST);
+                       backup.secure_host = gconf.get_string (GCONF_KEY_PROXY_SECURE_HOST);
+                       backup.http_port = gconf.get_int (GCONF_KEY_PROXY_HTTP_PORT);
+                       backup.socks_port = gconf.get_int (GCONF_KEY_PROXY_SOCKS_PORT);
+                       backup.secure_port = gconf.get_int (GCONF_KEY_PROXY_SECURE_PORT);
+
+                       backup.mode = gconf.get_string (GCONF_KEY_PROXY_MODE);
+               } catch (Error e) {
+                       error ("Error saving proxy settings: %s", e.message);
+                       backup = new ProxyBackup ();
+                       backup.use_http_proxy = false;
+
+                       backup.http_host = "";
+                       backup.socks_host = "";
+                       backup.secure_host = "";
+                       backup.http_port = 8080;
+                       backup.socks_port = 0;
+                       backup.secure_port = 0;
+
+                       backup.mode = "none";
+               }
+               try {
+               //      Hildon.Banner.show_information (null, null, "DEBUG: Proxy setup");
+                       gconf.set_bool (GCONF_KEY_PROXY_HTTP_ENABLED, true);
+
+                       gconf.set_string (GCONF_KEY_PROXY_HTTP_HOST, "127.0.0.1");
+                       gconf.set_string (GCONF_KEY_PROXY_SOCKS_HOST, "127.0.0.1");
+                       gconf.set_string (GCONF_KEY_PROXY_SECURE_HOST, "127.0.0.1");
+                       gconf.set_int (GCONF_KEY_PROXY_HTTP_PORT, 8118);
+                       gconf.set_int (GCONF_KEY_PROXY_SOCKS_PORT, 9050);
+                       gconf.set_int (GCONF_KEY_PROXY_SECURE_PORT, 8118);
+
+                       gconf.set_string (GCONF_KEY_PROXY_MODE, "manual");
+               } catch (Error e) {
+                       error ("Error changing proxy settings: %s", e.message);
+               }
+       }
+
+       /**
+        * Revert proxy settings
+        */
+       private void proxy_restore () {
+               if (backup != null) try {
+               //      Hildon.Banner.show_information (null, null, "DEBUG: Restoring proxy settings");
+                       gconf.set_bool (GCONF_KEY_PROXY_HTTP_ENABLED, backup.use_http_proxy);
+
+                       gconf.set_string (GCONF_KEY_PROXY_HTTP_HOST, backup.http_host);
+                       gconf.set_string (GCONF_KEY_PROXY_SOCKS_HOST, backup.socks_host);
+                       gconf.set_string (GCONF_KEY_PROXY_SECURE_HOST, backup.secure_host);
+                       gconf.set_int (GCONF_KEY_PROXY_HTTP_PORT, backup.http_port);
+                       gconf.set_int (GCONF_KEY_PROXY_SOCKS_PORT, backup.socks_port);
+                       gconf.set_int (GCONF_KEY_PROXY_SECURE_PORT, backup.secure_port);
+
+                       gconf.set_string (GCONF_KEY_PROXY_MODE, backup.mode);
+                       backup = null;
+               } catch (Error e) {
+                       error ("Error restoring proxy: %s", e.message);
+               }
+       }
+
+       /**
+        * Callback for the status menu button clicked signal
+        */
+       private void button_clicked_cb () {
+               var dialog = new Gtk.Dialog ();
+               var content = (Gtk.VBox) dialog.get_content_area ();
+
+               dialog.set_title ("The Onion Router");
+
+               var check = new Hildon.CheckButton (Hildon.SizeType.FINGER_HEIGHT);
+               check.set_label ("Enable onion routing");
+               check.set_active (tor_enabled);
+               content.pack_start (check, true, true, 0);
+
+               dialog.add_button ("Save", Gtk.ResponseType.ACCEPT);
+               dialog.response.connect ((response_id) => {
+                       if (response_id == Gtk.ResponseType.ACCEPT) {
+                               if (!tor_enabled && check.get_active ()) {
+                                       tor_enabled = true;
+
+                                       if (conic_connected) {
+                                               start_tor ();
+                                       } else {
+                                               conic.connect (ConIc.ConnectFlags.NONE);
+                                       }
+                               } else if (tor_enabled && !check.get_active ()) {
+                                       tor_enabled = false;
+
+                                       stop_tor ();
+                                       if (conic_connected)
+                                               conic.disconnect ();
+                               }
+                       }
+                       dialog.destroy ();
+               });
+
+               dialog.show_all ();
+       }
+
+       /**
+        * Callback for the ConIc connection-event signal
+        */
+       private void conic_connection_event_cb (ConIc.Connection conic, ConIc.ConnectionEvent event) {
+               var status = event.get_status ();
+               switch (status) {
+               case ConIc.ConnectionStatus.CONNECTED:
+                       conic_connected = true;
+                       if (tor_enabled) {
+                               start_tor ();
+                       } else {
+                               update_status ();
+                       }
+                       break;
+               case ConIc.ConnectionStatus.DISCONNECTING:
+                       conic_connected = false;
+                       stop_tor ();
+                       break;
+               case ConIc.ConnectionStatus.DISCONNECTED:
+               case ConIc.ConnectionStatus.NETWORK_UP:
+                       // ignore
+                       break;
+               }
+
+               var error = event.get_error ();
+               switch (error) {
+               case ConIc.ConnectionError.CONNECTION_FAILED:
+                       Hildon.Banner.show_information (null, null, "DEBUG: ConIc connection failed");
+                       break;
+               case ConIc.ConnectionError.USER_CANCELED:
+                       Hildon.Banner.show_information (null, null, "DEBUG: ConIc user canceled");
+                       break;
+               case ConIc.ConnectionError.NONE:
+               case ConIc.ConnectionError.INVALID_IAP:
+                       // ignore
+                       break;
+               }
+       }
+
+       private void create_widgets () {
+               Gtk.IconTheme icon_theme;
+               Gdk.Pixbuf pixbuf;
+               Gtk.Image image;
+
+               // Status menu button
+               button = new Hildon.Button.with_text (Hildon.SizeType.FINGER_HEIGHT,
+                                                     Hildon.ButtonArrangement.VERTICAL,
+                                                     "The Onion Router",
+                                                     tor_enabled ? "Enabled" : "Disabled");
+               icon_theme = Gtk.IconTheme.get_default();
+               try {
+                       pixbuf = icon_theme.load_icon ("tor_onion",
+                                                      STATUS_MENU_ICON_SIZE,
+                                                      Gtk.IconLookupFlags.NO_SVG);
+                       image = new Gtk.Image.from_pixbuf (pixbuf);
+                       button.set_image (image);
+               } catch (Error e) {
+                       error (e.message);
+               }
+               button.set_alignment (0.0f, 0.5f, 1.0f, 1.0f);
+               button.clicked.connect (button_clicked_cb);
+
+               add (button);
+
+               // Status area icon
+               update_status ();
+
+               show_all ();
+       }
+
+       construct {
+               // GConf hook-up
+               gconf = GConf.Client.get_default ();
+               try {
+                       tor_enabled = gconf.get_bool (GCONF_KEY_TOR_ENABLED);
+               } catch (Error e) {
+                       error ("Failed to get GConf setting: %s", e.message);
+               }
+               tor_connected = false;
+
+               // ConIc hook-up
+               conic = new ConIc.Connection ();
+               if (conic == null) {
+                       Hildon.Banner.show_information (null, null, "DEBUG: ConIc hook-up failed");
+               }
+               conic_connected = false;
+               conic.automatic_connection_events = true;
+               if (tor_enabled)
+                       conic.connect (ConIc.ConnectFlags.AUTOMATICALLY_TRIGGERED);
+               conic.connection_event.connect (conic_connection_event_cb);
+
+               // Osso hook-up
+               osso = new Osso.Context (STATUSMENU_TOR_LIBOSSO_SERVICE_NAME,
+                                        "0.0.1",
+                                        true,
+                                        null);
+
+               create_widgets ();
+       }
+}
+
+/**
+ * Vala code can't use the HD_DEFINE_PLUGIN_MODULE macro, but it handles
+ * most of the class registration issues itself. Only this code from
+ * HD_PLUGIN_MODULE_SYMBOLS_CODE has to be has to be included manually
+ * to register with hildon-desktop:
+ */
+[ModuleInit]
+public void hd_plugin_module_load (TypeModule plugin) {
+       // [ModuleInit] registers types automatically
+       ((HD.PluginModule) plugin).add_type (typeof (TorStatusMenuItem));
+}
+
+public void hd_plugin_module_unload (HD.PluginModule plugin) {
+}