[Pkg-anonymity-tools] [torbrowser-launcher] 03/48: split torbrowser-launcher into several files

Holger Levsen holger at moszumanska.debian.org
Wed Feb 4 22:15:36 UTC 2015


This is an automated email from the git hooks/post-receive script.

holger pushed a commit to branch master
in repository torbrowser-launcher.

commit 8e9d88e8eb699b12f1dbe54956daf50424b6f3be
Author: Micah Lee <micah at micahflee.com>
Date:   Wed Sep 3 11:24:28 2014 -0700

    split torbrowser-launcher into several files
---
 setup.py                                           |   27 +-
 torbrowser-launcher                                | 1121 +-------------------
 torbrowser_launcher/__init__.py                    |   63 ++
 torbrowser_launcher/common.py                      |  290 +++++
 .../launcher.py                                    |  500 +--------
 torbrowser_launcher/settings.py                    |  231 ++++
 6 files changed, 605 insertions(+), 1627 deletions(-)

diff --git a/setup.py b/setup.py
index aae20d4..a7dfff9 100644
--- a/setup.py
+++ b/setup.py
@@ -55,23 +55,24 @@ if not hasattr(sys, 'real_prefix'):
         'apparmor/torbrowser.Tor.tor',
         'apparmor/usr.bin.torbrowser-launcher'])]
 
-setup(name='torbrowser-launcher',
-      version=version,
-      author='Micah Lee',
-      author_email='micah at micahflee.com',
-      url='https://www.github.com/micahflee/torbrowser-launcher',
-      platforms=['GNU/Linux'],
-      license='BSD',
-
-      description='A program to help you download, keep updated, and run the Tor Browser Bundle',
-      long_description="""
+setup(
+    name='torbrowser-launcher',
+    version=version,
+    author='Micah Lee',
+    author_email='micah at micahflee.com',
+    url='https://www.github.com/micahflee/torbrowser-launcher',
+    platforms=['GNU/Linux'],
+    license='BSD',
+    description='A program to help you download, keep updated, and run the Tor Browser Bundle',
+    long_description="""
 Tor Browser Launcher is intended to make the Tor Browser Bundle (TBB) easier to maintain and use for GNU/Linux users. You install torbrowser-launcher from your distribution's package manager and it handles downloading the most recent version of TBB for you, in your language and for your architecture. It also adds a "Tor Browser" application launcher to your operating system's menu.
 
 When you first launch Tor Browser Launcher, it will download TBB from https://www.torproject.org/ and extract it in ~/.torproject, and then execute it. When you run it after that it will just execute TBB.
 
 Tor Browser Launcher will get updated each time a new version of TBB is released. When you open Tor Browser after an update, it will download the newer version of TBB for you and extract it over your old TBB directory in ~/.torproject, so you will maintain your TBB bookmarks.
 """,
+    packages=['torbrowser_launcher'],
+    scripts=['torbrowser-launcher'],
+    data_files=datafiles
+)
 
-      scripts=['torbrowser-launcher'],
-      data_files=datafiles
-      )
diff --git a/torbrowser-launcher b/torbrowser-launcher
old mode 100755
new mode 100644
index 7bbbf01..b4c06bc
--- a/torbrowser-launcher
+++ b/torbrowser-launcher
@@ -26,1122 +26,5 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 OTHER DEALINGS IN THE SOFTWARE.
 """
-
-import os
-import sys
-SHARE = os.getenv('TBL_SHARE', sys.prefix+'/share/torbrowser-launcher')
-
-import platform
-
-import gettext
-gettext.install('torbrowser-launcher', os.path.join(SHARE, 'locale'))
-
-from twisted.internet import gtk2reactor
-gtk2reactor.install()
-from twisted.internet import reactor
-
-import pygtk
-pygtk.require('2.0')
-import gtk
-
-import subprocess, locale, time, pickle, json, tarfile, psutil, hashlib, lzma
-
-from twisted.web.client import Agent, RedirectAgent, ResponseDone, ResponseFailed
-from twisted.web.http_headers import Headers
-from twisted.internet.protocol import Protocol
-from twisted.internet.ssl import ClientContextFactory
-from twisted.internet.error import DNSLookupError
-
-import OpenSSL
-
-
-class TryStableException(Exception):
-    pass
-
-
-class TryDefaultMirrorException(Exception):
-    pass
-
-
-class DownloadErrorException(Exception):
-    pass
-
-
-class VerifyTorProjectCert(ClientContextFactory):
-
-    def __init__(self, torproject_pem):
-        self.torproject_ca = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, open(torproject_pem, 'r').read())
-
-    def getContext(self, host, port):
-        ctx = ClientContextFactory.getContext(self)
-        ctx.set_verify_depth(0)
-        ctx.set_verify(OpenSSL.SSL.VERIFY_PEER | OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, self.verifyHostname)
-        return ctx
-
-    def verifyHostname(self, connection, cert, errno, depth, preverifyOK):
-        return cert.digest('sha256') == self.torproject_ca.digest('sha256')
-
-
-class TBLCommon:
-
-    def __init__(self, tbl_version):
-        print _('Initializing Tor Browser Launcher')
-        self.tbl_version = tbl_version
-
-        # initialize the app
-        self.default_mirror = 'https://www.torproject.org/dist/'
-        self.discover_arch_lang()
-        self.build_paths()
-        for d in self.paths['dirs']:
-            self.mkdir(self.paths['dirs'][d])
-        self.load_mirrors()
-        self.load_settings()
-        self.mkdir(self.paths['download_dir'])
-        self.mkdir(self.paths['tbb']['dir'])
-        self.init_gnupg()
-
-        # allow buttons to have icons
-        try:
-            gtk_settings = gtk.settings_get_default()
-            gtk_settings.props.gtk_button_images = True
-        except:
-            pass
-
-    # discover the architecture and language
-    def discover_arch_lang(self):
-        # figure out the architecture
-        self.architecture = 'x86_64' if '64' in platform.architecture()[0] else 'i686'
-
-        # figure out the language
-        available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
-        default_locale = locale.getdefaultlocale()[0]
-        if default_locale is None:
-            self.language = 'en-US'
-        else:
-            self.language = default_locale.replace('_', '-')
-            if self.language not in available_languages:
-                self.language = self.language.split('-')[0]
-                if self.language not in available_languages:
-                    for l in available_languages:
-                        if l[0:2] == self.language:
-                            self.language = l
-            # if language isn't available, default to english
-            if self.language not in available_languages:
-                self.language = 'en-US'
-
-    # build all relevant paths
-    def build_paths(self, tbb_version=None):
-        homedir = os.getenv('HOME')
-        if not homedir:
-            homedir = '/tmp/.torbrowser-'+os.getenv('USER')
-            if not os.path.exists(homedir):
-                try:
-                    os.mkdir(homedir, 0700)
-                except:
-                    self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
-        if not os.access(homedir, os.W_OK):
-            self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
-
-        tbb_config = '{0}/.config/torbrowser'.format(homedir)
-        tbb_cache = '{0}/.cache/torbrowser'.format(homedir)
-        tbb_local = '{0}/.local/share/torbrowser'.format(homedir)
-        old_tbb_data = '{0}/.torbrowser'.format(homedir)
-
-        if tbb_version:
-            # tarball filename
-            if self.architecture == 'x86_64':
-                arch = 'linux64'
-            else:
-                arch = 'linux32'
-            tarball_filename = 'tor-browser-'+arch+'-'+tbb_version+'_'+self.language+'.tar.xz'
-
-            # tarball
-            self.paths['tarball_url'] = '{0}torbrowser/'+tbb_version+'/'+tarball_filename
-            self.paths['tarball_file'] = tbb_cache+'/download/'+tarball_filename
-            self.paths['tarball_filename'] = tarball_filename
-
-            # sig
-            self.paths['sha256_file'] = tbb_cache+'/download/sha256sums.txt'
-            self.paths['sha256_sig_file'] = tbb_cache+'/download/sha256sums.txt.asc'
-            self.paths['sha256_url'] = '{0}torbrowser/'+tbb_version+'/sha256sums.txt'
-            self.paths['sha256_sig_url'] = '{0}torbrowser/'+tbb_version+'/sha256sums.txt.asc'
-        else:
-            self.paths = {
-                'dirs': {
-                    'config': tbb_config,
-                    'cache': tbb_cache,
-                    'local': tbb_local,
-                },
-                'old_data_dir': old_tbb_data,
-                'tbl_bin': __file__,
-                'icon_file': os.path.join(os.path.dirname(SHARE), 'pixmaps/torbrowser80.xpm'),
-                'torproject_pem': os.path.join(SHARE, 'torproject.pem'),
-                'erinn_key': os.path.join(SHARE, 'erinn.asc'),
-                'mirrors_txt': [os.path.join(SHARE, 'mirrors.txt'),
-                                tbb_config+'/mirrors.txt'],
-                'modem_sound': os.path.join(SHARE, 'modem.ogg'),
-                'download_dir': tbb_cache+'/download',
-                'gnupg_homedir': tbb_local+'/gnupg_homedir',
-                'settings_file': tbb_config+'/settings',
-                'update_check_url': 'https://www.torproject.org/projects/torbrowser/RecommendedTBBVersions',
-                'update_check_file': tbb_cache+'/download/RecommendedTBBVersions',
-                'tbb': {
-                    'dir': tbb_local+'/tbb/'+self.architecture,
-                    'start': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
-                    'versions': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/Docs/sources/versions',
-                },
-            }
-
-    # create a directory
-    @staticmethod
-    def mkdir(path):
-        try:
-            if not os.path.exists(path):
-                os.makedirs(path, 0700)
-                return True
-        except:
-            print _("Cannot create directory {0}").format(path)
-            return False
-        if not os.access(path, os.W_OK):
-            print _("{0} is not writable").format(path)
-            return False
-        return True
-
-    # if gnupg_homedir isn't set up, set it up
-    def init_gnupg(self):
-        if not os.path.exists(self.paths['gnupg_homedir']):
-            print _('Creating GnuPG homedir'), self.paths['gnupg_homedir']
-            self.mkdir(self.paths['gnupg_homedir'])
-        self.import_keys()
-
-    # import gpg keys
-    def import_keys(self):
-        print _('Importing keys')
-        subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['erinn_key']]).wait()
-
-    # load mirrors
-    def load_mirrors(self):
-        self.mirrors = []
-        for srcfile in self.paths['mirrors_txt']:
-            if os.path.exists(srcfile):
-                print "Successfully loaded mirrors from %s" % srcfile
-            elif not os.path.exists(srcfile):
-                print "Warning: can't load mirrors from %s" % srcfile
-                continue
-            for mirror in open(srcfile, 'r').readlines():
-                if mirror.strip() not in self.mirrors:
-                    self.mirrors.append(mirror.strip())
-
-    # load settings
-    def load_settings(self):
-        default_settings = {
-            'tbl_version': self.tbl_version,
-            'installed_version': False,
-            'latest_version': '0',
-            'update_over_tor': True,
-            'check_for_updates': False,
-            'modem_sound': False,
-            'last_update_check_timestamp': 0,
-            'mirror': self.default_mirror
-        }
-
-        if os.path.isfile(self.paths['settings_file']):
-            settings = pickle.load(open(self.paths['settings_file']))
-            resave = False
-
-            # settings migrations
-            if settings['tbl_version'] <= '0.1.0':
-                print '0.1.0 migration'
-                settings['installed_version'] = settings['installed_version']['stable']
-                settings['latest_version'] = settings['latest_version']['stable']
-                resave = True
-
-                # make new tbb folder
-                self.mkdir(self.paths['tbb']['dir'])
-                old_tbb_dir = self.paths['old_data_dir']+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language
-                new_tbb_dir = self.paths['tbb']['dir']+'/tor-browser_'+self.language
-                if os.path.isdir(old_tbb_dir):
-                    os.rename(old_tbb_dir, new_tbb_dir)
-
-            # make sure settings file is up-to-date
-            for setting in default_settings:
-                if setting not in settings:
-                    settings[setting] = default_settings[setting]
-                    resave = True
-
-            # make sure the version is current
-            if settings['tbl_version'] != self.tbl_version:
-                settings['tbl_version'] = self.tbl_version
-                resave = True
-
-            self.settings = settings
-            if resave:
-                self.save_settings()
-
-        else:
-            self.settings = default_settings
-            self.save_settings()
-
-    # save settings
-    def save_settings(self):
-        pickle.dump(self.settings, open(self.paths['settings_file'], 'w'))
-        return True
-
-    # get the process id of a program
-    @staticmethod
-    def get_pid(bin_path, python=False):
-        pid = None
-
-        for p in psutil.process_iter():
-            try:
-                if p.pid != os.getpid():
-                    exe = None
-                    if python:
-                        if len(p.cmdline) > 1:
-                            if 'python' in p.cmdline[0]:
-                                exe = p.cmdline[1]
-                    else:
-                        if len(p.cmdline) > 0:
-                            exe = p.cmdline[0]
-
-                    if exe == bin_path:
-                        pid = p.pid
-
-            except:
-                pass
-
-        return pid
-
-    # bring program's x window to front
-    @staticmethod
-    def bring_window_to_front(pid):
-        # figure out the window id
-        win_id = None
-        p = subprocess.Popen(['wmctrl', '-l', '-p'], stdout=subprocess.PIPE)
-        for line in p.stdout.readlines():
-            line_split = line.split()
-            cur_win_id = line_split[0]
-            cur_win_pid = int(line_split[2])
-            if cur_win_pid == pid:
-                win_id = cur_win_id
-
-        # bring to front
-        if win_id:
-            subprocess.call(['wmctrl', '-i', '-a', win_id])
-
-
-class TBLSettings:
-    def __init__(self, common):
-        print _('Starting settings dialog')
-        self.common = common
-
-        # set up the window
-        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
-        self.window.set_title(_("Tor Browser Launcher Settings"))
-        self.window.set_icon_from_file(self.common.paths['icon_file'])
-        self.window.set_position(gtk.WIN_POS_CENTER)
-        self.window.set_border_width(10)
-        self.window.connect("delete_event", self.delete_event)
-        self.window.connect("destroy", self.destroy)
-
-        # build the rest of the UI
-        self.box = gtk.VBox(False, 10)
-        self.window.add(self.box)
-        self.box.show()
-
-        self.hbox = gtk.HBox(False, 10)
-        self.box.pack_start(self.hbox, True, True, 0)
-        self.hbox.show()
-
-        self.settings_box = gtk.VBox(False, 10)
-        self.hbox.pack_start(self.settings_box, True, True, 0)
-        self.settings_box.show()
-
-        self.labels_box = gtk.VBox(False, 10)
-        self.hbox.pack_start(self.labels_box, True, True, 0)
-        self.labels_box.show()
-
-        # download over tor
-        try:
-            import txsocksx
-            self.txsocks_found = True
-        except ImportError:
-            self.txsocks_found = False
-        self.tor_update_checkbox = gtk.CheckButton(_("Download updates over Tor (recommended)"))
-        if self.txsocks_found:
-            self.tor_update_checkbox.set_tooltip_text(_("This option is only available when using a system wide Tor installation."))
-        else:
-            self.tor_update_checkbox.set_tooltip_text(_("This option requires the python-txsocksx package."))
-
-        self.settings_box.pack_start(self.tor_update_checkbox, True, True, 0)
-        if self.common.settings['update_over_tor'] and self.txsocks_found:
-            self.tor_update_checkbox.set_active(True)
-        else:
-            self.tor_update_checkbox.set_active(False)
-
-        if self.txsocks_found == False:
-            self.tor_update_checkbox.set_sensitive(False)
-
-        self.tor_update_checkbox.show()
-
-        # check for updates
-        self.update_checkbox = gtk.CheckButton(_("Check for updates next launch"))
-        self.settings_box.pack_start(self.update_checkbox, True, True, 0)
-        if self.common.settings['check_for_updates']:
-            self.update_checkbox.set_active(True)
-        else:
-            self.update_checkbox.set_active(False)
-        self.update_checkbox.show()
-
-        # modem sound
-        self.modem_checkbox = gtk.CheckButton(_("Play modem sound, because Tor is slow :]"))
-        self.settings_box.pack_start(self.modem_checkbox, True, True, 0)
-
-        try:
-            import pygame
-            if self.common.settings['modem_sound']:
-                self.modem_checkbox.set_active(True)
-            else:
-                self.modem_checkbox.set_active(False)
-        except ImportError:
-            self.modem_checkbox.set_active(False)
-            self.modem_checkbox.set_sensitive(False)
-            self.modem_checkbox.set_tooltip_text(_("This option requires python-pygame to be installed"))
-        self.modem_checkbox.show()
-
-        # labels
-        if(self.common.settings['installed_version']):
-            self.label1 = gtk.Label(_('Installed version:\n{0}').format(self.common.settings['installed_version']))
-        else:
-            self.label1 = gtk.Label(_('Not installed'))
-        self.label1.set_line_wrap(True)
-        self.labels_box.pack_start(self.label1, True, True, 0)
-        self.label1.show()
-
-        if(self.common.settings['last_update_check_timestamp'] > 0):
-            self.label1 = gtk.Label(_('Last checked for updates:\n{0}').format(time.strftime("%B %d, %Y %I:%M %P", time.gmtime(self.common.settings['last_update_check_timestamp']))))
-        else:
-            self.label1 = gtk.Label(_('Never checked for updates'))
-        self.label1.set_line_wrap(True)
-        self.labels_box.pack_start(self.label1, True, True, 0)
-        self.label1.show()
-
-        # mirrors
-        self.mirrors_box = gtk.HBox(False, 10)
-        self.box.pack_start(self.mirrors_box, True, True, 0)
-        self.mirrors_box.show()
-
-        self.mirrors_label = gtk.Label(_('Mirror'))
-        self.mirrors_label.set_line_wrap(True)
-        self.mirrors_box.pack_start(self.mirrors_label, True, True, 0)
-        self.mirrors_label.show()
-
-        self.mirrors = gtk.combo_box_new_text()
-        for mirror in self.common.mirrors:
-            self.mirrors.append_text(mirror)
-        if self.common.settings['mirror'] in self.common.mirrors:
-            self.mirrors.set_active(self.common.mirrors.index(self.common.settings['mirror']))
-        else:
-            self.mirrors.set_active(0)
-        self.mirrors_box.pack_start(self.mirrors, True, True, 0)
-        self.mirrors.show()
-
-        # button box
-        self.button_box = gtk.HButtonBox()
-        self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
-        self.box.pack_start(self.button_box, True, True, 0)
-        self.button_box.show()
-
-        # save and launch button
-        save_launch_image = gtk.Image()
-        save_launch_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
-        self.save_launch_button = gtk.Button(_("Launch Tor Browser"))
-        self.save_launch_button.set_image(save_launch_image)
-        self.save_launch_button.connect("clicked", self.save_launch, None)
-        self.button_box.add(self.save_launch_button)
-        self.save_launch_button.show()
-
-        # save and exit button
-        save_exit_image = gtk.Image()
-        save_exit_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
-        self.save_exit_button = gtk.Button(_("Save & Exit"))
-        self.save_exit_button.set_image(save_exit_image)
-        self.save_exit_button.connect("clicked", self.save_exit, None)
-        self.button_box.add(self.save_exit_button)
-        self.save_exit_button.show()
-
-        # cancel button
-        cancel_image = gtk.Image()
-        cancel_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
-        self.cancel_button = gtk.Button(_("Cancel"))
-        self.cancel_button.set_image(cancel_image)
-        self.cancel_button.connect("clicked", self.destroy, None)
-        self.button_box.add(self.cancel_button)
-        self.cancel_button.show()
-
-        # show the window
-        self.window.show()
-
-        # start gtk
-        gtk.main()
-
-    # UI Callback for update over tor/use system tor
-    def on_system_tor_clicked(self, event):
-        if self.txsocks_found:
-            value = self.system_tor_checkbox.get_active()
-        else:
-            value = False
-
-        self.tor_update_checkbox.set_active(value)
-        self.tor_update_checkbox.set_sensitive(value)
-
-    # save and launch
-    def save_launch(self, widget, data=None):
-        self.save()
-        subprocess.Popen([self.common.paths['tbl_bin']])
-        self.destroy(False)
-
-    # save and exit
-    def save_exit(self, widget, data=None):
-        self.save()
-        self.destroy(False)
-
-    # save settings
-    def save(self):
-        # checkbox options
-        self.common.settings['update_over_tor'] = self.tor_update_checkbox.get_active()
-        self.common.settings['check_for_updates'] = self.update_checkbox.get_active()
-        self.common.settings['modem_sound'] = self.modem_checkbox.get_active()
-
-        # figure out the selected mirror
-        self.common.settings['mirror'] = self.common.mirrors[self.mirrors.get_active()]
-
-        # save them
-        self.common.save_settings()
-
-    # exit
-    def delete_event(self, widget, event, data=None):
-        return False
-
-    def destroy(self, widget, data=None):
-        gtk.main_quit()
-
-
-class TBLLauncher:
-    def __init__(self, common):
-        print _('Starting launcher dialog')
-        self.common = common
-
-        # init launcher
-        self.set_gui(None, '', [])
-        self.launch_gui = True
-        print "LATEST VERSION", self.common.settings['latest_version']
-        self.common.build_paths(self.common.settings['latest_version'])
-
-        if self.common.settings['update_over_tor']:
-            try:
-                import txsocksx
-            except ImportError:
-                md = gtk.MessageDialog(None, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, gtk.BUTTONS_CLOSE, _("The python-txsocksx package is missing, downloads will not happen over tor"))
-                md.set_position(gtk.WIN_POS_CENTER)
-                md.run()
-                md.destroy()
-                self.common.settings['update_over_tor'] = False
-                self.common.save_settings()
-
-        # is firefox already running?
-        if self.common.settings['installed_version']:
-            firefox_pid = self.common.get_pid('./Browser/firefox')
-            if firefox_pid:
-                print _('Firefox is open, bringing to focus')
-                # bring firefox to front
-                self.common.bring_window_to_front(firefox_pid)
-                return
-
-        # check for updates?
-        check_for_updates = False
-        if self.common.settings['check_for_updates']:
-            check_for_updates = True
-
-        if not check_for_updates:
-            # how long was it since the last update check?
-            # 86400 seconds = 24 hours
-            current_timestamp = int(time.time())
-            if current_timestamp - self.common.settings['last_update_check_timestamp'] >= 86400:
-                check_for_updates = True
-
-        if check_for_updates:
-            # check for update
-            print 'Checking for update'
-            self.set_gui('task', _("Checking for Tor Browser update."),
-                         ['download_update_check',
-                          'attempt_update'])
-        else:
-            # no need to check for update
-            print _('Checked for update within 24 hours, skipping')
-            self.start_launcher()
-
-        if self.launch_gui:
-            # set up the window
-            self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
-            self.window.set_title(_("Tor Browser"))
-            self.window.set_icon_from_file(self.common.paths['icon_file'])
-            self.window.set_position(gtk.WIN_POS_CENTER)
-            self.window.set_border_width(10)
-            self.window.connect("delete_event", self.delete_event)
-            self.window.connect("destroy", self.destroy)
-
-            # build the rest of the UI
-            self.build_ui()
-
-    # download or run TBB
-    def start_launcher(self):
-        # is TBB already installed?
-        latest_version = self.common.settings['latest_version']
-        installed_version = self.common.settings['installed_version']
-
-        # verify installed version for newer versions of TBB (#58)
-        if installed_version >= '3.0':
-            versions_filename = self.common.paths['tbb']['versions']
-            if os.path.exists(versions_filename):
-                for line in open(versions_filename):
-                    if 'TORBROWSER_VERSION' in line:
-                        installed_version = line.lstrip('TORBROWSER_VERSION=').strip()
-
-        start = self.common.paths['tbb']['start']
-        if os.path.isfile(start) and os.access(start, os.X_OK):
-            if installed_version == latest_version:
-                print _('Latest version of TBB is installed, launching')
-                # current version of tbb is installed, launch it
-                self.run(False)
-                self.launch_gui = False
-            elif installed_version < latest_version:
-                print _('TBB is out of date, attempting to upgrade to {0}'.format(latest_version))
-                # there is a tbb upgrade available
-                self.set_gui('task', _("Your Tor Browser is out of date."),
-                             ['download_sha256',
-                              'download_sha256_sig',
-                              'download_tarball',
-                              'verify',
-                              'extract',
-                              'run'])
-            else:
-                # for some reason the installed tbb is newer than the current version?
-                self.set_gui('error', _("Something is wrong. The version of Tor Browser Bundle you have installed is newer than the current version?"), [])
-
-        # not installed
-        else:
-            print _('TBB is not installed, attempting to install {0}'.format(latest_version))
-            self.set_gui('task', _("Downloading and installing Tor Browser."),
-                         ['download_sha256',
-                          'download_sha256_sig',
-                          'download_tarball',
-                          'verify',
-                          'extract',
-                          'run'])
-
-    # there are different GUIs that might appear, this sets which one we want
-    def set_gui(self, gui, message, tasks, autostart=True):
-        self.gui = gui
-        self.gui_message = message
-        self.gui_tasks = tasks
-        self.gui_task_i = 0
-        self.gui_autostart = autostart
-
-    # set all gtk variables to False
-    def clear_ui(self):
-        if hasattr(self, 'box') and hasattr(self.box, 'destroy'):
-            self.box.destroy()
-        self.box = False
-
-        self.label = False
-        self.progressbar = False
-        self.button_box = False
-        self.start_button = False
-        self.exit_button = False
-
-    # build the application's UI
-    def build_ui(self):
-        self.clear_ui()
-
-        self.box = gtk.VBox(False, 20)
-        self.window.add(self.box)
-
-        if 'error' in self.gui:
-            # labels
-            self.label = gtk.Label(self.gui_message)
-            self.label.set_line_wrap(True)
-            self.box.pack_start(self.label, True, True, 0)
-            self.label.show()
-
-            # button box
-            self.button_box = gtk.HButtonBox()
-            self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
-            self.box.pack_start(self.button_box, True, True, 0)
-            self.button_box.show()
-
-            if self.gui != 'error':
-                # yes button
-                yes_image = gtk.Image()
-                yes_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
-                self.yes_button = gtk.Button("Yes")
-                self.yes_button.set_image(yes_image)
-                if self.gui == 'error_try_stable':
-                    self.yes_button.connect("clicked", self.try_stable, None)
-                elif self.gui == 'error_try_default_mirror':
-                    self.yes_button.connect("clicked", self.try_default_mirror, None)
-                elif self.gui == 'error_try_tor':
-                    self.yes_button.connect("clicked", self.try_tor, None)
-                self.button_box.add(self.yes_button)
-                self.yes_button.show()
-
-            # exit button
-            exit_image = gtk.Image()
-            exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
-            self.exit_button = gtk.Button("Exit")
-            self.exit_button.set_image(exit_image)
-            self.exit_button.connect("clicked", self.destroy, None)
-            self.button_box.add(self.exit_button)
-            self.exit_button.show()
-
-        elif self.gui == 'task':
-            # label
-            self.label = gtk.Label(self.gui_message)
-            self.label.set_line_wrap(True)
-            self.box.pack_start(self.label, True, True, 0)
-            self.label.show()
-
-            # progress bar
-            self.progressbar = gtk.ProgressBar(adjustment=None)
-            self.progressbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT)
-            self.progressbar.set_pulse_step(0.01)
-            self.box.pack_start(self.progressbar, True, True, 0)
-
-            # button box
-            self.button_box = gtk.HButtonBox()
-            self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
-            self.box.pack_start(self.button_box, True, True, 0)
-            self.button_box.show()
-
-            # start button
-            start_image = gtk.Image()
-            start_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
-            self.start_button = gtk.Button(_("Start"))
-            self.start_button.set_image(start_image)
-            self.start_button.connect("clicked", self.start, None)
-            self.button_box.add(self.start_button)
-            if not self.gui_autostart:
-                self.start_button.show()
-
-            # exit button
-            exit_image = gtk.Image()
-            exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
-            self.exit_button = gtk.Button(_("Exit"))
-            self.exit_button.set_image(exit_image)
-            self.exit_button.connect("clicked", self.destroy, None)
-            self.button_box.add(self.exit_button)
-            self.exit_button.show()
-
-        self.box.show()
-        self.window.show()
-
-        if self.gui_autostart:
-            self.start(None)
-
-    # start button clicked, begin tasks
-    def start(self, widget, data=None):
-        # disable the start button
-        if self.start_button:
-            self.start_button.set_sensitive(False)
-
-        # start running tasks
-        self.run_task()
-
-    # run the next task in the task list
-    def run_task(self):
-        self.refresh_gtk()
-
-        if self.gui_task_i >= len(self.gui_tasks):
-            self.destroy(False)
-            return
-
-        task = self.gui_tasks[self.gui_task_i]
-
-        # get ready for the next task
-        self.gui_task_i += 1
-
-        print _('Running task: {0}'.format(task))
-        if task == 'download_update_check':
-            print _('Downloading'), self.common.paths['update_check_url']
-            self.download('update check', self.common.paths['update_check_url'], self.common.paths['update_check_file'])
-
-        if task == 'attempt_update':
-            print _('Checking to see if update is needed')
-            self.attempt_update()
-
-        elif task == 'download_sha256':
-            print _('Downloading'), self.common.paths['sha256_url'].format(self.common.settings['mirror'])
-            self.download('signature', self.common.paths['sha256_url'], self.common.paths['sha256_file'])
-
-        elif task == 'download_sha256_sig':
-            print _('Downloading'), self.common.paths['sha256_sig_url'].format(self.common.settings['mirror'])
-            self.download('signature', self.common.paths['sha256_sig_url'], self.common.paths['sha256_sig_file'])
-
-        elif task == 'download_tarball':
-            print _('Downloading'), self.common.paths['tarball_url'].format(self.common.settings['mirror'])
-            self.download('tarball', self.common.paths['tarball_url'], self.common.paths['tarball_file'])
-
-        elif task == 'verify':
-            print _('Verifying signature')
-            self.verify()
-
-        elif task == 'extract':
-            print _('Extracting'), self.common.paths['tarball_filename']
-            self.extract()
-
-        elif task == 'run':
-            print _('Running'), self.common.paths['tbb']['start']
-            self.run()
-
-        elif task == 'start_over':
-            print _('Starting download over again')
-            self.start_over()
-
-    def response_received(self, response):
-        class FileDownloader(Protocol):
-            def __init__(self, common, file, url, total, progress, done_cb):
-                self.file = file
-                self.total = total
-                self.so_far = 0
-                self.progress = progress
-                self.all_done = done_cb
-
-                if response.code != 200:
-                    if common.settings['mirror'] != common.default_mirror:
-                        raise TryDefaultMirrorException(_("Download Error: {0} {1}\n\nYou are currently using a non-default mirror:\n{2}\n\nWould you like to switch back to the default?").format(response.code, response.phrase, common.settings['mirror']))
-                    else:
-                        raise DownloadErrorException(_("Download Error: {0} {1}").format(response.code, response.phrase))
-
-            def dataReceived(self, bytes):
-                self.file.write(bytes)
-                self.so_far += len(bytes)
-                percent = float(self.so_far) / float(self.total)
-                self.progress.set_fraction(percent)
-                amount = float(self.so_far)
-                units = "bytes"
-                for (size, unit) in [(1024 * 1024, "MiB"), (1024, "KiB")]:
-                    if amount > size:
-                        units = unit
-                        amount = amount / float(size)
-                        break
-
-                self.progress.set_text(_('Downloaded')+(' %2.1f%% (%2.1f %s)' % ((percent * 100.0), amount, units)))
-
-            def connectionLost(self, reason):
-                print _('Finished receiving body:'), reason.getErrorMessage()
-                self.all_done(reason)
-
-        if hasattr(self, 'current_download_url'):
-            url = self.current_download_url
-        else:
-            url = None
-
-        dl = FileDownloader(self.common, self.file_download, url, response.length, self.progressbar, self.response_finished)
-        response.deliverBody(dl)
-
-    def response_finished(self, msg):
-        if msg.check(ResponseDone):
-            self.file_download.close()
-            delattr(self, 'current_download_path')
-            delattr(self, 'current_download_url')
-
-            # next task!
-            self.run_task()
-
-        else:
-            print "FINISHED", msg
-            ## FIXME handle errors
-
-    def download_error(self, f):
-        print _("Download error:"), f.value, type(f.value)
-
-        if isinstance(f.value, TryStableException):
-            f.trap(TryStableException)
-            self.set_gui('error_try_stable', str(f.value), [], False)
-
-        elif isinstance(f.value, TryDefaultMirrorException):
-            f.trap(TryDefaultMirrorException)
-            self.set_gui('error_try_default_mirror', str(f.value), [], False)
-
-        elif isinstance(f.value, DownloadErrorException):
-            f.trap(DownloadErrorException)
-            self.set_gui('error', str(f.value), [], False)
-
-        elif isinstance(f.value, DNSLookupError):
-            f.trap(DNSLookupError)
-            if common.settings['mirror'] != common.default_mirror:
-                self.set_gui('error_try_default_mirror', _("DNS Lookup Error\n\nYou are currently using a non-default mirror:\n{0}\n\nWould you like to switch back to the default?").format(common.settings['mirror']), [], False)
-            else:
-                self.set_gui('error', str(f.value), [], False)
-
-        elif isinstance(f.value, ResponseFailed):
-            for reason in f.value.reasons:
-                if isinstance(reason.value, OpenSSL.SSL.Error):
-                    # TODO: add the ability to report attack by posting bug to trac.torproject.org
-                    if not self.common.settings['update_over_tor']:
-                        self.set_gui('error_try_tor', _('The SSL certificate served by https://www.torproject.org is invalid! You may be under attack. Try the download again using Tor?'), [], False)
-                    else:
-                        self.set_gui('error', _('The SSL certificate served by https://www.torproject.org is invalid! You may be under attack.'), [], False)
-
-        else:
-            self.set_gui('error', _("Error starting download:\n\n{0}\n\nAre you connected to the internet?").format(f.value), [], False)
-
-        self.build_ui()
-
-    def download(self, name, url, path):
-        # keep track of current download
-        self.current_download_path = path
-        self.current_download_url = url
-
-        # initialize the progress bar
-        mirror_url = url.format(self.common.settings['mirror'])
-        self.progressbar.set_fraction(0)
-        self.progressbar.set_text(_('Downloading {0}').format(name))
-        self.progressbar.show()
-        self.refresh_gtk()
-
-        if self.common.settings['update_over_tor']:
-            print _('Updating over Tor')
-            from twisted.internet.endpoints import TCP4ClientEndpoint
-            from txsocksx.http import SOCKS5Agent
-
-            torEndpoint = TCP4ClientEndpoint(reactor, '127.0.0.1', 9050)
-
-            # default mirror gets certificate pinning, only for requests that use the mirror
-            if self.common.settings['mirror'] == self.common.default_mirror and '{0}' in url:
-                agent = SOCKS5Agent(reactor, VerifyTorProjectCert(self.common.paths['torproject_pem']), proxyEndpoint=torEndpoint)
-            else:
-                agent = SOCKS5Agent(reactor, proxyEndpoint=torEndpoint)
-        else:
-            if self.common.settings['mirror'] == self.common.default_mirror and '{0}' in url:
-                agent = Agent(reactor, VerifyTorProjectCert(self.common.paths['torproject_pem']))
-            else:
-                agent = Agent(reactor)
-
-        # actually, agent needs to follow redirect
-        agent = RedirectAgent(agent)
-
-        # start the request
-        d = agent.request('GET', mirror_url,
-                          Headers({'User-Agent': ['torbrowser-launcher']}),
-                          None)
-
-        self.file_download = open(path, 'w')
-        d.addCallback(self.response_received).addErrback(self.download_error)
-
-        if not reactor.running:
-            reactor.run()
-
-    def try_default_mirror(self, widget, data=None):
-        # change mirror to default and relaunch TBL
-        self.common.settings['mirror'] = self.common.default_mirror
-        self.common.save_settings()
-        subprocess.Popen([self.common.paths['tbl_bin']])
-        self.destroy(False)
-
-    def try_tor(self, widget, data=None):
-        # set update_over_tor to true and relaunch TBL
-        self.common.settings['update_over_tor'] = True
-        self.common.save_settings()
-        subprocess.Popen([self.common.paths['tbl_bin']])
-        self.destroy(False)
-
-    def attempt_update(self):
-        # load the update check file
-        try:
-            versions = json.load(open(self.common.paths['update_check_file']))
-            latest = None
-
-            # filter linux versions
-            valid = []
-            for version in versions:
-                if '-Linux' in version:
-                    valid.append(str(version))
-            valid.sort()
-            if len(valid):
-                if len(valid) == 1:
-                    latest = valid.pop()
-                else:
-                    stable = []
-                    # remove alphas/betas
-                    for version in valid:
-                        if '-alpha-' not in version and '-beta-' not in version:
-                            stable.append(version)
-                    if len(stable):
-                        latest = stable.pop()
-                    else:
-                        latest = valid.pop()
-
-            if latest:
-                self.common.settings['latest_version'] = latest[:-len('-Linux')]
-                self.common.settings['last_update_check_timestamp'] = int(time.time())
-                self.common.settings['check_for_updates'] = False
-                self.common.save_settings()
-                self.common.build_paths(self.common.settings['latest_version'])
-                self.start_launcher()
-
-            else:
-                # failed to find the latest version
-                self.set_gui('error', _("Error checking for updates."), [], False)
-
-        except:
-            # not a valid JSON object
-            self.set_gui('error', _("Error checking for updates."), [], False)
-
-        # now start over
-        self.clear_ui()
-        self.build_ui()
-
-    def verify(self):
-        # initialize the progress bar
-        self.progressbar.set_fraction(0)
-        self.progressbar.set_text(_('Verifying Signature'))
-        self.progressbar.show()
-
-        verified = False
-        # check the sha256 file's sig, and also take the sha256 of the tarball and compare
-        p = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.common.paths['gnupg_homedir'], '--verify', self.common.paths['sha256_sig_file']])
-        self.pulse_until_process_exits(p)
-        if p.returncode == 0:
-            # compare with sha256 of the tarball
-            tarball_sha256 = hashlib.sha256(open(self.common.paths['tarball_file'], 'r').read()).hexdigest()
-            for line in open(self.common.paths['sha256_file'], 'r').readlines():
-                if tarball_sha256.lower() in line.lower() and self.common.paths['tarball_filename'] in line:
-                    verified = True
-
-        if verified:
-            self.run_task()
-        else:
-            # TODO: add the ability to report attack by posting bug to trac.torproject.org
-            self.set_gui('task', _("SIGNATURE VERIFICATION FAILED!\n\nYou might be under attack, or there might just be a networking problem. Click Start try the download again."), ['start_over'], False)
-            self.clear_ui()
-            self.build_ui()
-
-            if not reactor.running:
-                reactor.run()
-
-    def extract(self):
-        # initialize the progress bar
-        self.progressbar.set_fraction(0)
-        self.progressbar.set_text(_('Installing'))
-        self.progressbar.show()
-        self.refresh_gtk()
-
-        extracted = False
-        try:
-            if self.common.paths['tarball_file'][-2:] == 'xz':
-                # if tarball is .tar.xz
-                xz = lzma.LZMAFile(self.common.paths['tarball_file'])
-                tf = tarfile.open(fileobj=xz)
-                tf.extractall(self.common.paths['tbb']['dir'])
-                extracted = True
-            else:
-                # if tarball is .tar.gz
-                if tarfile.is_tarfile(self.common.paths['tarball_file']):
-                    tf = tarfile.open(self.common.paths['tarball_file'])
-                    tf.extractall(self.common.paths['tbb']['dir'])
-                    extracted = True
-        except:
-            pass
-
-        if not extracted:
-            self.set_gui('task', _("Tor Browser Launcher doesn't understand the file format of {0}".format(self.common.paths['tarball_file'])), ['start_over'], False)
-            self.clear_ui()
-            self.build_ui()
-            return
-
-        # installation is finished, so save installed_version
-        self.common.settings['installed_version'] = self.common.settings['latest_version']
-        self.common.save_settings()
-
-        self.run_task()
-
-    def run(self, run_next_task=True):
-        devnull = open('/dev/null', 'w')
-        subprocess.Popen([self.common.paths['tbb']['start']], stdout=devnull, stderr=devnull)
-
-        # play modem sound?
-        if self.common.settings['modem_sound']:
-            try:
-                import pygame
-                pygame.mixer.init()
-                sound = pygame.mixer.Sound(self.common.paths['modem_sound'])
-                sound.play()
-                time.sleep(10)
-            except ImportError:
-                md = gtk.MessageDialog(None, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, gtk.BUTTONS_CLOSE, _("The python-pygame package is missing, the modem sound is unavailable."))
-                md.set_position(gtk.WIN_POS_CENTER)
-                md.run()
-                md.destroy()
-
-        if run_next_task:
-            self.run_task()
-
-    # make the progress bar pulse until process p (a Popen object) finishes
-    def pulse_until_process_exits(self, p):
-        while p.poll() is None:
-            time.sleep(0.01)
-            self.progressbar.pulse()
-            self.refresh_gtk()
-
-    # start over and download TBB again
-    def start_over(self):
-        self.label.set_text(_("Downloading Tor Browser Bundle over again."))
-        self.gui_tasks = ['download_tarball', 'verify', 'extract', 'run']
-        self.gui_task_i = 0
-        self.start(None)
-
-    # refresh gtk
-    def refresh_gtk(self):
-        while gtk.events_pending():
-            gtk.main_iteration(False)
-
-    # exit
-    def delete_event(self, widget, event, data=None):
-        return False
-
-    def destroy(self, widget, data=None):
-        if hasattr(self, 'file_download'):
-            self.file_download.close()
-        if hasattr(self, 'current_download_path'):
-            os.remove(self.current_download_path)
-            delattr(self, 'current_download_path')
-            delattr(self, 'current_download_url')
-        if reactor.running:
-            reactor.stop()
-
-if __name__ == "__main__":
-    with open(os.path.join(SHARE, 'version')) as buf:
-        tor_browser_launcher_version = buf.read().strip()
-
-    print _('Tor Browser Launcher')
-    print _('By Micah Lee, licensed under GPLv3')
-    print _('version {0}').format(tor_browser_launcher_version)
-    print 'https://github.com/micahflee/torbrowser-launcher'
-
-    common = TBLCommon(tor_browser_launcher_version)
-
-    # is torbrowser-launcher already running?
-    tbl_pid = common.get_pid(common.paths['tbl_bin'], True)
-    if tbl_pid:
-        print _('Tor Browser Launcher is already running (pid {0}), bringing to front').format(tbl_pid)
-        common.bring_window_to_front(tbl_pid)
-        sys.exit()
-
-    if '-settings' in sys.argv:
-        # settings mode
-        app = TBLSettings(common)
-
-    else:
-        # launcher mode
-        app = TBLLauncher(common)
+import torbrowser_launcher
+torbrowser_launcher.main()
diff --git a/torbrowser_launcher/__init__.py b/torbrowser_launcher/__init__.py
new file mode 100644
index 0000000..90e0430
--- /dev/null
+++ b/torbrowser_launcher/__init__.py
@@ -0,0 +1,63 @@
+"""
+Tor Browser Launcher
+https://github.com/micahflee/torbrowser-launcher/
+
+Copyright (c) 2013-2014 Micah Lee <micah at micahflee.com>
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+"""
+
+import os, sys
+
+from common import Common, SHARE
+from settings import Settings
+from launcher import Launcher
+
+def main():
+    with open(os.path.join(SHARE, 'version')) as buf:
+        tor_browser_launcher_version = buf.read().strip()
+
+    print _('Tor Browser Launcher')
+    print _('By Micah Lee, licensed under GPLv3')
+    print _('version {0}').format(tor_browser_launcher_version)
+    print 'https://github.com/micahflee/torbrowser-launcher'
+
+    common = Common(tor_browser_launcher_version)
+
+    # is torbrowser-launcher already running?
+    tbl_pid = common.get_pid(common.paths['tbl_bin'], True)
+    if tbl_pid:
+        print _('Tor Browser Launcher is already running (pid {0}), bringing to front').format(tbl_pid)
+        common.bring_window_to_front(tbl_pid)
+        sys.exit()
+
+    if '-settings' in sys.argv:
+        # settings mode
+        app = Settings(common)
+
+    else:
+        # launcher mode
+        app = Launcher(common)
+
+if __name__ == "__main__":
+    main()
+
diff --git a/torbrowser_launcher/common.py b/torbrowser_launcher/common.py
new file mode 100644
index 0000000..bb97567
--- /dev/null
+++ b/torbrowser_launcher/common.py
@@ -0,0 +1,290 @@
+"""
+Tor Browser Launcher
+https://github.com/micahflee/torbrowser-launcher/
+
+Copyright (c) 2013-2014 Micah Lee <micah at micahflee.com>
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+"""
+
+import os, sys, platform, subprocess, locale, pickle, psutil
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+
+SHARE = os.getenv('TBL_SHARE', sys.prefix+'/share/torbrowser-launcher')
+
+import gettext
+gettext.install('torbrowser-launcher', os.path.join(SHARE, 'locale'))
+
+from twisted.internet import gtk2reactor
+gtk2reactor.install()
+
+class Common:
+
+    def __init__(self, tbl_version):
+        print _('Initializing Tor Browser Launcher')
+        self.tbl_version = tbl_version
+
+        # initialize the app
+        self.default_mirror = 'https://www.torproject.org/dist/'
+        self.discover_arch_lang()
+        self.build_paths()
+        for d in self.paths['dirs']:
+            self.mkdir(self.paths['dirs'][d])
+        self.load_mirrors()
+        self.load_settings()
+        self.mkdir(self.paths['download_dir'])
+        self.mkdir(self.paths['tbb']['dir'])
+        self.init_gnupg()
+
+        # allow buttons to have icons
+        try:
+            gtk_settings = gtk.settings_get_default()
+            gtk_settings.props.gtk_button_images = True
+        except:
+            pass
+
+    # discover the architecture and language
+    def discover_arch_lang(self):
+        # figure out the architecture
+        self.architecture = 'x86_64' if '64' in platform.architecture()[0] else 'i686'
+
+        # figure out the language
+        available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
+        default_locale = locale.getdefaultlocale()[0]
+        if default_locale is None:
+            self.language = 'en-US'
+        else:
+            self.language = default_locale.replace('_', '-')
+            if self.language not in available_languages:
+                self.language = self.language.split('-')[0]
+                if self.language not in available_languages:
+                    for l in available_languages:
+                        if l[0:2] == self.language:
+                            self.language = l
+            # if language isn't available, default to english
+            if self.language not in available_languages:
+                self.language = 'en-US'
+
+    # build all relevant paths
+    def build_paths(self, tbb_version=None):
+        homedir = os.getenv('HOME')
+        if not homedir:
+            homedir = '/tmp/.torbrowser-'+os.getenv('USER')
+            if not os.path.exists(homedir):
+                try:
+                    os.mkdir(homedir, 0700)
+                except:
+                    self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
+        if not os.access(homedir, os.W_OK):
+            self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
+
+        tbb_config = '{0}/.config/torbrowser'.format(homedir)
+        tbb_cache = '{0}/.cache/torbrowser'.format(homedir)
+        tbb_local = '{0}/.local/share/torbrowser'.format(homedir)
+        old_tbb_data = '{0}/.torbrowser'.format(homedir)
+
+        if tbb_version:
+            # tarball filename
+            if self.architecture == 'x86_64':
+                arch = 'linux64'
+            else:
+                arch = 'linux32'
+            tarball_filename = 'tor-browser-'+arch+'-'+tbb_version+'_'+self.language+'.tar.xz'
+
+            # tarball
+            self.paths['tarball_url'] = '{0}torbrowser/'+tbb_version+'/'+tarball_filename
+            self.paths['tarball_file'] = tbb_cache+'/download/'+tarball_filename
+            self.paths['tarball_filename'] = tarball_filename
+
+            # sig
+            self.paths['sha256_file'] = tbb_cache+'/download/sha256sums.txt'
+            self.paths['sha256_sig_file'] = tbb_cache+'/download/sha256sums.txt.asc'
+            self.paths['sha256_url'] = '{0}torbrowser/'+tbb_version+'/sha256sums.txt'
+            self.paths['sha256_sig_url'] = '{0}torbrowser/'+tbb_version+'/sha256sums.txt.asc'
+        else:
+            self.paths = {
+                'dirs': {
+                    'config': tbb_config,
+                    'cache': tbb_cache,
+                    'local': tbb_local,
+                },
+                'old_data_dir': old_tbb_data,
+                'tbl_bin': __file__,
+                'icon_file': os.path.join(os.path.dirname(SHARE), 'pixmaps/torbrowser80.xpm'),
+                'torproject_pem': os.path.join(SHARE, 'torproject.pem'),
+                'erinn_key': os.path.join(SHARE, 'erinn.asc'),
+                'mirrors_txt': [os.path.join(SHARE, 'mirrors.txt'),
+                                tbb_config+'/mirrors.txt'],
+                'modem_sound': os.path.join(SHARE, 'modem.ogg'),
+                'download_dir': tbb_cache+'/download',
+                'gnupg_homedir': tbb_local+'/gnupg_homedir',
+                'settings_file': tbb_config+'/settings',
+                'update_check_url': 'https://www.torproject.org/projects/torbrowser/RecommendedTBBVersions',
+                'update_check_file': tbb_cache+'/download/RecommendedTBBVersions',
+                'tbb': {
+                    'dir': tbb_local+'/tbb/'+self.architecture,
+                    'start': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
+                    'versions': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/Docs/sources/versions',
+                },
+            }
+
+    # create a directory
+    @staticmethod
+    def mkdir(path):
+        try:
+            if not os.path.exists(path):
+                os.makedirs(path, 0700)
+                return True
+        except:
+            print _("Cannot create directory {0}").format(path)
+            return False
+        if not os.access(path, os.W_OK):
+            print _("{0} is not writable").format(path)
+            return False
+        return True
+
+    # if gnupg_homedir isn't set up, set it up
+    def init_gnupg(self):
+        if not os.path.exists(self.paths['gnupg_homedir']):
+            print _('Creating GnuPG homedir'), self.paths['gnupg_homedir']
+            self.mkdir(self.paths['gnupg_homedir'])
+        self.import_keys()
+
+    # import gpg keys
+    def import_keys(self):
+        print _('Importing keys')
+        subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['erinn_key']]).wait()
+
+    # load mirrors
+    def load_mirrors(self):
+        self.mirrors = []
+        for srcfile in self.paths['mirrors_txt']:
+            if os.path.exists(srcfile):
+                print "Successfully loaded mirrors from %s" % srcfile
+            elif not os.path.exists(srcfile):
+                print "Warning: can't load mirrors from %s" % srcfile
+                continue
+            for mirror in open(srcfile, 'r').readlines():
+                if mirror.strip() not in self.mirrors:
+                    self.mirrors.append(mirror.strip())
+
+    # load settings
+    def load_settings(self):
+        default_settings = {
+            'tbl_version': self.tbl_version,
+            'installed_version': False,
+            'latest_version': '0',
+            'update_over_tor': True,
+            'check_for_updates': False,
+            'modem_sound': False,
+            'last_update_check_timestamp': 0,
+            'mirror': self.default_mirror
+        }
+
+        if os.path.isfile(self.paths['settings_file']):
+            settings = pickle.load(open(self.paths['settings_file']))
+            resave = False
+
+            # settings migrations
+            if settings['tbl_version'] <= '0.1.0':
+                print '0.1.0 migration'
+                settings['installed_version'] = settings['installed_version']['stable']
+                settings['latest_version'] = settings['latest_version']['stable']
+                resave = True
+
+                # make new tbb folder
+                self.mkdir(self.paths['tbb']['dir'])
+                old_tbb_dir = self.paths['old_data_dir']+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language
+                new_tbb_dir = self.paths['tbb']['dir']+'/tor-browser_'+self.language
+                if os.path.isdir(old_tbb_dir):
+                    os.rename(old_tbb_dir, new_tbb_dir)
+
+            # make sure settings file is up-to-date
+            for setting in default_settings:
+                if setting not in settings:
+                    settings[setting] = default_settings[setting]
+                    resave = True
+
+            # make sure the version is current
+            if settings['tbl_version'] != self.tbl_version:
+                settings['tbl_version'] = self.tbl_version
+                resave = True
+
+            self.settings = settings
+            if resave:
+                self.save_settings()
+
+        else:
+            self.settings = default_settings
+            self.save_settings()
+
+    # save settings
+    def save_settings(self):
+        pickle.dump(self.settings, open(self.paths['settings_file'], 'w'))
+        return True
+
+    # get the process id of a program
+    @staticmethod
+    def get_pid(bin_path, python=False):
+        pid = None
+
+        for p in psutil.process_iter():
+            try:
+                if p.pid != os.getpid():
+                    exe = None
+                    if python:
+                        if len(p.cmdline) > 1:
+                            if 'python' in p.cmdline[0]:
+                                exe = p.cmdline[1]
+                    else:
+                        if len(p.cmdline) > 0:
+                            exe = p.cmdline[0]
+
+                    if exe == bin_path:
+                        pid = p.pid
+
+            except:
+                pass
+
+        return pid
+
+    # bring program's x window to front
+    @staticmethod
+    def bring_window_to_front(pid):
+        # figure out the window id
+        win_id = None
+        p = subprocess.Popen(['wmctrl', '-l', '-p'], stdout=subprocess.PIPE)
+        for line in p.stdout.readlines():
+            line_split = line.split()
+            cur_win_id = line_split[0]
+            cur_win_pid = int(line_split[2])
+            if cur_win_pid == pid:
+                win_id = cur_win_id
+
+        # bring to front
+        if win_id:
+            subprocess.call(['wmctrl', '-i', '-a', win_id])
+
+
diff --git a/torbrowser-launcher b/torbrowser_launcher/launcher.py
old mode 100755
new mode 100644
similarity index 59%
copy from torbrowser-launcher
copy to torbrowser_launcher/launcher.py
index 7bbbf01..5172512
--- a/torbrowser-launcher
+++ b/torbrowser_launcher/launcher.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 """
 Tor Browser Launcher
 https://github.com/micahflee/torbrowser-launcher/
@@ -27,25 +26,8 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 OTHER DEALINGS IN THE SOFTWARE.
 """
 
-import os
-import sys
-SHARE = os.getenv('TBL_SHARE', sys.prefix+'/share/torbrowser-launcher')
-
-import platform
-
-import gettext
-gettext.install('torbrowser-launcher', os.path.join(SHARE, 'locale'))
-
-from twisted.internet import gtk2reactor
-gtk2reactor.install()
+import os, subprocess, time, json, tarfile, hashlib, lzma
 from twisted.internet import reactor
-
-import pygtk
-pygtk.require('2.0')
-import gtk
-
-import subprocess, locale, time, pickle, json, tarfile, psutil, hashlib, lzma
-
 from twisted.web.client import Agent, RedirectAgent, ResponseDone, ResponseFailed
 from twisted.web.http_headers import Headers
 from twisted.internet.protocol import Protocol
@@ -54,21 +36,20 @@ from twisted.internet.error import DNSLookupError
 
 import OpenSSL
 
+import pygtk
+pygtk.require('2.0')
+import gtk
 
 class TryStableException(Exception):
     pass
 
-
 class TryDefaultMirrorException(Exception):
     pass
 
-
 class DownloadErrorException(Exception):
     pass
 
-
 class VerifyTorProjectCert(ClientContextFactory):
-
     def __init__(self, torproject_pem):
         self.torproject_ca = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, open(torproject_pem, 'r').read())
 
@@ -81,453 +62,7 @@ class VerifyTorProjectCert(ClientContextFactory):
     def verifyHostname(self, connection, cert, errno, depth, preverifyOK):
         return cert.digest('sha256') == self.torproject_ca.digest('sha256')
 
-
-class TBLCommon:
-
-    def __init__(self, tbl_version):
-        print _('Initializing Tor Browser Launcher')
-        self.tbl_version = tbl_version
-
-        # initialize the app
-        self.default_mirror = 'https://www.torproject.org/dist/'
-        self.discover_arch_lang()
-        self.build_paths()
-        for d in self.paths['dirs']:
-            self.mkdir(self.paths['dirs'][d])
-        self.load_mirrors()
-        self.load_settings()
-        self.mkdir(self.paths['download_dir'])
-        self.mkdir(self.paths['tbb']['dir'])
-        self.init_gnupg()
-
-        # allow buttons to have icons
-        try:
-            gtk_settings = gtk.settings_get_default()
-            gtk_settings.props.gtk_button_images = True
-        except:
-            pass
-
-    # discover the architecture and language
-    def discover_arch_lang(self):
-        # figure out the architecture
-        self.architecture = 'x86_64' if '64' in platform.architecture()[0] else 'i686'
-
-        # figure out the language
-        available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
-        default_locale = locale.getdefaultlocale()[0]
-        if default_locale is None:
-            self.language = 'en-US'
-        else:
-            self.language = default_locale.replace('_', '-')
-            if self.language not in available_languages:
-                self.language = self.language.split('-')[0]
-                if self.language not in available_languages:
-                    for l in available_languages:
-                        if l[0:2] == self.language:
-                            self.language = l
-            # if language isn't available, default to english
-            if self.language not in available_languages:
-                self.language = 'en-US'
-
-    # build all relevant paths
-    def build_paths(self, tbb_version=None):
-        homedir = os.getenv('HOME')
-        if not homedir:
-            homedir = '/tmp/.torbrowser-'+os.getenv('USER')
-            if not os.path.exists(homedir):
-                try:
-                    os.mkdir(homedir, 0700)
-                except:
-                    self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
-        if not os.access(homedir, os.W_OK):
-            self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
-
-        tbb_config = '{0}/.config/torbrowser'.format(homedir)
-        tbb_cache = '{0}/.cache/torbrowser'.format(homedir)
-        tbb_local = '{0}/.local/share/torbrowser'.format(homedir)
-        old_tbb_data = '{0}/.torbrowser'.format(homedir)
-
-        if tbb_version:
-            # tarball filename
-            if self.architecture == 'x86_64':
-                arch = 'linux64'
-            else:
-                arch = 'linux32'
-            tarball_filename = 'tor-browser-'+arch+'-'+tbb_version+'_'+self.language+'.tar.xz'
-
-            # tarball
-            self.paths['tarball_url'] = '{0}torbrowser/'+tbb_version+'/'+tarball_filename
-            self.paths['tarball_file'] = tbb_cache+'/download/'+tarball_filename
-            self.paths['tarball_filename'] = tarball_filename
-
-            # sig
-            self.paths['sha256_file'] = tbb_cache+'/download/sha256sums.txt'
-            self.paths['sha256_sig_file'] = tbb_cache+'/download/sha256sums.txt.asc'
-            self.paths['sha256_url'] = '{0}torbrowser/'+tbb_version+'/sha256sums.txt'
-            self.paths['sha256_sig_url'] = '{0}torbrowser/'+tbb_version+'/sha256sums.txt.asc'
-        else:
-            self.paths = {
-                'dirs': {
-                    'config': tbb_config,
-                    'cache': tbb_cache,
-                    'local': tbb_local,
-                },
-                'old_data_dir': old_tbb_data,
-                'tbl_bin': __file__,
-                'icon_file': os.path.join(os.path.dirname(SHARE), 'pixmaps/torbrowser80.xpm'),
-                'torproject_pem': os.path.join(SHARE, 'torproject.pem'),
-                'erinn_key': os.path.join(SHARE, 'erinn.asc'),
-                'mirrors_txt': [os.path.join(SHARE, 'mirrors.txt'),
-                                tbb_config+'/mirrors.txt'],
-                'modem_sound': os.path.join(SHARE, 'modem.ogg'),
-                'download_dir': tbb_cache+'/download',
-                'gnupg_homedir': tbb_local+'/gnupg_homedir',
-                'settings_file': tbb_config+'/settings',
-                'update_check_url': 'https://www.torproject.org/projects/torbrowser/RecommendedTBBVersions',
-                'update_check_file': tbb_cache+'/download/RecommendedTBBVersions',
-                'tbb': {
-                    'dir': tbb_local+'/tbb/'+self.architecture,
-                    'start': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
-                    'versions': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/Docs/sources/versions',
-                },
-            }
-
-    # create a directory
-    @staticmethod
-    def mkdir(path):
-        try:
-            if not os.path.exists(path):
-                os.makedirs(path, 0700)
-                return True
-        except:
-            print _("Cannot create directory {0}").format(path)
-            return False
-        if not os.access(path, os.W_OK):
-            print _("{0} is not writable").format(path)
-            return False
-        return True
-
-    # if gnupg_homedir isn't set up, set it up
-    def init_gnupg(self):
-        if not os.path.exists(self.paths['gnupg_homedir']):
-            print _('Creating GnuPG homedir'), self.paths['gnupg_homedir']
-            self.mkdir(self.paths['gnupg_homedir'])
-        self.import_keys()
-
-    # import gpg keys
-    def import_keys(self):
-        print _('Importing keys')
-        subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['erinn_key']]).wait()
-
-    # load mirrors
-    def load_mirrors(self):
-        self.mirrors = []
-        for srcfile in self.paths['mirrors_txt']:
-            if os.path.exists(srcfile):
-                print "Successfully loaded mirrors from %s" % srcfile
-            elif not os.path.exists(srcfile):
-                print "Warning: can't load mirrors from %s" % srcfile
-                continue
-            for mirror in open(srcfile, 'r').readlines():
-                if mirror.strip() not in self.mirrors:
-                    self.mirrors.append(mirror.strip())
-
-    # load settings
-    def load_settings(self):
-        default_settings = {
-            'tbl_version': self.tbl_version,
-            'installed_version': False,
-            'latest_version': '0',
-            'update_over_tor': True,
-            'check_for_updates': False,
-            'modem_sound': False,
-            'last_update_check_timestamp': 0,
-            'mirror': self.default_mirror
-        }
-
-        if os.path.isfile(self.paths['settings_file']):
-            settings = pickle.load(open(self.paths['settings_file']))
-            resave = False
-
-            # settings migrations
-            if settings['tbl_version'] <= '0.1.0':
-                print '0.1.0 migration'
-                settings['installed_version'] = settings['installed_version']['stable']
-                settings['latest_version'] = settings['latest_version']['stable']
-                resave = True
-
-                # make new tbb folder
-                self.mkdir(self.paths['tbb']['dir'])
-                old_tbb_dir = self.paths['old_data_dir']+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language
-                new_tbb_dir = self.paths['tbb']['dir']+'/tor-browser_'+self.language
-                if os.path.isdir(old_tbb_dir):
-                    os.rename(old_tbb_dir, new_tbb_dir)
-
-            # make sure settings file is up-to-date
-            for setting in default_settings:
-                if setting not in settings:
-                    settings[setting] = default_settings[setting]
-                    resave = True
-
-            # make sure the version is current
-            if settings['tbl_version'] != self.tbl_version:
-                settings['tbl_version'] = self.tbl_version
-                resave = True
-
-            self.settings = settings
-            if resave:
-                self.save_settings()
-
-        else:
-            self.settings = default_settings
-            self.save_settings()
-
-    # save settings
-    def save_settings(self):
-        pickle.dump(self.settings, open(self.paths['settings_file'], 'w'))
-        return True
-
-    # get the process id of a program
-    @staticmethod
-    def get_pid(bin_path, python=False):
-        pid = None
-
-        for p in psutil.process_iter():
-            try:
-                if p.pid != os.getpid():
-                    exe = None
-                    if python:
-                        if len(p.cmdline) > 1:
-                            if 'python' in p.cmdline[0]:
-                                exe = p.cmdline[1]
-                    else:
-                        if len(p.cmdline) > 0:
-                            exe = p.cmdline[0]
-
-                    if exe == bin_path:
-                        pid = p.pid
-
-            except:
-                pass
-
-        return pid
-
-    # bring program's x window to front
-    @staticmethod
-    def bring_window_to_front(pid):
-        # figure out the window id
-        win_id = None
-        p = subprocess.Popen(['wmctrl', '-l', '-p'], stdout=subprocess.PIPE)
-        for line in p.stdout.readlines():
-            line_split = line.split()
-            cur_win_id = line_split[0]
-            cur_win_pid = int(line_split[2])
-            if cur_win_pid == pid:
-                win_id = cur_win_id
-
-        # bring to front
-        if win_id:
-            subprocess.call(['wmctrl', '-i', '-a', win_id])
-
-
-class TBLSettings:
-    def __init__(self, common):
-        print _('Starting settings dialog')
-        self.common = common
-
-        # set up the window
-        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
-        self.window.set_title(_("Tor Browser Launcher Settings"))
-        self.window.set_icon_from_file(self.common.paths['icon_file'])
-        self.window.set_position(gtk.WIN_POS_CENTER)
-        self.window.set_border_width(10)
-        self.window.connect("delete_event", self.delete_event)
-        self.window.connect("destroy", self.destroy)
-
-        # build the rest of the UI
-        self.box = gtk.VBox(False, 10)
-        self.window.add(self.box)
-        self.box.show()
-
-        self.hbox = gtk.HBox(False, 10)
-        self.box.pack_start(self.hbox, True, True, 0)
-        self.hbox.show()
-
-        self.settings_box = gtk.VBox(False, 10)
-        self.hbox.pack_start(self.settings_box, True, True, 0)
-        self.settings_box.show()
-
-        self.labels_box = gtk.VBox(False, 10)
-        self.hbox.pack_start(self.labels_box, True, True, 0)
-        self.labels_box.show()
-
-        # download over tor
-        try:
-            import txsocksx
-            self.txsocks_found = True
-        except ImportError:
-            self.txsocks_found = False
-        self.tor_update_checkbox = gtk.CheckButton(_("Download updates over Tor (recommended)"))
-        if self.txsocks_found:
-            self.tor_update_checkbox.set_tooltip_text(_("This option is only available when using a system wide Tor installation."))
-        else:
-            self.tor_update_checkbox.set_tooltip_text(_("This option requires the python-txsocksx package."))
-
-        self.settings_box.pack_start(self.tor_update_checkbox, True, True, 0)
-        if self.common.settings['update_over_tor'] and self.txsocks_found:
-            self.tor_update_checkbox.set_active(True)
-        else:
-            self.tor_update_checkbox.set_active(False)
-
-        if self.txsocks_found == False:
-            self.tor_update_checkbox.set_sensitive(False)
-
-        self.tor_update_checkbox.show()
-
-        # check for updates
-        self.update_checkbox = gtk.CheckButton(_("Check for updates next launch"))
-        self.settings_box.pack_start(self.update_checkbox, True, True, 0)
-        if self.common.settings['check_for_updates']:
-            self.update_checkbox.set_active(True)
-        else:
-            self.update_checkbox.set_active(False)
-        self.update_checkbox.show()
-
-        # modem sound
-        self.modem_checkbox = gtk.CheckButton(_("Play modem sound, because Tor is slow :]"))
-        self.settings_box.pack_start(self.modem_checkbox, True, True, 0)
-
-        try:
-            import pygame
-            if self.common.settings['modem_sound']:
-                self.modem_checkbox.set_active(True)
-            else:
-                self.modem_checkbox.set_active(False)
-        except ImportError:
-            self.modem_checkbox.set_active(False)
-            self.modem_checkbox.set_sensitive(False)
-            self.modem_checkbox.set_tooltip_text(_("This option requires python-pygame to be installed"))
-        self.modem_checkbox.show()
-
-        # labels
-        if(self.common.settings['installed_version']):
-            self.label1 = gtk.Label(_('Installed version:\n{0}').format(self.common.settings['installed_version']))
-        else:
-            self.label1 = gtk.Label(_('Not installed'))
-        self.label1.set_line_wrap(True)
-        self.labels_box.pack_start(self.label1, True, True, 0)
-        self.label1.show()
-
-        if(self.common.settings['last_update_check_timestamp'] > 0):
-            self.label1 = gtk.Label(_('Last checked for updates:\n{0}').format(time.strftime("%B %d, %Y %I:%M %P", time.gmtime(self.common.settings['last_update_check_timestamp']))))
-        else:
-            self.label1 = gtk.Label(_('Never checked for updates'))
-        self.label1.set_line_wrap(True)
-        self.labels_box.pack_start(self.label1, True, True, 0)
-        self.label1.show()
-
-        # mirrors
-        self.mirrors_box = gtk.HBox(False, 10)
-        self.box.pack_start(self.mirrors_box, True, True, 0)
-        self.mirrors_box.show()
-
-        self.mirrors_label = gtk.Label(_('Mirror'))
-        self.mirrors_label.set_line_wrap(True)
-        self.mirrors_box.pack_start(self.mirrors_label, True, True, 0)
-        self.mirrors_label.show()
-
-        self.mirrors = gtk.combo_box_new_text()
-        for mirror in self.common.mirrors:
-            self.mirrors.append_text(mirror)
-        if self.common.settings['mirror'] in self.common.mirrors:
-            self.mirrors.set_active(self.common.mirrors.index(self.common.settings['mirror']))
-        else:
-            self.mirrors.set_active(0)
-        self.mirrors_box.pack_start(self.mirrors, True, True, 0)
-        self.mirrors.show()
-
-        # button box
-        self.button_box = gtk.HButtonBox()
-        self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
-        self.box.pack_start(self.button_box, True, True, 0)
-        self.button_box.show()
-
-        # save and launch button
-        save_launch_image = gtk.Image()
-        save_launch_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
-        self.save_launch_button = gtk.Button(_("Launch Tor Browser"))
-        self.save_launch_button.set_image(save_launch_image)
-        self.save_launch_button.connect("clicked", self.save_launch, None)
-        self.button_box.add(self.save_launch_button)
-        self.save_launch_button.show()
-
-        # save and exit button
-        save_exit_image = gtk.Image()
-        save_exit_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
-        self.save_exit_button = gtk.Button(_("Save & Exit"))
-        self.save_exit_button.set_image(save_exit_image)
-        self.save_exit_button.connect("clicked", self.save_exit, None)
-        self.button_box.add(self.save_exit_button)
-        self.save_exit_button.show()
-
-        # cancel button
-        cancel_image = gtk.Image()
-        cancel_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
-        self.cancel_button = gtk.Button(_("Cancel"))
-        self.cancel_button.set_image(cancel_image)
-        self.cancel_button.connect("clicked", self.destroy, None)
-        self.button_box.add(self.cancel_button)
-        self.cancel_button.show()
-
-        # show the window
-        self.window.show()
-
-        # start gtk
-        gtk.main()
-
-    # UI Callback for update over tor/use system tor
-    def on_system_tor_clicked(self, event):
-        if self.txsocks_found:
-            value = self.system_tor_checkbox.get_active()
-        else:
-            value = False
-
-        self.tor_update_checkbox.set_active(value)
-        self.tor_update_checkbox.set_sensitive(value)
-
-    # save and launch
-    def save_launch(self, widget, data=None):
-        self.save()
-        subprocess.Popen([self.common.paths['tbl_bin']])
-        self.destroy(False)
-
-    # save and exit
-    def save_exit(self, widget, data=None):
-        self.save()
-        self.destroy(False)
-
-    # save settings
-    def save(self):
-        # checkbox options
-        self.common.settings['update_over_tor'] = self.tor_update_checkbox.get_active()
-        self.common.settings['check_for_updates'] = self.update_checkbox.get_active()
-        self.common.settings['modem_sound'] = self.modem_checkbox.get_active()
-
-        # figure out the selected mirror
-        self.common.settings['mirror'] = self.common.mirrors[self.mirrors.get_active()]
-
-        # save them
-        self.common.save_settings()
-
-    # exit
-    def delete_event(self, widget, event, data=None):
-        return False
-
-    def destroy(self, widget, data=None):
-        gtk.main_quit()
-
-
-class TBLLauncher:
+class Launcher:
     def __init__(self, common):
         print _('Starting launcher dialog')
         self.common = common
@@ -1120,28 +655,3 @@ class TBLLauncher:
         if reactor.running:
             reactor.stop()
 
-if __name__ == "__main__":
-    with open(os.path.join(SHARE, 'version')) as buf:
-        tor_browser_launcher_version = buf.read().strip()
-
-    print _('Tor Browser Launcher')
-    print _('By Micah Lee, licensed under GPLv3')
-    print _('version {0}').format(tor_browser_launcher_version)
-    print 'https://github.com/micahflee/torbrowser-launcher'
-
-    common = TBLCommon(tor_browser_launcher_version)
-
-    # is torbrowser-launcher already running?
-    tbl_pid = common.get_pid(common.paths['tbl_bin'], True)
-    if tbl_pid:
-        print _('Tor Browser Launcher is already running (pid {0}), bringing to front').format(tbl_pid)
-        common.bring_window_to_front(tbl_pid)
-        sys.exit()
-
-    if '-settings' in sys.argv:
-        # settings mode
-        app = TBLSettings(common)
-
-    else:
-        # launcher mode
-        app = TBLLauncher(common)
diff --git a/torbrowser_launcher/settings.py b/torbrowser_launcher/settings.py
new file mode 100644
index 0000000..0aba63b
--- /dev/null
+++ b/torbrowser_launcher/settings.py
@@ -0,0 +1,231 @@
+"""
+Tor Browser Launcher
+https://github.com/micahflee/torbrowser-launcher/
+
+Copyright (c) 2013-2014 Micah Lee <micah at micahflee.com>
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+"""
+
+import subprocess, time
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+
+class Settings:
+    def __init__(self, common):
+        print _('Starting settings dialog')
+        self.common = common
+
+        # set up the window
+        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+        self.window.set_title(_("Tor Browser Launcher Settings"))
+        self.window.set_icon_from_file(self.common.paths['icon_file'])
+        self.window.set_position(gtk.WIN_POS_CENTER)
+        self.window.set_border_width(10)
+        self.window.connect("delete_event", self.delete_event)
+        self.window.connect("destroy", self.destroy)
+
+        # build the rest of the UI
+        self.box = gtk.VBox(False, 10)
+        self.window.add(self.box)
+        self.box.show()
+
+        self.hbox = gtk.HBox(False, 10)
+        self.box.pack_start(self.hbox, True, True, 0)
+        self.hbox.show()
+
+        self.settings_box = gtk.VBox(False, 10)
+        self.hbox.pack_start(self.settings_box, True, True, 0)
+        self.settings_box.show()
+
+        self.labels_box = gtk.VBox(False, 10)
+        self.hbox.pack_start(self.labels_box, True, True, 0)
+        self.labels_box.show()
+
+        # download over tor
+        try:
+            import txsocksx
+            self.txsocks_found = True
+        except ImportError:
+            self.txsocks_found = False
+        self.tor_update_checkbox = gtk.CheckButton(_("Download updates over Tor (recommended)"))
+        if self.txsocks_found:
+            self.tor_update_checkbox.set_tooltip_text(_("This option is only available when using a system wide Tor installation."))
+        else:
+            self.tor_update_checkbox.set_tooltip_text(_("This option requires the python-txsocksx package."))
+
+        self.settings_box.pack_start(self.tor_update_checkbox, True, True, 0)
+        if self.common.settings['update_over_tor'] and self.txsocks_found:
+            self.tor_update_checkbox.set_active(True)
+        else:
+            self.tor_update_checkbox.set_active(False)
+
+        if self.txsocks_found == False:
+            self.tor_update_checkbox.set_sensitive(False)
+
+        self.tor_update_checkbox.show()
+
+        # check for updates
+        self.update_checkbox = gtk.CheckButton(_("Check for updates next launch"))
+        self.settings_box.pack_start(self.update_checkbox, True, True, 0)
+        if self.common.settings['check_for_updates']:
+            self.update_checkbox.set_active(True)
+        else:
+            self.update_checkbox.set_active(False)
+        self.update_checkbox.show()
+
+        # modem sound
+        self.modem_checkbox = gtk.CheckButton(_("Play modem sound, because Tor is slow :]"))
+        self.settings_box.pack_start(self.modem_checkbox, True, True, 0)
+
+        try:
+            import pygame
+            if self.common.settings['modem_sound']:
+                self.modem_checkbox.set_active(True)
+            else:
+                self.modem_checkbox.set_active(False)
+        except ImportError:
+            self.modem_checkbox.set_active(False)
+            self.modem_checkbox.set_sensitive(False)
+            self.modem_checkbox.set_tooltip_text(_("This option requires python-pygame to be installed"))
+        self.modem_checkbox.show()
+
+        # labels
+        if(self.common.settings['installed_version']):
+            self.label1 = gtk.Label(_('Installed version:\n{0}').format(self.common.settings['installed_version']))
+        else:
+            self.label1 = gtk.Label(_('Not installed'))
+        self.label1.set_line_wrap(True)
+        self.labels_box.pack_start(self.label1, True, True, 0)
+        self.label1.show()
+
+        if(self.common.settings['last_update_check_timestamp'] > 0):
+            self.label1 = gtk.Label(_('Last checked for updates:\n{0}').format(time.strftime("%B %d, %Y %I:%M %P", time.gmtime(self.common.settings['last_update_check_timestamp']))))
+        else:
+            self.label1 = gtk.Label(_('Never checked for updates'))
+        self.label1.set_line_wrap(True)
+        self.labels_box.pack_start(self.label1, True, True, 0)
+        self.label1.show()
+
+        # mirrors
+        self.mirrors_box = gtk.HBox(False, 10)
+        self.box.pack_start(self.mirrors_box, True, True, 0)
+        self.mirrors_box.show()
+
+        self.mirrors_label = gtk.Label(_('Mirror'))
+        self.mirrors_label.set_line_wrap(True)
+        self.mirrors_box.pack_start(self.mirrors_label, True, True, 0)
+        self.mirrors_label.show()
+
+        self.mirrors = gtk.combo_box_new_text()
+        for mirror in self.common.mirrors:
+            self.mirrors.append_text(mirror)
+        if self.common.settings['mirror'] in self.common.mirrors:
+            self.mirrors.set_active(self.common.mirrors.index(self.common.settings['mirror']))
+        else:
+            self.mirrors.set_active(0)
+        self.mirrors_box.pack_start(self.mirrors, True, True, 0)
+        self.mirrors.show()
+
+        # button box
+        self.button_box = gtk.HButtonBox()
+        self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
+        self.box.pack_start(self.button_box, True, True, 0)
+        self.button_box.show()
+
+        # save and launch button
+        save_launch_image = gtk.Image()
+        save_launch_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
+        self.save_launch_button = gtk.Button(_("Launch Tor Browser"))
+        self.save_launch_button.set_image(save_launch_image)
+        self.save_launch_button.connect("clicked", self.save_launch, None)
+        self.button_box.add(self.save_launch_button)
+        self.save_launch_button.show()
+
+        # save and exit button
+        save_exit_image = gtk.Image()
+        save_exit_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
+        self.save_exit_button = gtk.Button(_("Save & Exit"))
+        self.save_exit_button.set_image(save_exit_image)
+        self.save_exit_button.connect("clicked", self.save_exit, None)
+        self.button_box.add(self.save_exit_button)
+        self.save_exit_button.show()
+
+        # cancel button
+        cancel_image = gtk.Image()
+        cancel_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
+        self.cancel_button = gtk.Button(_("Cancel"))
+        self.cancel_button.set_image(cancel_image)
+        self.cancel_button.connect("clicked", self.destroy, None)
+        self.button_box.add(self.cancel_button)
+        self.cancel_button.show()
+
+        # show the window
+        self.window.show()
+
+        # start gtk
+        gtk.main()
+
+    # UI Callback for update over tor/use system tor
+    def on_system_tor_clicked(self, event):
+        if self.txsocks_found:
+            value = self.system_tor_checkbox.get_active()
+        else:
+            value = False
+
+        self.tor_update_checkbox.set_active(value)
+        self.tor_update_checkbox.set_sensitive(value)
+
+    # save and launch
+    def save_launch(self, widget, data=None):
+        self.save()
+        subprocess.Popen([self.common.paths['tbl_bin']])
+        self.destroy(False)
+
+    # save and exit
+    def save_exit(self, widget, data=None):
+        self.save()
+        self.destroy(False)
+
+    # save settings
+    def save(self):
+        # checkbox options
+        self.common.settings['update_over_tor'] = self.tor_update_checkbox.get_active()
+        self.common.settings['check_for_updates'] = self.update_checkbox.get_active()
+        self.common.settings['modem_sound'] = self.modem_checkbox.get_active()
+
+        # figure out the selected mirror
+        self.common.settings['mirror'] = self.common.mirrors[self.mirrors.get_active()]
+
+        # save them
+        self.common.save_settings()
+
+    # exit
+    def delete_event(self, widget, event, data=None):
+        return False
+
+    def destroy(self, widget, data=None):
+        gtk.main_quit()
+
+

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/collab-maint/torbrowser-launcher.git



More information about the Pkg-anonymity-tools mailing list