4 https://github.com/micahflee/torbrowser-launcher/
6 Copyright (c) 2013 Micah Lee <micahflee@riseup.net>
8 Permission is hereby granted, free of charge, to any person
9 obtaining a copy of this software and associated documentation
10 files (the "Software"), to deal in the Software without
11 restriction, including without limitation the rights to use,
12 copy, modify, merge, publish, distribute, sublicense, and/or sell
13 copies of the Software, and to permit persons to whom the
14 Software is furnished to do so, subject to the following
17 The above copyright notice and this permission notice shall be
18 included in all copies or substantial portions of the Software.
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 OTHER DEALINGS IN THE SOFTWARE.
31 gettext.install('torbrowser-launcher', '/usr/share/torbrowser-launcher/locale')
33 from twisted.internet import gtk2reactor
35 from twisted.internet import reactor
41 import os, sys, subprocess, locale, urllib2, gobject, time, pickle, json, tarfile, psutil
43 from twisted.web.client import Agent, ResponseDone
44 from twisted.web.http_headers import Headers
45 from twisted.internet.protocol import Protocol
46 from twisted.internet.ssl import ClientContextFactory
48 from OpenSSL.SSL import Context, VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT
49 from OpenSSL.crypto import load_certificate, FILETYPE_PEM
51 class VerifyTorProjectCert(ClientContextFactory):
53 def __init__(self, torproject_pem):
54 self.torproject_ca = load_certificate(FILETYPE_PEM, open(torproject_pem, 'r').read())
56 def getContext(self, host, port):
57 ctx = ClientContextFactory.getContext(self)
58 ctx.set_verify_depth(0)
59 ctx.set_verify(VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, self.verifyHostname)
62 def verifyHostname(self, connection, cert, errno, depth, preverifyOK):
63 return cert.digest('sha256') == self.torproject_ca.digest('sha256')
68 print _('Initializing Tor Browser Launcher')
71 self.discover_arch_lang()
73 self.mkdir(self.paths['dir']['download'])
74 self.mkdir(self.paths['dir']['tbb'])
77 self.available_versions = {
78 'tbl_stable': _('Tor Browser Bundle - stable'),
79 'tbl_alpha': _('Tor Browser Bundle - alpha'),
80 'obs_tbl': _('Obsfproxy Tor Browser Bundle')
83 # allow buttons to have icons
85 settings = gtk.settings_get_default()
86 settings.props.gtk_button_images = True
90 # discover the architecture and language
91 def discover_arch_lang(self):
92 # figure out the architecture
93 (sysname, nodename, release, version, machine) = os.uname()
94 self.architecture = machine
96 # figure out the language
97 available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
98 default_locale = locale.getdefaultlocale()[0]
99 if default_locale == None:
100 self.language = 'en-US'
102 self.language = default_locale.replace('_', '-')
103 if self.language not in available_languages:
104 self.language = self.language.split('-')[0]
105 if self.language not in available_languages:
106 for l in available_languages:
107 if l[0:2] == self.language:
109 # if language isn't available, default to english
110 if self.language not in available_languages:
111 self.language = 'en-US'
113 # build all relevant paths
114 def build_paths(self, tbb_version = None):
115 homedir = os.getenv('HOME')
117 homedir = '/tmp/.torbrowser-'+os.getenv('USER')
118 if os.path.exists(homedir) == False:
120 os.mkdir(homedir, 0700)
122 self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
123 if not os.access(homedir, os.W_OK):
124 self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
126 tbb_data = '%s/.torbrowser' % homedir
129 tarball_filename = 'tor-browser-gnu-linux-'+self.architecture+'-'+tbb_version+'-dev-'+self.language+'.tar.gz'
130 self.paths['file']['tarball'] = tbb_data+'/download/'+tarball_filename
131 self.paths['file']['tarball_sig'] = tbb_data+'/download/'+tarball_filename+'.asc'
132 self.paths['url']['tarball'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename
133 self.paths['url']['tarball_sig'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename+'.asc'
134 self.paths['filename']['tarball'] = tarball_filename
135 self.paths['filename']['tarball_sig'] = tarball_filename+'.asc'
141 'download': tbb_data+'/download',
142 'tbb': tbb_data+'/tbb/'+self.architecture,
143 'gnupg_homedir': tbb_data+'/gnupg_homedir'
146 'tbl_bin': '/usr/bin/torbrowser-launcher',
147 'settings': tbb_data+'/settings',
148 'version': tbb_data+'/version',
149 'start': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
150 'vidalia_bin': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
151 'firefox_bin': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
152 'firefox_profile': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
153 'update_check': tbb_data+'/download/RecommendedTBBVersions',
154 'icon': '/usr/share/pixmaps/torbrowser80.xpm',
155 'torproject_pem': '/usr/share/torbrowser-launcher/torproject.pem',
156 'erinn_key': '/usr/share/torbrowser-launcher/erinn.asc',
157 'sebastian_key': '/usr/share/torbrowser-launcher/sebastian.asc',
158 'alexandre_key': '/usr/share/torbrowser-launcher/alexandre.asc'
161 'update_check': 'https://check.torproject.org/RecommendedTBBVersions'
167 def mkdir(self, path):
169 if os.path.exists(path) == False:
170 os.makedirs(path, 0700)
173 self.set_gui('error', _("Cannot create directory {0}").format(path), [], False)
175 if not os.access(path, os.W_OK):
176 self.set_gui('error', _("{0} is not writable").format(path), [], False)
180 # if gnupg_homedir isn't set up, set it up
181 def init_gnupg(self):
182 if not os.path.exists(self.paths['dir']['gnupg_homedir']):
183 print _('Creating GnuPG homedir'), self.paths['dir']['gnupg_homedir']
184 if self.mkdir(self.paths['dir']['gnupg_homedir']):
186 print _('Importing keys')
187 p1 = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['dir']['gnupg_homedir'], '--import', self.paths['file']['erinn_key']])
189 p2 = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['dir']['gnupg_homedir'], '--import', self.paths['file']['sebastian_key']])
191 p3 = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['dir']['gnupg_homedir'], '--import', self.paths['file']['alexandre_key']])
195 def load_settings(self):
196 if os.path.isfile(self.paths['file']['settings']):
197 self.settings = pickle.load(open(self.paths['file']['settings']))
199 if not 'installed_version' in self.settings:
201 if not 'latest_version' in self.settings:
203 if not 'last_update_check_timestamp' in self.settings:
207 'preferred_version': 'tbl_stable',
208 'installed_version': False,
209 'latest_version': '0',
210 'last_update_check_timestamp': 0
216 def save_settings(self):
217 pickle.dump(self.settings, open(self.paths['file']['settings'], 'w'))
220 # get the process id of a program
221 def get_pid(self, bin_path, python = False):
224 for p in psutil.process_iter():
226 if p.pid != os.getpid():
229 if len(p.cmdline) > 1:
230 if 'python' in p.cmdline[0]:
233 if len(p.cmdline) > 0:
244 # bring program's x window to front
245 def bring_window_to_front(self, pid):
246 # figure out the window id
248 p = subprocess.Popen(['wmctrl', '-l', '-p'], stdout=subprocess.PIPE)
249 for line in p.stdout.readlines():
250 line_split = line.split()
251 cur_win_id = line_split[0]
252 cur_win_pid = int(line_split[2])
253 if cur_win_pid == pid:
258 subprocess.call(['wmctrl', '-i', '-a', win_id])
261 def __init__(self, common):
262 print _('Starting settings dialog')
264 self.common.load_settings()
267 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
268 self.window.set_title(_("Tor Browser Launcher Settings"))
269 self.window.set_icon_from_file(self.common.paths['file']['icon'])
270 self.window.set_position(gtk.WIN_POS_CENTER)
271 self.window.set_border_width(10)
272 self.window.connect("delete_event", self.delete_event)
273 self.window.connect("destroy", self.destroy)
275 # build the rest of the UI
276 self.box = gtk.VBox(False, 10)
277 self.window.add(self.box)
280 self.hbox = gtk.HBox(False, 10)
281 self.box.pack_start(self.hbox, True, True, 0)
284 self.settings_box = gtk.VBox(False, 10)
285 self.hbox.pack_start(self.settings_box, True, True, 0)
286 self.settings_box.show()
288 self.labels_box = gtk.VBox(False, 10)
289 self.hbox.pack_start(self.labels_box, True, True, 0)
290 self.labels_box.show()
293 self.pref_ver_box = gtk.HBox(False, 10)
294 self.settings_box.pack_start(self.pref_ver_box, True, True, 0)
295 self.pref_ver_box.show()
297 self.pref_ver_label = gtk.Label(_('I prefer'))
298 self.pref_ver_label.set_line_wrap(True)
299 self.pref_ver_box.pack_start(self.pref_ver_label, True, True, 0)
300 self.pref_ver_label.show()
303 for i in self.common.available_versions:
304 options.append(self.common.available_versions[i])
307 self.pref_ver = gtk.combo_box_new_text()
308 for option in options:
309 self.pref_ver.append_text(option)
310 self.pref_ver.set_active(0)
311 self.pref_ver_box.pack_start(self.pref_ver, True, True, 0)
315 self.tor_update_checkbox = gtk.CheckButton(_("Check for and download updates over Tor"))
316 self.settings_box.pack_start(self.tor_update_checkbox, True, True, 0)
317 self.tor_update_checkbox.show()
320 self.update_checkbox = gtk.CheckButton(_("Check for updates next launch"))
321 self.settings_box.pack_start(self.update_checkbox, True, True, 0)
322 self.update_checkbox.show()
325 if(self.common.settings['installed_version']):
326 self.label1 = gtk.Label(_('Installed version:\n{0}').format(self.common.settings['installed_version']))
328 self.label1 = gtk.Label(_('Not installed'))
329 self.label1.set_line_wrap(True)
330 self.labels_box.pack_start(self.label1, True, True, 0)
333 if(self.common.settings['last_update_check_timestamp'] > 0):
334 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']))))
336 self.label1 = gtk.Label(_('Never checked for updates'))
337 self.label1.set_line_wrap(True)
338 self.labels_box.pack_start(self.label1, True, True, 0)
342 self.button_box = gtk.HButtonBox()
343 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
344 self.box.pack_start(self.button_box, True, True, 0)
345 self.button_box.show()
347 # save and launch button
348 save_launch_image = gtk.Image()
349 save_launch_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
350 self.save_launch_button = gtk.Button(_("Launch Tor Browser"))
351 self.save_launch_button.set_image(save_launch_image)
352 self.save_launch_button.connect("clicked", self.save_launch, None)
353 self.button_box.add(self.save_launch_button)
354 self.save_launch_button.show()
356 # save and exit button
357 save_exit_image = gtk.Image()
358 save_exit_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
359 self.save_exit_button = gtk.Button(_("Save & Exit"))
360 self.save_exit_button.set_image(save_exit_image)
361 self.save_exit_button.connect("clicked", self.save_exit, None)
362 self.button_box.add(self.save_exit_button)
363 self.save_exit_button.show()
366 cancel_image = gtk.Image()
367 cancel_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
368 self.cancel_button = gtk.Button(_("Cancel"))
369 self.cancel_button.set_image(cancel_image)
370 self.cancel_button.connect("clicked", self.destroy, None)
371 self.button_box.add(self.cancel_button)
372 self.cancel_button.show()
381 def save_launch(self, widget, data=None):
383 p = subprocess.Popen([self.common.paths['file']['tbl_bin']])
387 def save_exit(self, widget, data=None):
396 def delete_event(self, widget, event, data=None):
398 def destroy(self, widget, data=None):
403 def __init__(self, common):
404 print _('Starting launcher dialog')
407 self.set_gui(None, '', [])
408 self.launch_gui = True
410 # if we haven't already hit an error
411 if self.gui != 'error':
413 if self.common.load_settings():
414 self.common.build_paths(self.common.settings['latest_version'])
416 # is vidalia already running and we just need to open a new firefox?
417 if self.common.settings['installed_version']:
418 vidalia_pid = self.common.get_pid('./App/vidalia')
419 firefox_pid = self.common.get_pid(self.common.paths['file']['firefox_bin'])
421 if vidalia_pid and not firefox_pid:
422 print _('Vidalia is already open, but Firefox is closed. Launching new Firefox.')
423 self.common.bring_window_to_front(vidalia_pid)
424 subprocess.Popen([self.common.paths['file']['firefox_bin'], '-no-remote', '-profile', self.common.paths['file']['firefox_profile']])
426 elif vidalia_pid and firefox_pid:
427 print _('Vidalia and Firefox are already open, bringing them to focus')
429 # bring firefox to front, then vidalia
430 self.common.bring_window_to_front(firefox_pid)
431 self.common.bring_window_to_front(vidalia_pid)
434 # how long was it since the last update check?
435 # 86400 seconds = 24 hours
436 current_timestamp = int(time.time())
437 if current_timestamp - self.common.settings['last_update_check_timestamp'] >= 86400:
439 print 'Checking for update'
440 self.set_gui('task', _("Checking for Tor Browser update."),
441 ['download_update_check',
445 # no need to check for update
446 print _('Checked for update within 24 hours, skipping')
447 self.start_launcher()
450 self.set_gui('error', _("Error loading settings. Delete {0} and try again.").format(self.common.paths['file']['settings']), [])
454 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
455 self.window.set_title(_("Tor Browser"))
456 self.window.set_icon_from_file(self.common.paths['file']['icon'])
457 self.window.set_position(gtk.WIN_POS_CENTER)
458 self.window.set_border_width(10)
459 self.window.connect("delete_event", self.delete_event)
460 self.window.connect("destroy", self.destroy)
462 # build the rest of the UI
465 # download or run TBB
466 def start_launcher(self):
467 # is TBB already installed?
468 if os.path.isfile(self.common.paths['file']['start']) and os.access(self.common.paths['file']['start'], os.X_OK):
469 if self.common.settings['installed_version'] == self.common.settings['latest_version']:
470 # current version of tbb is installed, launch it
472 self.launch_gui = False
473 elif self.common.settings['installed_version'] < self.common.settings['latest_version']:
474 # there is a tbb upgrade available
475 self.set_gui('task', _("Your Tor Browser is out of date."),
477 'download_tarball_sig',
482 # for some reason the installed tbb is newer than the current version?
483 self.set_gui('error', _("Something is wrong. The version of Tor Browser Bundle you have installed is newer than the current version?"), [])
487 # are the tarball and sig already downloaded?
488 if os.path.isfile(self.common.paths['file']['tarball']) and os.path.isfile(self.common.paths['file']['tarball_sig']):
489 # start the gui with verify
490 self.set_gui('task', _("Installing Tor Browser."),
497 self.set_gui('task', _("Downloading and installing Tor Browser."),
499 'download_tarball_sig',
504 # there are different GUIs that might appear, this sets which one we want
505 def set_gui(self, gui, message, tasks, autostart=True):
507 self.gui_message = message
508 self.gui_tasks = tasks
510 self.gui_autostart = autostart
512 # set all gtk variables to False
514 if hasattr(self, 'box'):
519 self.progressbar = False
520 self.button_box = False
521 self.start_button = False
522 self.exit_button = False
524 # build the application's UI
526 self.box = gtk.VBox(False, 20)
527 self.window.add(self.box)
529 if self.gui == 'error':
531 self.label = gtk.Label( self.gui_message )
532 self.label.set_line_wrap(True)
533 self.box.pack_start(self.label, True, True, 0)
537 exit_image = gtk.Image()
538 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
539 self.exit_button = gtk.Button("Exit")
540 self.exit_button.set_image(exit_image)
541 self.exit_button.connect("clicked", self.destroy, None)
542 self.box.add(self.exit_button)
543 self.exit_button.show()
545 elif self.gui == 'task':
547 self.label = gtk.Label( self.gui_message )
548 self.label.set_line_wrap(True)
549 self.box.pack_start(self.label, True, True, 0)
553 self.progressbar = gtk.ProgressBar(adjustment=None)
554 self.progressbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT)
555 self.progressbar.set_pulse_step(0.01)
556 self.box.pack_start(self.progressbar, True, True, 0)
559 self.button_box = gtk.HButtonBox()
560 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
561 self.box.pack_start(self.button_box, True, True, 0)
562 self.button_box.show()
565 start_image = gtk.Image()
566 start_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
567 self.start_button = gtk.Button(_("Start"))
568 self.start_button.set_image(start_image)
569 self.start_button.connect("clicked", self.start, None)
570 self.button_box.add(self.start_button)
571 if not self.gui_autostart:
572 self.start_button.show()
575 exit_image = gtk.Image()
576 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
577 self.exit_button = gtk.Button(_("Exit"))
578 self.exit_button.set_image(exit_image)
579 self.exit_button.connect("clicked", self.destroy, None)
580 self.button_box.add(self.exit_button)
581 self.exit_button.show()
586 if self.gui_autostart:
589 # start button clicked, begin tasks
590 def start(self, widget, data=None):
591 # disable the start button
592 if self.start_button:
593 self.start_button.set_sensitive(False)
595 # start running tasks
598 # run the next task in the task list
602 if self.gui_task_i >= len(self.gui_tasks):
606 task = self.gui_tasks[self.gui_task_i]
608 # get ready for the next task
611 if task == 'download_update_check':
612 print _('Downloading'), self.common.paths['url']['update_check']
613 self.download('update check', self.common.paths['url']['update_check'], self.common.paths['file']['update_check'])
615 if task == 'attempt_update':
616 print _('Checking to see if update it needed')
617 self.attempt_update()
619 elif task == 'download_tarball':
620 print _('Downloading'), self.common.paths['url']['tarball']
621 self.download('tarball', self.common.paths['url']['tarball'], self.common.paths['file']['tarball'])
623 elif task == 'download_tarball_sig':
624 print _('Downloading'), self.common.paths['url']['tarball_sig']
625 self.download('signature', self.common.paths['url']['tarball_sig'], self.common.paths['file']['tarball_sig'])
627 elif task == 'verify':
628 print _('Verifying signature')
631 elif task == 'extract':
632 print _('Extracting'), self.common.paths['filename']['tarball']
636 print _('Running'), self.common.paths['file']['start']
639 elif task == 'start_over':
640 print _('Starting download over again')
643 def response_received(self, response):
644 class FileDownloader(Protocol):
645 def __init__(self, file, total, progress, done_cb):
649 self.progress = progress
650 self.all_done = done_cb
652 def dataReceived(self, bytes):
653 self.file.write(bytes)
654 self.so_far += len(bytes)
655 percent = float(self.so_far) / float(self.total)
656 self.progress.set_fraction(percent)
657 amount = float(self.so_far)
659 for (size, unit) in [(1024 * 1024, "MiB"), (1024, "KiB")]:
662 amount = amount / float(size)
665 self.progress.set_text(_('Downloaded')+(' %2.1f%% (%2.1f %s)' % ((percent * 100.0), amount, units)))
667 def connectionLost(self, reason):
668 print _('Finished receiving body:'), reason.getErrorMessage()
669 self.all_done(reason)
671 dl = FileDownloader(self.file_download, response.length, self.progressbar, self.response_finished)
672 response.deliverBody(dl)
674 def response_finished(self, msg):
675 if msg.check(ResponseDone):
676 self.file_download.close()
681 print "FINISHED", msg
682 ## FIXME handle errors
684 def download_error(self, f):
685 print _("Download error"), f
686 self.set_gui('error', _("Error starting download:\n\n{0}\n\nAre you connected to the internet?").format(f.value), [], False)
690 def download(self, name, url, path):
691 # initialize the progress bar
692 self.progressbar.set_fraction(0)
693 self.progressbar.set_text(_('Downloading {0}').format(name))
694 self.progressbar.show()
697 agent = Agent(reactor, VerifyTorProjectCert(self.common.paths['file']['torproject_pem']))
698 d = agent.request('GET', url,
699 Headers({'User-Agent': ['torbrowser-launcher']}),
702 self.file_download = open(path, 'w')
703 d.addCallback(self.response_received).addErrback(self.download_error)
705 if not reactor.running:
708 def attempt_update(self):
709 # load the update check file
711 versions = json.load(open(self.common.paths['file']['update_check']))
712 latest_version = None
715 for version in versions:
716 if str(version).find(end) != -1:
717 latest_version = str(version)
720 self.common.settings['latest_version'] = latest_version[:-len(end)]
721 self.common.settings['last_update_check_timestamp'] = int(time.time())
722 self.common.save_settings()
723 self.common.build_paths(self.common.settings['latest_version'])
724 self.start_launcher()
727 # failed to find the latest version
728 self.set_gui('error', _("Error checking for updates."), [], False)
731 # not a valid JSON object
732 self.set_gui('error', _("Error checking for updates."), [], False)
739 # initialize the progress bar
740 self.progressbar.set_fraction(0)
741 self.progressbar.set_text(_('Verifying Signature'))
742 self.progressbar.show()
744 p = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.common.paths['dir']['gnupg_homedir'], '--verify', self.common.paths['file']['tarball_sig']])
745 self.pulse_until_process_exits(p)
747 if p.returncode == 0:
750 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)
754 if not reactor.running:
758 # initialize the progress bar
759 self.progressbar.set_fraction(0)
760 self.progressbar.set_text(_('Installing'))
761 self.progressbar.show()
764 # make sure this file is a tarfile
765 if tarfile.is_tarfile(self.common.paths['file']['tarball']):
766 tf = tarfile.open(self.common.paths['file']['tarball'])
767 tf.extractall(self.common.paths['dir']['tbb'])
769 self.set_gui('task', _("Tor Browser Launcher doesn't understand the file format of {0}"), ['start_over'], False)
773 # installation is finished, so save installed_version
774 self.common.settings['installed_version'] = self.common.settings['latest_version']
775 self.common.save_settings()
779 def run(self, run_next_task = True):
780 subprocess.Popen([self.common.paths['file']['start']])
784 # make the progress bar pulse until process p (a Popen object) finishes
785 def pulse_until_process_exits(self, p):
786 while p.poll() == None:
788 self.progressbar.pulse()
791 # start over and download TBB again
792 def start_over(self):
793 self.label.set_text(_("Downloading Tor Browser Bundle over again."))
794 self.gui_tasks = ['download_tarball', 'download_tarball_sig', 'verify', 'extract', 'run']
799 def refresh_gtk(self):
800 while gtk.events_pending():
801 gtk.main_iteration(False)
804 def delete_event(self, widget, event, data=None):
806 def destroy(self, widget, data=None):
807 if hasattr(self, 'file_download'):
808 self.file_download.close()
812 if __name__ == "__main__":
813 tor_browser_launcher_version = '0.0.2'
815 print _('Tor Browser Launcher')
816 print _('By Micah Lee, licensed under GPLv3')
817 print _('version {0}').format(tor_browser_launcher_version)
818 print 'https://github.com/micahflee/torbrowser-launcher'
822 # is torbrowser-launcher already running?
823 tbl_pid = common.get_pid(common.paths['file']['tbl_bin'], True)
825 print _('Tor Browser Launcher is already running (pid {0}), bringing to front').format(tbl_pid)
826 common.bring_window_to_front(tbl_pid)
829 if '-settings' in sys.argv:
831 app = TBLSettings(common)
835 app = TBLLauncher(common)