]> git.lizzy.rs Git - torbrowser-launcher.git/blob - torbrowser-launcher
added license and copyright header to source files. fixes #30.
[torbrowser-launcher.git] / torbrowser-launcher
1 """
2 Tor Browser Launcher
3 https://github.com/micahflee/torbrowser-launcher/
4
5 Copyright (c) 2013 Micah Lee
6 All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions
10 are met:
11 1. Redistributions of source code must retain the above copyright
12    notice, this list of conditions and the following disclaimer.
13 2. Redistributions in binary form must reproduce the above copyright
14    notice, this list of conditions and the following disclaimer in the
15    documentation and/or other materials provided with the distribution.
16 3. Neither the name of the University nor the names of its contributors
17    may be used to endorse or promote products derived from this software
18    without specific prior written permission.
19
20 THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 SUCH DAMAGE.
31 """
32
33 #!/usr/bin/env python
34
35 import gettext
36 gettext.install('torbrowser-launcher', '/usr/share/torbrowser-launcher/locale')
37
38 from twisted.internet import gtk2reactor
39 gtk2reactor.install()
40 from twisted.internet import reactor
41
42 import pygtk
43 pygtk.require('2.0')
44 import gtk
45
46 import os, sys, subprocess, locale, urllib2, gobject, time, pickle, json, tarfile, psutil
47
48 from twisted.web.client import Agent, ResponseDone
49 from twisted.web.http_headers import Headers
50 from twisted.internet.protocol import Protocol
51 from twisted.internet.ssl import ClientContextFactory
52
53 from OpenSSL.SSL import Context, VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT
54 from OpenSSL.crypto import load_certificate, FILETYPE_PEM
55
56 class VerifyTorProjectCert(ClientContextFactory):
57
58     def __init__(self, torproject_pem):
59         self.torproject_ca = load_certificate(FILETYPE_PEM, open(torproject_pem, 'r').read())
60
61     def getContext(self, host, port):
62         ctx = ClientContextFactory.getContext(self)
63         ctx.set_verify_depth(0)
64         ctx.set_verify(VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, self.verifyHostname)
65         return ctx
66
67     def verifyHostname(self, connection, cert, errno, depth, preverifyOK):
68         return cert.digest('sha256') == self.torproject_ca.digest('sha256')
69
70
71 class TorBrowserLauncher:
72     def __init__(self):
73         # initialize the app
74         self.set_gui(None, '', [])
75         self.discover_arch_lang()
76         self.build_paths()
77         self.mkdir(self.paths['dir']['download'])
78         self.mkdir(self.paths['dir']['tbb'])
79         self.init_gnupg()
80
81         # allow buttons to have icons
82         try:
83             settings = gtk.settings_get_default()
84             settings.props.gtk_button_images = True
85         except:
86             pass
87
88         self.launch_gui = True
89
90         # if we haven't already hit an error
91         if self.gui != 'error':
92             # load settings
93             if self.load_settings():
94                 self.build_paths(self.settings['latest_version'])
95
96                 # is tbb already running and we just need to open a new firefox?
97                 if self.settings['installed_version']:
98                     vidalia_pid = None
99                     firefox_pid = None
100                     for p in psutil.process_iter():
101                         try:
102                             exe = None
103
104                             # old versions of psutil don't have exe
105                             if hasattr(p, 'exe'):
106                                 exe = p.exe
107                             # need to rely on cmdline instead
108                             else:
109                                 if len(p.cmdline) > 0:
110                                     exe = p.cmdline[0]
111                             
112                             if exe == self.paths['file']['vidalia_bin'] or exe == './App/vidalia':
113                                 vidalia_pid = p.pid
114                             if exe == self.paths['file']['firefox_bin']:
115                                 firefox_pid = p.pid
116
117                         except:
118                             pass
119
120                     if vidalia_pid and not firefox_pid:
121                         print _('Vidalia is already open, but Firefox is closed. Launching new Firefox.')
122                         subprocess.Popen([self.paths['file']['firefox_bin'], '-no-remote', '-profile', self.paths['file']['firefox_profile']])
123                         return
124                     elif vidalia_pid and firefox_pid:
125                         print _('Vidalia and Firefox are already open, bringing them to focus')
126
127                         # figure out the window ids of vidalia and firefox
128                         vidalia_win_id = None
129                         firefox_win_id = None
130                         p = subprocess.Popen(['wmctrl', '-l', '-p'], stdout=subprocess.PIPE)
131                         for line in p.stdout.readlines():
132                             line_split = line.split()
133                             win_id = line_split[0]
134                             win_pid = int(line_split[2])
135                             if win_pid == vidalia_pid:
136                                 vidalia_win_id = win_id
137                             if win_pid == firefox_pid:
138                                 firefox_win_id = win_id
139
140                         # bring firefox to front, then vidalia
141                         if firefox_win_id:
142                             subprocess.call(['wmctrl', '-i', '-a', firefox_win_id])
143                         if vidalia_win_id:
144                             subprocess.call(['wmctrl', '-i', '-a', vidalia_win_id])
145
146                         return
147
148                 # how long was it since the last update check?
149                 # 86400 seconds = 24 hours
150                 current_timestamp = int(time.time())
151                 if current_timestamp - self.settings['last_update_check_timestamp'] >= 86400:
152                     # check for update
153                     print 'Checking for update'
154                     self.set_gui('task', _("Checking for Tor Browser update."), 
155                         ['download_update_check', 
156                          'attempt_update'])
157
158                 else:
159                     # no need to check for update
160                     print _('Checked for update within 24 hours, skipping')
161                     self.start_launcher()
162
163             else:
164                 self.set_gui('error', _("Error loading settings. Delete ~/.torbrowser and try again."), [])
165
166         if self.launch_gui:
167             # set up the window
168             self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
169             self.window.set_title(_("Tor Browser"))
170             self.window.set_icon_from_file(self.paths['file']['icon'])
171             self.window.set_position(gtk.WIN_POS_CENTER)
172             self.window.set_border_width(10)
173             self.window.connect("delete_event", self.delete_event)
174             self.window.connect("destroy", self.destroy)
175
176             # build the rest of the UI
177             self.build_ui()
178
179     # download or run TBB
180     def start_launcher(self):
181       # is TBB already installed?
182       if os.path.isfile(self.paths['file']['start']) and os.access(self.paths['file']['start'], os.X_OK):
183         if self.settings['installed_version'] == self.settings['latest_version']:
184           # current version of tbb is installed, launch it
185           self.run(False)
186           self.launch_gui = False
187         elif self.settings['installed_version'] < self.settings['latest_version']:
188           # there is a tbb upgrade available
189           self.set_gui('task', _("Your Tor Browser is out of date."), 
190             ['download_tarball', 
191              'download_tarball_sig', 
192              'verify', 
193              'extract', 
194              'run'])
195         else:
196           # for some reason the installed tbb is newer than the current version?
197           self.set_gui('error', _("Something is wrong. The version of Tor Browser Bundle you have installed is newer than the current version?"), [])
198
199       # not installed
200       else:
201           # are the tarball and sig already downloaded?
202           if os.path.isfile(self.paths['file']['tarball']) and os.path.isfile(self.paths['file']['tarball_sig']):
203               # start the gui with verify
204               self.set_gui('task', _("Installing Tor Browser."), 
205                   ['verify', 
206                    'extract', 
207                    'run'])
208
209           # first run
210           else:
211               self.set_gui('task', _("Downloading and installing Tor Browser."), 
212                   ['download_tarball', 
213                    'download_tarball_sig', 
214                    'verify', 
215                    'extract', 
216                    'run'])
217    
218     # discover the architecture and language
219     def discover_arch_lang(self):
220         # figure out the architecture
221         (sysname, nodename, release, version, machine) = os.uname()
222         self.architecture = machine
223
224         # figure out the language
225         available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
226         default_locale = locale.getdefaultlocale()[0]
227         if default_locale == None:
228             self.language = 'en-US'
229         else:
230             self.language = default_locale.replace('_', '-')
231             if self.language not in available_languages:
232                 self.language = self.language.split('-')[0]
233                 if self.language not in available_languages:
234                     for l in available_languages:
235                         if l[0:2] == self.language:
236                             self.language = l
237             # if language isn't available, default to english
238             if self.language not in available_languages:
239                 self.language = 'en-US'
240
241     # build all relevant paths
242     def build_paths(self, tbb_version = None):
243         homedir = os.getenv('HOME')
244         if not homedir:
245             homedir = '/tmp/.torbrowser-'+os.getenv('USER')
246             if os.path.exists(homedir) == False:
247                 try:
248                     os.mkdir(homedir, 0700)
249                 except:
250                     self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
251         if not os.access(homedir, os.W_OK):
252             self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
253
254         tbb_data = '%s/.torbrowser' % homedir
255
256         if tbb_version:
257             tarball_filename = 'tor-browser-gnu-linux-'+self.architecture+'-'+tbb_version+'-dev-'+self.language+'.tar.gz'
258             self.paths['file']['tarball'] = tbb_data+'/download/'+tarball_filename
259             self.paths['file']['tarball_sig'] = tbb_data+'/download/'+tarball_filename+'.asc'
260             self.paths['url']['tarball'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename
261             self.paths['url']['tarball_sig'] = 'https://www.torproject.org/dist/torbrowser/linux/'+tarball_filename+'.asc'
262             self.paths['filename']['tarball'] = tarball_filename
263             self.paths['filename']['tarball_sig'] = tarball_filename+'.asc'
264
265         else:
266             self.paths = {
267                 'dir': {
268                     'data': tbb_data,
269                     'download': tbb_data+'/download',
270                     'tbb': tbb_data+'/tbb/'+self.architecture,
271                     'gnupg_homedir': tbb_data+'/gnupg_homedir'
272                 },
273                 'file': {
274                     'settings': tbb_data+'/settings',
275                     'version': tbb_data+'/version',
276                     'start': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
277                     'vidalia_bin': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/App/vidalia',
278                     'firefox_bin': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/App/Firefox/firefox',
279                     'firefox_profile': tbb_data+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
280                     'update_check': tbb_data+'/download/RecommendedTBBVersions',
281                     'icon': '/usr/share/pixmaps/torbrowser80.xpm',
282                     'torproject_pem': '/usr/share/torbrowser-launcher/torproject.pem',
283                     'erinn_key': '/usr/share/torbrowser-launcher/erinn.asc',
284                     'sebastian_key': '/usr/share/torbrowser-launcher/sebastian.asc'
285                 },
286                 'url': {
287                     'update_check': 'https://check.torproject.org/RecommendedTBBVersions'
288                 },
289                 'filename': {}
290             }
291
292     # create a directory
293     def mkdir(self, path):
294         try:
295             if os.path.exists(path) == False:
296                 os.makedirs(path, 0700)
297                 return True
298         except:
299             self.set_gui('error', _("Cannot create directory {0}").format(path), [], False)
300             return False
301         if not os.access(path, os.W_OK):
302             self.set_gui('error', _("{0} is not writable").format(path), [], False)
303             return False
304         return True
305
306     # if gnupg_homedir isn't set up, set it up
307     def init_gnupg(self):
308         if not os.path.exists(self.paths['dir']['gnupg_homedir']):
309             print _('Creating GnuPG homedir'), self.paths['dir']['gnupg_homedir']
310             if self.mkdir(self.paths['dir']['gnupg_homedir']):
311                 # import keys
312                 print _('Importing keys')
313                 p1 = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['dir']['gnupg_homedir'], '--import', self.paths['file']['erinn_key']])
314                 p1.wait()
315                 p2 = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['dir']['gnupg_homedir'], '--import', self.paths['file']['sebastian_key']])
316                 p2.wait()
317
318     # there are different GUIs that might appear, this sets which one we want
319     def set_gui(self, gui, message, tasks, autostart=True):
320         self.gui = gui
321         self.gui_message = message
322         self.gui_tasks = tasks
323         self.gui_task_i = 0
324         self.gui_autostart = autostart
325
326     # set all gtk variables to False
327     def clear_ui(self):
328         if hasattr(self, 'box'):
329             self.box.destroy()
330         self.box = False
331
332         self.label = False
333         self.progressbar = False
334         self.button_box = False
335         self.start_button = False
336         self.exit_button = False
337
338     # build the application's UI
339     def build_ui(self):
340         self.box = gtk.VBox(False, 20)
341         self.window.add(self.box)
342
343         if self.gui == 'error':
344             # labels
345             self.label = gtk.Label( self.gui_message ) 
346             self.label.set_line_wrap(True)
347             self.box.pack_start(self.label, True, True, 0)
348             self.label.show()
349
350             #self.label2 = gtk.Label("You can fix the problem by deleting:\n"+self.paths['dir']['data']+"\n\nHowever, you will lose all your bookmarks and other Tor Browser preferences.") 
351             #self.label2.set_line_wrap(True)
352             #self.box.pack_start(self.label2, True, True, 0)
353             #self.label2.show()
354
355             # exit button
356             exit_image = gtk.Image()
357             exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
358             self.exit_button = gtk.Button("Exit")
359             self.exit_button.set_image(exit_image)
360             self.exit_button.connect("clicked", self.destroy, None)
361             self.box.add(self.exit_button)
362             self.exit_button.show()
363
364         elif self.gui == 'task':
365             # label
366             self.label = gtk.Label( self.gui_message ) 
367             self.label.set_line_wrap(True)
368             self.box.pack_start(self.label, True, True, 0)
369             self.label.show()
370             
371             # progress bar
372             self.progressbar = gtk.ProgressBar(adjustment=None)
373             self.progressbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT)
374             self.progressbar.set_pulse_step(0.01)
375             self.box.pack_start(self.progressbar, True, True, 0)
376
377             # button box
378             self.button_box = gtk.HButtonBox()
379             self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
380             self.box.pack_start(self.button_box, True, True, 0)
381             self.button_box.show()
382
383             # start button
384             start_image = gtk.Image()
385             start_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
386             self.start_button = gtk.Button("Start")
387             self.start_button.set_image(start_image)
388             self.start_button.connect("clicked", self.start, None)
389             self.button_box.add(self.start_button)
390             if not self.gui_autostart:
391               self.start_button.show()
392
393             # exit button
394             exit_image = gtk.Image()
395             exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
396             self.exit_button = gtk.Button("Exit")
397             self.exit_button.set_image(exit_image)
398             self.exit_button.connect("clicked", self.destroy, None)
399             self.button_box.add(self.exit_button)
400             self.exit_button.show()
401
402         self.box.show()
403         self.window.show()
404
405         if self.gui_autostart:
406             self.start(None)
407
408     # start button clicked, begin tasks
409     def start(self, widget, data=None):
410         # disable the start button
411         if self.start_button:
412             self.start_button.set_sensitive(False)
413
414         # start running tasks
415         self.run_task()
416       
417     # run the next task in the task list
418     def run_task(self):
419         self.refresh_gtk()
420
421         if self.gui_task_i >= len(self.gui_tasks):
422             self.destroy(False)
423             return
424
425         task = self.gui_tasks[self.gui_task_i]
426         
427         # get ready for the next task
428         self.gui_task_i += 1
429
430         if task == 'download_update_check':
431             print _('Downloading'), self.paths['url']['update_check']
432             self.download('update check', self.paths['url']['update_check'], self.paths['file']['update_check'])
433         
434         if task == 'attempt_update':
435             print _('Checking to see if update it needed')
436             self.attempt_update()
437
438         elif task == 'download_tarball':
439             print _('Downloading'), self.paths['url']['tarball']
440             self.download('tarball', self.paths['url']['tarball'], self.paths['file']['tarball'])
441
442         elif task == 'download_tarball_sig':
443             print _('Downloading'), self.paths['url']['tarball_sig']
444             self.download('signature', self.paths['url']['tarball_sig'], self.paths['file']['tarball_sig'])
445
446         elif task == 'verify':
447             print _('Verifying signature')
448             self.verify()
449
450         elif task == 'extract':
451             print _('Extracting'), self.paths['filename']['tarball']
452             self.extract()
453
454         elif task == 'run':
455             print _('Running'), self.paths['file']['start']
456             self.run()
457         
458         elif task == 'start_over':
459             print _('Starting download over again')
460             self.start_over()
461
462     def response_received(self, response):
463         class FileDownloader(Protocol):
464             def __init__(self, file, total, progress, done_cb):
465                 self.file = file
466                 self.total = total
467                 self.so_far = 0
468                 self.progress = progress
469                 self.all_done = done_cb
470
471             def dataReceived(self, bytes):
472                 self.file.write(bytes)
473                 self.so_far += len(bytes)
474                 percent = float(self.so_far) / float(self.total)
475                 self.progress.set_fraction(percent)
476                 amount = float(self.so_far)
477                 units = "bytes"
478                 for (size, unit) in [(1024 * 1024, "MiB"), (1024, "KiB")]:
479                     if amount > size:
480                         units = unit
481                         amount = amount / float(size)
482                         break
483
484                 self.progress.set_text(_('Downloaded')+(' %2.1f%% (%2.1f %s)' % ((percent * 100.0), amount, units)))
485
486             def connectionLost(self, reason):
487                 print _('Finished receiving body:'), reason.getErrorMessage()
488                 self.all_done(reason)
489
490         dl = FileDownloader(self.file_download, response.length, self.progressbar, self.response_finished)
491         response.deliverBody(dl)
492
493     def response_finished(self, msg):
494         if msg.check(ResponseDone):
495             self.file_download.close()
496             # next task!
497             self.run_task()
498
499         else:
500             print "FINISHED", msg
501             ## FIXME handle errors
502
503     def download_error(self, f):
504         print _("Download error"), f
505         self.set_gui('error', _("Error starting download:\n\n{0}\n\nAre you connected to the internet?").format(f.value), [], False)
506         self.clear_ui()
507         self.build_ui()
508
509     def download(self, name, url, path):
510         # initialize the progress bar
511         self.progressbar.set_fraction(0) 
512         self.progressbar.set_text(_('Downloading {0}').format(name))
513         self.progressbar.show()
514         self.refresh_gtk()
515
516         agent = Agent(reactor, VerifyTorProjectCert(self.paths['file']['torproject_pem']))
517         d = agent.request('GET', url,
518                           Headers({'User-Agent': ['torbrowser-launcher']}),
519                           None)
520
521         self.file_download = open(path, 'w')
522         d.addCallback(self.response_received).addErrback(self.download_error)
523         
524         if not reactor.running:
525             reactor.run()
526
527     def attempt_update(self):
528         # load the update check file
529         try:
530             versions = json.load(open(self.paths['file']['update_check']))
531             latest_version = None
532
533             end = '-Linux'
534             for version in versions:
535                 if str(version).find(end) != -1:
536                     latest_version = str(version)
537
538             if latest_version:
539                 self.settings['latest_version'] = latest_version[:-len(end)]
540                 self.settings['last_update_check_timestamp'] = int(time.time())
541                 self.save_settings()
542                 self.build_paths(self.settings['latest_version'])
543                 self.start_launcher()
544
545             else:
546                 # failed to find the latest version
547                 self.set_gui('error', _("Error checking for updates."), [], False)
548         
549         except:
550             # not a valid JSON object
551             self.set_gui('error', _("Error checking for updates."), [], False)
552
553         # now start over
554         self.clear_ui()
555         self.build_ui()
556
557     def verify(self):
558         # initialize the progress bar
559         self.progressbar.set_fraction(0) 
560         self.progressbar.set_text(_('Verifying Signature'))
561         self.progressbar.show()
562
563         p = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['dir']['gnupg_homedir'], '--verify', self.paths['file']['tarball_sig']])
564         self.pulse_until_process_exits(p)
565         
566         if p.returncode == 0:
567             self.run_task()
568         else:
569             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)
570             self.clear_ui()
571             self.build_ui()
572
573             if not reactor.running:
574                 reactor.run()
575
576     def extract(self):
577         # initialize the progress bar
578         self.progressbar.set_fraction(0) 
579         self.progressbar.set_text(_('Installing'))
580         self.progressbar.show()
581         self.refresh_gtk()
582
583         # make sure this file is a tarfile
584         if tarfile.is_tarfile(self.paths['file']['tarball']):
585           tf = tarfile.open(self.paths['file']['tarball'])
586           tf.extractall(self.paths['dir']['tbb'])
587         else:
588             self.set_gui('task', _("Tor Browser Launcher doesn't understand the file format of {0}"), ['start_over'], False)
589             self.clear_ui()
590             self.build_ui()
591
592         # installation is finished, so save installed_version
593         self.settings['installed_version'] = self.settings['latest_version']
594         self.save_settings()
595
596         self.run_task()
597
598     def run(self, run_next_task = True):
599         subprocess.Popen([self.paths['file']['start']])
600         if run_next_task:
601             self.run_task()
602
603     # make the progress bar pulse until process p (a Popen object) finishes
604     def pulse_until_process_exits(self, p):
605         while p.poll() == None:
606             time.sleep(0.01)
607             self.progressbar.pulse()
608             self.refresh_gtk()
609
610     # start over and download TBB again
611     def start_over(self):
612         self.label.set_text(_("Downloading Tor Browser Bundle over again."))
613         self.gui_tasks = ['download_tarball', 'download_tarball_sig', 'verify', 'extract', 'run']
614         self.gui_task_i = 0
615         self.start(None)
616
617     # load settings
618     def load_settings(self):
619         if os.path.isfile(self.paths['file']['settings']):
620             self.settings = pickle.load(open(self.paths['file']['settings']))
621             # sanity checks
622             if not 'installed_version' in self.settings:
623                 return False
624             if not 'latest_version' in self.settings:
625                 return False
626             if not 'last_update_check_timestamp' in self.settings:
627                 return False
628         else:
629             self.settings = {
630                 'installed_version': False,
631                 'latest_version': '0',
632                 'last_update_check_timestamp': 0
633             }
634             self.save_settings()
635         return True
636
637     # save settings
638     def save_settings(self):
639         pickle.dump(self.settings, open(self.paths['file']['settings'], 'w'))
640         return True
641     
642     # refresh gtk
643     def refresh_gtk(self):
644         while gtk.events_pending():
645             gtk.main_iteration(False)
646
647     # exit
648     def delete_event(self, widget, event, data=None):
649         return False
650     def destroy(self, widget, data=None):
651         if hasattr(self, 'file_download'):
652             self.file_download.close()
653         if reactor.running:
654             reactor.stop()
655
656 if __name__ == "__main__":
657     tor_browser_launcher_version = '0.0.1'
658
659     print _('Tor Browser Launcher')
660     print _('By Micah Lee, licensed under GPLv3')
661     print _('version {0}').format(tor_browser_launcher_version)
662     print 'https://github.com/micahflee/torbrowser-launcher'
663
664     app = TorBrowserLauncher()
665