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 # allow buttons to have icons
79 settings = gtk.settings_get_default()
80 settings.props.gtk_button_images = True
84 # discover the architecture and language
85 def discover_arch_lang(self):
86 # figure out the architecture
87 (sysname, nodename, release, version, machine) = os.uname()
88 self.architecture = machine
90 # figure out the language
91 available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
92 default_locale = locale.getdefaultlocale()[0]
93 if default_locale == None:
94 self.language = 'en-US'
96 self.language = default_locale.replace('_', '-')
97 if self.language not in available_languages:
98 self.language = self.language.split('-')[0]
99 if self.language not in available_languages:
100 for l in available_languages:
101 if l[0:2] == self.language:
103 # if language isn't available, default to english
104 if self.language not in available_languages:
105 self.language = 'en-US'
107 # build all relevant paths
108 def build_paths(self, tbb_version = None):
109 homedir = os.getenv('HOME')
111 homedir = '/tmp/.torbrowser-'+os.getenv('USER')
112 if os.path.exists(homedir) == False:
114 os.mkdir(homedir, 0700)
116 self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
117 if not os.access(homedir, os.W_OK):
118 self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
120 tbb_data = '%s/.torbrowser' % homedir
123 tarball_filename = 'tor-browser-gnu-linux-'+self.architecture+'-'+tbb_version+'-dev-'+self.language+'.tar.gz'
124 self.paths['file']['tarball'] = tbb_data+'/download/'+tarball_filename
125 self.paths['file']['tarball_sig'] = tbb_data+'/download/'+tarball_filename+'.asc'
126 self.paths['url']['tarball'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename
127 self.paths['url']['tarball_sig'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename+'.asc'
128 self.paths['filename']['tarball'] = tarball_filename
129 self.paths['filename']['tarball_sig'] = tarball_filename+'.asc'
135 'download': tbb_data+'/download',
136 'tbb': tbb_data+'/tbb/'+self.architecture,
137 'gnupg_homedir': tbb_data+'/gnupg_homedir'
140 'tbl_bin': '/usr/bin/torbrowser-launcher',
141 'settings': tbb_data+'/settings',
142 'version': tbb_data+'/version',
143 'start': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
144 'vidalia_bin': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
145 'firefox_bin': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
146 'firefox_profile': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
147 'update_check': tbb_data+'/download/RecommendedTBBVersions',
148 'icon': '/usr/share/pixmaps/torbrowser80.xpm',
149 'torproject_pem': '/usr/share/torbrowser-launcher/torproject.pem',
150 'erinn_key': '/usr/share/torbrowser-launcher/erinn.asc',
151 'sebastian_key': '/usr/share/torbrowser-launcher/sebastian.asc',
152 'alexandre_key': '/usr/share/torbrowser-launcher/alexandre.asc'
155 'update_check': 'https://check.torproject.org/RecommendedTBBVersions'
161 def mkdir(self, path):
163 if os.path.exists(path) == False:
164 os.makedirs(path, 0700)
167 self.set_gui('error', _("Cannot create directory {0}").format(path), [], False)
169 if not os.access(path, os.W_OK):
170 self.set_gui('error', _("{0} is not writable").format(path), [], False)
174 # if gnupg_homedir isn't set up, set it up
175 def init_gnupg(self):
176 if not os.path.exists(self.paths['dir']['gnupg_homedir']):
177 print _('Creating GnuPG homedir'), self.paths['dir']['gnupg_homedir']
178 if self.mkdir(self.paths['dir']['gnupg_homedir']):
180 print _('Importing keys')
181 p1 = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['dir']['gnupg_homedir'], '--import', self.paths['file']['erinn_key']])
183 p2 = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['dir']['gnupg_homedir'], '--import', self.paths['file']['sebastian_key']])
185 p3 = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['dir']['gnupg_homedir'], '--import', self.paths['file']['alexandre_key']])
189 def load_settings(self):
190 if os.path.isfile(self.paths['file']['settings']):
191 self.settings = pickle.load(open(self.paths['file']['settings']))
193 if not 'installed_version' in self.settings:
195 if not 'latest_version' in self.settings:
197 if not 'last_update_check_timestamp' in self.settings:
201 'installed_version': False,
202 'latest_version': '0',
203 'last_update_check_timestamp': 0
209 def save_settings(self):
210 pickle.dump(self.settings, open(self.paths['file']['settings'], 'w'))
213 # get the process id of a program
214 def get_pid(self, bin_path, python = False):
217 for p in psutil.process_iter():
219 if p.pid != os.getpid():
222 if len(p.cmdline) > 1:
223 if 'python' in p.cmdline[0]:
226 if len(p.cmdline) > 0:
237 # bring program's x window to front
238 def bring_window_to_front(self, pid):
239 # figure out the window id
241 p = subprocess.Popen(['wmctrl', '-l', '-p'], stdout=subprocess.PIPE)
242 for line in p.stdout.readlines():
243 line_split = line.split()
244 cur_win_id = line_split[0]
245 cur_win_pid = int(line_split[2])
246 if cur_win_pid == pid:
251 subprocess.call(['wmctrl', '-i', '-a', win_id])
254 def __init__(self, common):
255 print _('Starting settings dialog')
257 self.common.load_settings()
260 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
261 self.window.set_title(_("Tor Browser Launcher Settings"))
262 self.window.set_icon_from_file(self.common.paths['file']['icon'])
263 self.window.set_position(gtk.WIN_POS_CENTER)
264 self.window.set_border_width(10)
265 self.window.connect("delete_event", self.delete_event)
266 self.window.connect("destroy", self.destroy)
268 # build the rest of the UI
269 self.box = gtk.VBox(False, 10)
270 self.window.add(self.box)
273 self.hbox = gtk.HBox(False, 10)
274 self.box.pack_start(self.hbox, True, True, 0)
277 self.settings_box = gtk.VBox(False, 10)
278 self.hbox.pack_start(self.settings_box, True, True, 0)
279 self.settings_box.show()
281 self.labels_box = gtk.VBox(False, 10)
282 self.hbox.pack_start(self.labels_box, True, True, 0)
283 self.labels_box.show()
286 self.pref_ver_box = gtk.HBox(False, 10)
287 self.settings_box.pack_start(self.pref_ver_box, True, True, 0)
288 self.pref_ver_box.show()
290 self.pref_ver_label = gtk.Label(_('I prefer'))
291 self.pref_ver_label.set_line_wrap(True)
292 self.pref_ver_box.pack_start(self.pref_ver_label, True, True, 0)
293 self.pref_ver_label.show()
296 _('Tor Browser Bundle - stable'),
297 _('Tor Browser Bundle - alpha'),
298 _('Obsfproxy Tor Browser Bundle - stable'),
299 _('Obsfproxy Tor Browser Bundle - alpha')
301 self.pref_ver = gtk.combo_box_new_text()
302 for option in options:
303 self.pref_ver.append_text(option)
304 self.pref_ver.set_active(0)
305 self.pref_ver_box.pack_start(self.pref_ver, True, True, 0)
309 self.tor_update_checkbox = gtk.CheckButton(_("Check for and download updates over Tor"))
310 self.settings_box.pack_start(self.tor_update_checkbox, True, True, 0)
311 self.tor_update_checkbox.show()
314 self.update_checkbox = gtk.CheckButton(_("Check for updates next launch"))
315 self.settings_box.pack_start(self.update_checkbox, True, True, 0)
316 self.update_checkbox.show()
319 if(self.common.settings['installed_version']):
320 self.label1 = gtk.Label(_('Installed version:\n{0}').format(self.common.settings['installed_version']))
322 self.label1 = gtk.Label(_('Not installed'))
323 self.label1.set_line_wrap(True)
324 self.labels_box.pack_start(self.label1, True, True, 0)
327 if(self.common.settings['last_update_check_timestamp'] > 0):
328 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']))))
330 self.label1 = gtk.Label(_('Never checked for updates'))
331 self.label1.set_line_wrap(True)
332 self.labels_box.pack_start(self.label1, True, True, 0)
336 self.button_box = gtk.HButtonBox()
337 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
338 self.box.pack_start(self.button_box, True, True, 0)
339 self.button_box.show()
341 # save and launch button
342 save_launch_image = gtk.Image()
343 save_launch_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
344 self.save_launch_button = gtk.Button(_("Launch Tor Browser"))
345 self.save_launch_button.set_image(save_launch_image)
346 self.save_launch_button.connect("clicked", self.save_launch, None)
347 self.button_box.add(self.save_launch_button)
348 self.save_launch_button.show()
350 # save and exit button
351 save_exit_image = gtk.Image()
352 save_exit_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
353 self.save_exit_button = gtk.Button(_("Save & Exit"))
354 self.save_exit_button.set_image(save_exit_image)
355 self.save_exit_button.connect("clicked", self.save_exit, None)
356 self.button_box.add(self.save_exit_button)
357 self.save_exit_button.show()
360 cancel_image = gtk.Image()
361 cancel_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
362 self.cancel_button = gtk.Button(_("Cancel"))
363 self.cancel_button.set_image(cancel_image)
364 self.cancel_button.connect("clicked", self.destroy, None)
365 self.button_box.add(self.cancel_button)
366 self.cancel_button.show()
375 def save_launch(self, widget, data=None):
377 p = subprocess.Popen([self.common.paths['file']['tbl_bin']])
381 def save_exit(self, widget, data=None):
390 def delete_event(self, widget, event, data=None):
392 def destroy(self, widget, data=None):
397 def __init__(self, common):
398 print _('Starting launcher dialog')
401 self.set_gui(None, '', [])
402 self.launch_gui = True
404 # if we haven't already hit an error
405 if self.gui != 'error':
407 if self.common.load_settings():
408 self.common.build_paths(self.common.settings['latest_version'])
410 # is vidalia already running and we just need to open a new firefox?
411 if self.common.settings['installed_version']:
412 vidalia_pid = self.common.get_pid('./App/vidalia')
413 firefox_pid = self.common.get_pid(self.common.paths['file']['firefox_bin'])
415 if vidalia_pid and not firefox_pid:
416 print _('Vidalia is already open, but Firefox is closed. Launching new Firefox.')
417 self.common.bring_window_to_front(vidalia_pid)
418 subprocess.Popen([self.common.paths['file']['firefox_bin'], '-no-remote', '-profile', self.common.paths['file']['firefox_profile']])
420 elif vidalia_pid and firefox_pid:
421 print _('Vidalia and Firefox are already open, bringing them to focus')
423 # bring firefox to front, then vidalia
424 self.common.bring_window_to_front(firefox_pid)
425 self.common.bring_window_to_front(vidalia_pid)
428 # how long was it since the last update check?
429 # 86400 seconds = 24 hours
430 current_timestamp = int(time.time())
431 if current_timestamp - self.common.settings['last_update_check_timestamp'] >= 86400:
433 print 'Checking for update'
434 self.set_gui('task', _("Checking for Tor Browser update."),
435 ['download_update_check',
439 # no need to check for update
440 print _('Checked for update within 24 hours, skipping')
441 self.start_launcher()
444 self.set_gui('error', _("Error loading settings. Delete ~/.torbrowser and try again."), [])
448 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
449 self.window.set_title(_("Tor Browser"))
450 self.window.set_icon_from_file(self.common.paths['file']['icon'])
451 self.window.set_position(gtk.WIN_POS_CENTER)
452 self.window.set_border_width(10)
453 self.window.connect("delete_event", self.delete_event)
454 self.window.connect("destroy", self.destroy)
456 # build the rest of the UI
459 # download or run TBB
460 def start_launcher(self):
461 # is TBB already installed?
462 if os.path.isfile(self.common.paths['file']['start']) and os.access(self.common.paths['file']['start'], os.X_OK):
463 if self.common.settings['installed_version'] == self.common.settings['latest_version']:
464 # current version of tbb is installed, launch it
466 self.launch_gui = False
467 elif self.common.settings['installed_version'] < self.common.settings['latest_version']:
468 # there is a tbb upgrade available
469 self.set_gui('task', _("Your Tor Browser is out of date."),
471 'download_tarball_sig',
476 # for some reason the installed tbb is newer than the current version?
477 self.set_gui('error', _("Something is wrong. The version of Tor Browser Bundle you have installed is newer than the current version?"), [])
481 # are the tarball and sig already downloaded?
482 if os.path.isfile(self.common.paths['file']['tarball']) and os.path.isfile(self.common.paths['file']['tarball_sig']):
483 # start the gui with verify
484 self.set_gui('task', _("Installing Tor Browser."),
491 self.set_gui('task', _("Downloading and installing Tor Browser."),
493 'download_tarball_sig',
498 # there are different GUIs that might appear, this sets which one we want
499 def set_gui(self, gui, message, tasks, autostart=True):
501 self.gui_message = message
502 self.gui_tasks = tasks
504 self.gui_autostart = autostart
506 # set all gtk variables to False
508 if hasattr(self, 'box'):
513 self.progressbar = False
514 self.button_box = False
515 self.start_button = False
516 self.exit_button = False
518 # build the application's UI
520 self.box = gtk.VBox(False, 20)
521 self.window.add(self.box)
523 if self.gui == 'error':
525 self.label = gtk.Label( self.gui_message )
526 self.label.set_line_wrap(True)
527 self.box.pack_start(self.label, True, True, 0)
531 exit_image = gtk.Image()
532 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
533 self.exit_button = gtk.Button("Exit")
534 self.exit_button.set_image(exit_image)
535 self.exit_button.connect("clicked", self.destroy, None)
536 self.box.add(self.exit_button)
537 self.exit_button.show()
539 elif self.gui == 'task':
541 self.label = gtk.Label( self.gui_message )
542 self.label.set_line_wrap(True)
543 self.box.pack_start(self.label, True, True, 0)
547 self.progressbar = gtk.ProgressBar(adjustment=None)
548 self.progressbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT)
549 self.progressbar.set_pulse_step(0.01)
550 self.box.pack_start(self.progressbar, True, True, 0)
553 self.button_box = gtk.HButtonBox()
554 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
555 self.box.pack_start(self.button_box, True, True, 0)
556 self.button_box.show()
559 start_image = gtk.Image()
560 start_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
561 self.start_button = gtk.Button(_("Start"))
562 self.start_button.set_image(start_image)
563 self.start_button.connect("clicked", self.start, None)
564 self.button_box.add(self.start_button)
565 if not self.gui_autostart:
566 self.start_button.show()
569 exit_image = gtk.Image()
570 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
571 self.exit_button = gtk.Button(_("Exit"))
572 self.exit_button.set_image(exit_image)
573 self.exit_button.connect("clicked", self.destroy, None)
574 self.button_box.add(self.exit_button)
575 self.exit_button.show()
580 if self.gui_autostart:
583 # start button clicked, begin tasks
584 def start(self, widget, data=None):
585 # disable the start button
586 if self.start_button:
587 self.start_button.set_sensitive(False)
589 # start running tasks
592 # run the next task in the task list
596 if self.gui_task_i >= len(self.gui_tasks):
600 task = self.gui_tasks[self.gui_task_i]
602 # get ready for the next task
605 if task == 'download_update_check':
606 print _('Downloading'), self.common.paths['url']['update_check']
607 self.download('update check', self.common.paths['url']['update_check'], self.common.paths['file']['update_check'])
609 if task == 'attempt_update':
610 print _('Checking to see if update it needed')
611 self.attempt_update()
613 elif task == 'download_tarball':
614 print _('Downloading'), self.common.paths['url']['tarball']
615 self.download('tarball', self.common.paths['url']['tarball'], self.common.paths['file']['tarball'])
617 elif task == 'download_tarball_sig':
618 print _('Downloading'), self.common.paths['url']['tarball_sig']
619 self.download('signature', self.common.paths['url']['tarball_sig'], self.common.paths['file']['tarball_sig'])
621 elif task == 'verify':
622 print _('Verifying signature')
625 elif task == 'extract':
626 print _('Extracting'), self.common.paths['filename']['tarball']
630 print _('Running'), self.common.paths['file']['start']
633 elif task == 'start_over':
634 print _('Starting download over again')
637 def response_received(self, response):
638 class FileDownloader(Protocol):
639 def __init__(self, file, total, progress, done_cb):
643 self.progress = progress
644 self.all_done = done_cb
646 def dataReceived(self, bytes):
647 self.file.write(bytes)
648 self.so_far += len(bytes)
649 percent = float(self.so_far) / float(self.total)
650 self.progress.set_fraction(percent)
651 amount = float(self.so_far)
653 for (size, unit) in [(1024 * 1024, "MiB"), (1024, "KiB")]:
656 amount = amount / float(size)
659 self.progress.set_text(_('Downloaded')+(' %2.1f%% (%2.1f %s)' % ((percent * 100.0), amount, units)))
661 def connectionLost(self, reason):
662 print _('Finished receiving body:'), reason.getErrorMessage()
663 self.all_done(reason)
665 dl = FileDownloader(self.file_download, response.length, self.progressbar, self.response_finished)
666 response.deliverBody(dl)
668 def response_finished(self, msg):
669 if msg.check(ResponseDone):
670 self.file_download.close()
675 print "FINISHED", msg
676 ## FIXME handle errors
678 def download_error(self, f):
679 print _("Download error"), f
680 self.set_gui('error', _("Error starting download:\n\n{0}\n\nAre you connected to the internet?").format(f.value), [], False)
684 def download(self, name, url, path):
685 # initialize the progress bar
686 self.progressbar.set_fraction(0)
687 self.progressbar.set_text(_('Downloading {0}').format(name))
688 self.progressbar.show()
691 agent = Agent(reactor, VerifyTorProjectCert(self.common.paths['file']['torproject_pem']))
692 d = agent.request('GET', url,
693 Headers({'User-Agent': ['torbrowser-launcher']}),
696 self.file_download = open(path, 'w')
697 d.addCallback(self.response_received).addErrback(self.download_error)
699 if not reactor.running:
702 def attempt_update(self):
703 # load the update check file
705 versions = json.load(open(self.common.paths['file']['update_check']))
706 latest_version = None
709 for version in versions:
710 if str(version).find(end) != -1:
711 latest_version = str(version)
714 self.common.settings['latest_version'] = latest_version[:-len(end)]
715 self.common.settings['last_update_check_timestamp'] = int(time.time())
716 self.common.save_settings()
717 self.common.build_paths(self.common.settings['latest_version'])
718 self.start_launcher()
721 # failed to find the latest version
722 self.set_gui('error', _("Error checking for updates."), [], False)
725 # not a valid JSON object
726 self.set_gui('error', _("Error checking for updates."), [], False)
733 # initialize the progress bar
734 self.progressbar.set_fraction(0)
735 self.progressbar.set_text(_('Verifying Signature'))
736 self.progressbar.show()
738 p = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.common.paths['dir']['gnupg_homedir'], '--verify', self.common.paths['file']['tarball_sig']])
739 self.pulse_until_process_exits(p)
741 if p.returncode == 0:
744 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)
748 if not reactor.running:
752 # initialize the progress bar
753 self.progressbar.set_fraction(0)
754 self.progressbar.set_text(_('Installing'))
755 self.progressbar.show()
758 # make sure this file is a tarfile
759 if tarfile.is_tarfile(self.common.paths['file']['tarball']):
760 tf = tarfile.open(self.common.paths['file']['tarball'])
761 tf.extractall(self.common.paths['dir']['tbb'])
763 self.set_gui('task', _("Tor Browser Launcher doesn't understand the file format of {0}"), ['start_over'], False)
767 # installation is finished, so save installed_version
768 self.common.settings['installed_version'] = self.common.settings['latest_version']
769 self.common.save_settings()
773 def run(self, run_next_task = True):
774 subprocess.Popen([self.common.paths['file']['start']])
778 # make the progress bar pulse until process p (a Popen object) finishes
779 def pulse_until_process_exits(self, p):
780 while p.poll() == None:
782 self.progressbar.pulse()
785 # start over and download TBB again
786 def start_over(self):
787 self.label.set_text(_("Downloading Tor Browser Bundle over again."))
788 self.gui_tasks = ['download_tarball', 'download_tarball_sig', 'verify', 'extract', 'run']
793 def refresh_gtk(self):
794 while gtk.events_pending():
795 gtk.main_iteration(False)
798 def delete_event(self, widget, event, data=None):
800 def destroy(self, widget, data=None):
801 if hasattr(self, 'file_download'):
802 self.file_download.close()
806 if __name__ == "__main__":
807 tor_browser_launcher_version = '0.0.1'
809 print _('Tor Browser Launcher')
810 print _('By Micah Lee, licensed under GPLv3')
811 print _('version {0}').format(tor_browser_launcher_version)
812 print 'https://github.com/micahflee/torbrowser-launcher'
816 # is torbrowser-launcher already running?
817 tbl_pid = common.get_pid(common.paths['file']['tbl_bin'], True)
819 print _('Tor Browser Launcher is already running (pid {0}), bringing to front').format(tbl_pid)
820 common.bring_window_to_front(tbl_pid)
823 if '-settings' in sys.argv:
825 app = TBLSettings(common)
829 app = TBLLauncher(common)