uzbl_config path now respects XDG_CONFIG_HOME
[uzbl-mobile] / examples / data / uzbl / scripts / uzbl_tabbed.py
1 #!/usr/bin/python
2
3 # Uzbl tabbing wrapper using a fifo socket interface
4 # Copyrite (c) 2009, Tom Adams <tom@holizz.com>
5 # Copyrite (c) 2009, quigybo <?>
6 # Copyrite (c) 2009, Mason Larobina <mason.larobina@gmail.com>
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21
22 # Author(s): 
23 #   Tom Adams <tom@holizz.com>
24 #       Wrote the original uzbl_tabbed.py as a proof of concept.
25 #
26 #   quigybo <?>
27 #       Made signifigant headway on the uzbl_tabbing.py script on the 
28 #       uzbl wiki <http://www.uzbl.org/wiki/uzbl_tabbed> 
29 #
30 #   Mason Larobina <mason.larobina@gmail.com>
31 #       Rewrite of the uzbl_tabbing.py script to use a fifo socket interface
32 #       and inherit configuration options from the user's uzbl config.
33 #
34 # Contributor(s):
35 #   (None yet)
36
37
38 # Issues: 
39 #   - status_background colour is not honoured (reverts to gtk default).
40 #   - new windows are not caught and opened in a new tab.
41 #   - need an easier way to read a uzbl instances window title instead of 
42 #     spawning a shell to spawn uzblctrl to communicate to the uzbl 
43 #     instance via socket to dump the window title to then pipe it to 
44 #     the tabbing managers fifo socket.
45 #   - probably missing some os.path.expandvars somewhere. 
46
47
48 # Todo: 
49 #   - add command line options to use a different session file, not use a
50 #     session file and or open a uri on starup. 
51 #   - ellipsize individual tab titles when the tab-list becomes over-crowded
52 #   - add "<" & ">" arrows to tablist to indicate that only a subset of the 
53 #     currently open tabs are being displayed on the tablist.
54 #   - probably missing some os.path.expandvars somewhere and other 
55 #     user-friendly.. things, this is still a very early version. 
56 #   - fix status_background issues & style tablist. 
57 #   - add the small tab-list display when both gtk tabs and text vim-like
58 #     tablist are hidden (I.e. [ 1 2 3 4 5 ])
59 #   - check spelling.
60
61
62 import pygtk
63 import gtk
64 import subprocess
65 import os
66 import re
67 import time
68 import getopt
69 import pango
70 import select
71 import sys
72 import gobject
73
74 pygtk.require('2.0')
75
76 def error(msg):
77     sys.stderr.write("%s\n"%msg)
78
79 if 'XDG_DATA_HOME' in os.environ.keys() and os.environ['XDG_DATA_HOME']:
80     data_dir = os.path.join(os.environ['XDG_DATA_HOME'], 'uzbl/')
81
82 else:
83     data_dir = os.path.join(os.environ['HOME'], '.local/share/uzbl/')
84
85 # === Default Configuration ====================================================
86
87 # Location of your uzbl configuration file.
88 if 'XDG_CONFIG_HOME' in os.environ.keys() and os.environ['XDG_CONFIG_HOME']:
89     uzbl_config = os.path.join(os.environ['XDG_CONFIG_HOME'], 'uzbl/config')
90 else:
91     uzbl_config = os.path.join(os.environ['HOME'],'.config/uzbl/config')
92
93 # All of these settings can be inherited from your uzbl config file.
94 config = {'show_tabs': True,
95   'show_gtk_tabs': False,
96   'switch_to_new_tabs': True,
97   'save_session': True,
98   'fifo_dir': '/tmp',
99   'icon_path': os.path.join(data_dir, 'uzbl.png'),
100   'session_file': os.path.join(data_dir, 'session'),
101   'tab_colours': 'foreground = "#999"',
102   'tab_text_colours': 'foreground = "#444"',
103   'selected_tab': 'foreground = "#aaa" background="#303030"',
104   'selected_tab_text': 'foreground = "green"',
105   'window_size': "800,800",
106   'monospace_size': 10, 
107   'bind_new_tab': 'gn',
108   'bind_tab_from_clipboard': 'gY', 
109   'bind_close_tab': 'gC',
110   'bind_next_tab': 'gt',
111   'bind_prev_tab': 'gT',
112   'bind_goto_tab': 'gi_',
113   'bind_goto_first': 'g<',
114   'bind_goto_last':'g>'}
115
116 # === End Configuration =======================================================
117
118 def readconfig(uzbl_config, config):
119     '''Loads relevant config from the users uzbl config file into the global
120     config dictionary.'''
121
122     if not os.path.exists(uzbl_config):
123         error("Unable to load config %r" % uzbl_config)
124         return None
125     
126     # Define parsing regular expressions
127     isint = re.compile("^[0-9]+$").match
128     findsets = re.compile("^set\s+([^\=]+)\s*\=\s*(.+)$",\
129       re.MULTILINE).findall
130
131     h = open(os.path.expandvars(uzbl_config), 'r')
132     rawconfig = h.read()
133     h.close()
134     
135     for (key, value) in findsets(rawconfig):
136         key = key.strip()
137         if key not in config.keys(): continue
138         if isint(value): value = int(value)
139         config[key] = value
140
141
142 def rmkdir(path):
143     '''Recursively make directories.
144     I.e. `mkdir -p /some/nonexistant/path/`'''
145
146     path, sep = os.path.realpath(path), os.path.sep
147     dirs = path.split(sep)
148     for i in range(2,len(dirs)+1):
149         dir = os.path.join(sep,sep.join(dirs[:i]))
150         if not os.path.exists(dir):
151             os.mkdir(dir)
152
153
154 def counter():
155     '''To infinity and beyond!'''
156
157     i = 0
158     while True:
159         i += 1
160         yield i
161
162
163 class UzblTabbed:
164     '''A tabbed version of uzbl using gtk.Notebook'''
165
166     class UzblInstance:
167         '''Uzbl instance meta-data/meta-action object.'''
168
169         def __init__(self, parent, socket, fifo, pid, url='', switch=True):
170             self.parent = parent
171             self.socket = socket # the gtk socket
172             self.fifo = fifo
173             self.pid = pid
174             self.title = "New tab"
175             self.url = url
176             self.timers = {}
177             self._lastprobe = 0
178             self._switch_on_config = switch
179             self._outgoing = []
180             self._configured = False
181
182             # Probe commands
183             self._probeurl = 'sh \'echo "url %s $6" > "%s"\'' % (self.pid,\
184               self.parent.fifo_socket)
185             
186             # As soon as the variable expansion bug is fixed in uzbl
187             # I can start using this command to fetch the winow title
188             self._probetitle = 'sh \'echo "title %s @window_title" > "%s"\'' \
189               % (self.pid, self.parent.fifo_socket)
190
191             # When notebook tab deleted the kill switch is raised.
192             self._kill = False
193              
194             # Queue binds for uzbl child
195             self.parent.config_uzbl(self)
196
197
198         def flush(self, timer_call=False):
199             '''Flush messages from the queue.'''
200             
201             if self._kill:
202                 error("Flush called on dead page.")
203                 return False
204
205             if os.path.exists(self.fifo):
206                 h = open(self.fifo, 'w')
207                 while len(self._outgoing):
208                     msg = self._outgoing.pop(0)
209                     h.write("%s\n" % msg)
210                 h.close()
211
212             elif not timer_call and self._configured:
213                 # TODO: I dont know what to do here. A previously thought
214                 # alright uzbl client fifo socket has now gone missing.
215                 # I think this should be fatal (at least for the page in
216                 # question). I'll wait until this error appears in the wild. 
217                 error("Error: fifo %r lost in action." % self.fifo)
218             
219             if not len(self._outgoing) and timer_call:
220                 self._configured = True
221
222                 if timer_call in self.timers.keys():
223                     gobject.source_remove(self.timers[timer_call])
224                     del self.timers[timer_call]
225
226                 if self._switch_on_config:
227                     notebook = list(self.parent.notebook)
228                     try:
229                         tabid = notebook.index(self.socket)
230                         self.parent.goto_tab(tabid)
231
232                     except ValueError:
233                         pass
234                 
235             return len(self._outgoing)
236
237
238         def probe(self):
239             '''Probes the client for information about its self.'''
240             
241             # Ugly way of getting the socket path. Screwed if fifo is in any
242             # other part of the fifo socket path.
243             socket = 'socket'.join(self.fifo.split('fifo'))
244             # Hackish & wasteful way of getting the window title. 
245             subcmd = 'print title %s @<document.title>@' % self.pid
246             cmd = 'uzblctrl -s "%s" -c "%s" > "%s" &' % (socket, subcmd, \
247               self.parent.fifo_socket)
248             subprocess.Popen([cmd], shell=True)
249             self.send(self._probeurl)
250             
251             # Wont work yet.
252             #self.send(self._probetitle)
253
254             self._lastprobe = time.time()
255
256
257         def send(self, msg):
258             '''Child fifo write function.'''
259
260             self._outgoing.append(msg)
261             # Flush messages from the queue if able.
262             return self.flush()
263
264
265     def __init__(self):
266         '''Create tablist, window and notebook.'''
267         
268         self.pages = {}
269         self._pidcounter = counter()
270         self.next_pid = self._pidcounter.next
271         self._watchers = {}
272         self._timers = {}
273         self._buffer = ""
274
275         # Create main window
276         self.window = gtk.Window()
277         try: 
278             window_size = map(int, config['window_size'].split(','))
279             self.window.set_default_size(*window_size)
280
281         except:
282             error("Invalid value for default_size in config file.")
283
284         self.window.set_title("Uzbl Browser")
285         self.window.set_border_width(0)
286         
287         # Set main window icon
288         icon_path = config['icon_path']
289         if os.path.exists(icon_path):
290             self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path))
291
292         else:
293             icon_path = '/usr/share/uzbl/examples/data/uzbl/uzbl.png'
294             if os.path.exists(icon_path):
295                 self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path))
296         
297         # Attach main window event handlers
298         self.window.connect("delete-event", self.quit)
299         
300         # Create tab list 
301         if config['show_tabs']:
302             vbox = gtk.VBox()
303             self.window.add(vbox)
304
305             self.tablist = gtk.Label()
306             self.tablist.set_use_markup(True)
307             self.tablist.set_justify(gtk.JUSTIFY_LEFT)
308             self.tablist.set_line_wrap(False)
309             self.tablist.set_selectable(False)
310             self.tablist.set_padding(0,2)
311             self.tablist.set_alignment(0,0)
312             self.tablist.set_ellipsize(pango.ELLIPSIZE_END)
313             self.tablist.set_text(" ")
314             self.tablist.show()
315             vbox.pack_start(self.tablist, False, False, 0)
316         
317         # Create notebook
318         self.notebook = gtk.Notebook()
319         self.notebook.set_show_tabs(config['show_gtk_tabs'])
320         self.notebook.set_show_border(False)
321         self.notebook.connect("page-removed", self.tab_closed)
322         self.notebook.connect("switch-page", self.tab_changed)
323         self.notebook.show()
324         if config['show_tabs']:
325             vbox.pack_end(self.notebook, True, True, 0)
326             vbox.show()
327         else:
328             self.window.add(self.notebook)
329         
330         self.window.show()
331         self.wid = self.notebook.window.xid
332         # Fifo socket definition
333         self._refindfifos = re.compile('^uzbl_fifo_%s_[0-9]+$' % self.wid)
334         fifo_filename = 'uzbltabbed_%d' % os.getpid()
335         self.fifo_socket = os.path.join(config['fifo_dir'], fifo_filename)
336
337         self._watchers = {}
338         self._buffer = ""
339         self._create_fifo_socket(self.fifo_socket)
340         self._setup_fifo_watcher(self.fifo_socket)
341
342
343     def run(self):
344         
345         # Update tablist timer
346         timer = "update-tablist"
347         timerid = gobject.timeout_add(500, self.update_tablist,timer)
348         self._timers[timer] = timerid
349
350         # Due to the hackish way in which the window titles are read 
351         # too many window will cause the application to slow down insanely
352         timer = "probe-clients"
353         timerid = gobject.timeout_add(1000, self.probe_clients, timer)
354         self._timers[timer] = timerid
355
356         gtk.main()
357
358
359     def _find_fifos(self, fifo_dir):
360         '''Find all child fifo sockets in fifo_dir.'''
361         
362         dirlist = '\n'.join(os.listdir(fifo_dir))
363         allfifos = self._refindfifos.findall(dirlist)
364         return sorted(allfifos)
365
366
367     def _create_fifo_socket(self, fifo_socket):
368         '''Create interprocess communication fifo socket.''' 
369
370         if os.path.exists(fifo_socket):
371             if not os.access(fifo_socket, os.F_OK | os.R_OK | os.W_OK):
372                 os.mkfifo(fifo_socket)
373
374         else:
375             basedir = os.path.dirname(self.fifo_socket)
376             if not os.path.exists(basedir):
377                 rmkdir(basedir)
378             os.mkfifo(self.fifo_socket)
379         
380         print "Listening on %s" % self.fifo_socket
381
382
383     def _setup_fifo_watcher(self, fifo_socket, fd=None):
384         '''Open fifo socket fd and setup gobject IO_IN & IO_HUP watchers.
385         Also log the creation of a fd and store the the internal
386         self._watchers dictionary along with the filename of the fd.'''
387         
388         #TODO: Convert current self._watcher dict manipulation to the better 
389         # IMHO self._timers handling by using "timer-keys" as the keys instead
390         # of the fifo fd's as keys.
391
392         if fd:
393             os.close(fd)
394             if fd in self._watchers.keys():
395                 d = self._watchers[fd]
396                 watchers = d['watchers']
397                 for watcher in list(watchers):
398                     gobject.source_remove(watcher)
399                     watchers.remove(watcher)
400                 del self._watchers[fd]         
401         
402         fd = os.open(fifo_socket, os.O_RDONLY | os.O_NONBLOCK)
403         self._watchers[fd] = {'watchers': [], 'filename': fifo_socket}
404             
405         watcher = self._watchers[fd]['watchers'].append
406         watcher(gobject.io_add_watch(fd, gobject.IO_IN, self.read_fifo))
407         watcher(gobject.io_add_watch(fd, gobject.IO_HUP, self.fifo_hangup))
408         
409
410     def probe_clients(self, timer_call):
411         '''Load balance probe all uzbl clients for up-to-date window titles 
412         and uri's.'''
413         
414         p = self.pages 
415         probetimes = [(s, p[s]._lastprobe) for s in p.keys()]
416         socket, lasttime = sorted(probetimes, key=lambda t: t[1])[0]
417
418         if (time.time()-lasttime) > 5:
419             # Probe a uzbl instance at most once every 10 seconds
420             self.pages[socket].probe()
421
422         return True
423
424
425     def fifo_hangup(self, fd, cb_condition):
426         '''Handle fifo socket hangups.'''
427         
428         # Close fd, re-open fifo_socket and watch.
429         self._setup_fifo_watcher(self.fifo_socket, fd)
430
431         # And to kill any gobject event handlers calling this function:
432         return False
433
434
435     def read_fifo(self, fd, cb_condition):
436         '''Read from fifo socket and handle fifo socket hangups.'''
437
438         self._buffer = os.read(fd, 1024)
439         temp = self._buffer.split("\n")
440         self._buffer = temp.pop()
441
442         for cmd in [s.strip().split() for s in temp if len(s.strip())]:
443             try:
444                 #print cmd
445                 self.parse_command(cmd)
446
447             except:
448                 #raise
449                 error("Invalid command: %s" % ' '.join(cmd))
450         
451         return True
452
453     def parse_command(self, cmd):
454         '''Parse instructions from uzbl child processes.'''
455         
456         # Commands ( [] = optional, {} = required ) 
457         # new [uri]
458         #   open new tab and head to optional uri. 
459         # close [tab-num] 
460         #   close current tab or close via tab id.
461         # next [n-tabs]
462         #   open next tab or n tabs down. Supports negative indexing.
463         # prev [n-tabs]
464         #   open prev tab or n tabs down. Supports negative indexing.
465         # goto {tab-n}
466         #   goto tab n.  
467         # first
468         #   goto first tab.
469         # last
470         #   goto last tab. 
471         # title {pid} {document-title}
472         #   updates tablist title.
473         # url {pid} {document-location}
474          
475         # WARNING SOME OF THESE COMMANDS MIGHT NOT BE WORKING YET OR FAIL.
476
477         if cmd[0] == "new":
478             if len(cmd) == 2:
479                 self.new_tab(cmd[1])
480
481             else:
482                 self.new_tab()
483
484         elif cmd[0] == "newfromclip":
485             url = subprocess.Popen(['xclip','-selection','clipboard','-o'],\
486               stdout=subprocess.PIPE).communicate()[0]
487             if url:
488                 self.new_tab(url)
489
490         elif cmd[0] == "close":
491             if len(cmd) == 2:
492                 self.close_tab(int(cmd[1]))
493
494             else:
495                 self.close_tab()
496
497         elif cmd[0] == "next":
498             if len(cmd) == 2:
499                 self.next_tab(int(cmd[1]))
500                    
501             else:
502                 self.next_tab()
503
504         elif cmd[0] == "prev":
505             if len(cmd) == 2:
506                 self.prev_tab(int(cmd[1]))
507
508             else:
509                 self.prev_tab()
510         
511         elif cmd[0] == "goto":
512             self.goto_tab(int(cmd[1]))
513
514         elif cmd[0] == "first":
515             self.goto_tab(0)
516
517         elif cmd[0] == "last":
518             self.goto_tab(-1)
519
520         elif cmd[0] in ["title", "url"]:
521             if len(cmd) > 2:
522                 uzbl = self.get_uzbl_by_pid(int(cmd[1]))
523                 if uzbl:
524                     old = getattr(uzbl, cmd[0])
525                     new = ' '.join(cmd[2:])
526                     setattr(uzbl, cmd[0], new)
527                     if old != new:
528                        self.update_tablist()
529                 else:
530                     error("Cannot find uzbl instance with pid %r" % int(cmd[1]))
531         else:
532             error("Unknown command: %s" % ' '.join(cmd))
533
534     
535     def get_uzbl_by_pid(self, pid):
536         '''Return uzbl instance by pid.'''
537
538         for socket in self.pages.keys():
539             if self.pages[socket].pid == pid:
540                 return self.pages[socket]
541         return False
542    
543
544     def new_tab(self,url='', switch=True):
545         '''Add a new tab to the notebook and start a new instance of uzbl.
546         Use the switch option to negate config['switch_to_new_tabs'] option 
547         when you need to load multiple tabs at a time (I.e. like when 
548         restoring a session from a file).'''
549        
550         pid = self.next_pid()
551         socket = gtk.Socket()
552         socket.show()
553         self.notebook.append_page(socket)
554         sid = socket.get_id()
555         
556         if url:
557             url = '--uri %s' % url
558         
559         fifo_filename = 'uzbl_fifo_%s_%0.2d' % (self.wid, pid)
560         fifo_socket = os.path.join(config['fifo_dir'], fifo_filename)
561         uzbl = self.UzblInstance(self, socket, fifo_socket, pid,\
562           url=url, switch=switch)
563         self.pages[socket] = uzbl
564         cmd = 'uzbl -s %s -n %s_%0.2d %s &' % (sid, self.wid, pid, url)
565         subprocess.Popen([cmd], shell=True)        
566         
567         # Add gobject timer to make sure the config is pushed when fifo socket
568         # has been created. 
569         timerid = gobject.timeout_add(100, uzbl.flush, "flush-initial-config")
570         uzbl.timers['flush-initial-config'] = timerid
571     
572         self.update_tablist()
573
574
575     def config_uzbl(self, uzbl):
576         '''Send bind commands for tab new/close/next/prev to a uzbl 
577         instance.'''
578
579         binds = []
580         bind_format = 'bind %s = sh "echo \\\"%s\\\" > \\\"%s\\\""'
581         bind = lambda key, action: binds.append(bind_format % (key, action, \
582           self.fifo_socket))
583         
584         # Keys are defined in the config section
585         # bind ( key , command back to fifo ) 
586         bind(config['bind_new_tab'], 'new')
587         bind(config['bind_tab_from_clipboard'], 'newfromclip')
588         bind(config['bind_close_tab'], 'close')
589         bind(config['bind_next_tab'], 'next')
590         bind(config['bind_prev_tab'], 'prev')
591         bind(config['bind_goto_tab'], 'goto %s')
592         bind(config['bind_goto_first'], 'goto 0')
593         bind(config['bind_goto_last'], 'goto -1')
594
595         uzbl.send("\n".join(binds))
596
597
598     def goto_tab(self, n):
599         '''Goto tab n (supports negative indexing).'''
600         
601         notebook = list(self.notebook)
602         
603         try: 
604             page = notebook[n]
605             i = notebook.index(page)
606             self.notebook.set_current_page(i)
607         
608         except IndexError:
609             pass
610
611         self.update_tablist()
612
613
614     def next_tab(self, n=1):
615         '''Switch to next tab or n tabs right.'''
616         
617         if n >= 1:
618             numofpages = self.notebook.get_n_pages()
619             pagen = self.notebook.get_current_page() + n
620             self.notebook.set_current_page( pagen % numofpages ) 
621
622         self.update_tablist()
623
624
625     def prev_tab(self, n=1):
626         '''Switch to prev tab or n tabs left.'''
627         
628         if n >= 1:
629             numofpages = self.notebook.get_n_pages()
630             pagen = self.notebook.get_current_page() - n
631             while pagen < 0: 
632                 pagen += numofpages
633             self.notebook.set_current_page(pagen)
634
635         self.update_tablist()
636
637
638     def close_tab(self, tabid=None):
639         '''Closes current tab. Supports negative indexing.'''
640         
641         if not tabid: 
642             tabid = self.notebook.get_current_page()
643         
644         try: 
645             socket = list(self.notebook)[tabid]
646
647         except IndexError:
648             error("Invalid index. Cannot close tab.")
649             return False
650
651         uzbl = self.pages[socket]
652         # Kill timers:
653         for timer in uzbl.timers.keys():
654             error("Removing timer %r %r" % (timer, uzbl.timers[timer]))
655             gobject.source_remove(uzbl.timers[timer])
656
657         uzbl._outgoing = []
658         uzbl._kill = True
659         del self.pages[socket]
660         self.notebook.remove_page(tabid)
661
662         self.update_tablist()
663
664
665     def tab_closed(self, notebook, socket, page_num):
666         '''Close the window if no tabs are left. Called by page-removed 
667         signal.'''
668         
669         if socket in self.pages.keys():
670             uzbl = self.pages[socket]
671             for timer in uzbl.timers.keys():
672                 error("Removing timer %r %r" % (timer, uzbl.timers[timer]))
673                 gobject.source_remove(uzbl.timers[timer])
674
675             uzbl._outgoing = []
676             uzbl._kill = True
677             del self.pages[socket]
678         
679         if self.notebook.get_n_pages() == 0:
680             self.quit()
681
682         self.update_tablist()
683
684
685     def tab_changed(self, notebook, page, page_num):
686         '''Refresh tab list. Called by switch-page signal.'''
687
688         self.update_tablist()
689
690
691     def update_tablist(self, timer_call=None):
692         '''Upate tablist status bar.'''
693
694         pango = ""
695
696         normal = (config['tab_colours'], config['tab_text_colours'])
697         selected = (config['selected_tab'], config['selected_tab_text'])
698         
699         tab_format = "<span %s> [ %d <span %s> %s</span> ] </span>"
700         
701         title_format = "%s - Uzbl Browser"
702
703         uzblkeys = self.pages.keys()
704         curpage = self.notebook.get_current_page()
705
706         for index, socket in enumerate(self.notebook):
707             if socket not in uzblkeys:
708                 #error("Theres a socket in the notebook that I have no uzbl "\
709                 #  "record of.")
710                 continue
711             uzbl = self.pages[socket]
712             
713             if index == curpage:
714                 colours = selected
715                 self.window.set_title(title_format % uzbl.title)
716
717             else:
718                 colours = normal
719             
720             pango += tab_format % (colours[0], index, colours[1], uzbl.title)
721
722         self.tablist.set_markup(pango)
723
724         return True
725
726
727     #def quit(self, window, event):
728     def quit(self, *args):
729         '''Cleanup the application and quit. Called by delete-event signal.'''
730
731         for fd in self._watchers.keys():
732             d = self._watchers[fd]
733             watchers = d['watchers']
734             for watcher in list(watchers):
735                 gobject.source_remove(watcher)
736         
737         for timer in self._timers.keys():
738             gobject.source_remove(self._timers[timer])
739
740         if os.path.exists(self.fifo_socket):
741             os.unlink(self.fifo_socket)
742             print "Unlinked %s" % self.fifo_socket
743         
744         if config['save_session']:
745             session_file = os.path.expandvars(config['session_file'])
746             if self.notebook.get_n_pages():
747                 if not os.path.isfile(session_file):
748                     dirname = os.path.dirname(session_file)
749                     if not os.path.isdir(dirname):
750                         rmkdir(dirname)
751
752                 h = open(session_file, 'w')
753                 h.write('current = %s\n' % self.notebook.get_current_page())
754                 h.close()
755                 for socket in list(self.notebook):
756                     if socket not in self.pages.keys(): continue
757                     uzbl = self.pages[socket]
758                     uzbl.send('sh "echo $6 >> %s"' % session_file)
759                     time.sleep(0.05)
760
761             else:
762                 # Notebook has no pages so delete session file if it exists.
763                 # Its better to not exist than be blank IMO. 
764                 if os.path.isfile(session_file):
765                     os.remove(session_file)
766
767         gtk.main_quit() 
768
769
770 if __name__ == "__main__":
771     
772     # Read from the uzbl config into the global config dictionary. 
773     readconfig(uzbl_config, config)
774      
775     uzbl = UzblTabbed()
776     
777     if os.path.isfile(os.path.expandvars(config['session_file'])):
778         h = open(os.path.expandvars(config['session_file']),'r')
779         urls = [s.strip() for s in h.readlines()]
780         h.close()
781         current = 0
782         for url in urls:
783             if url.startswith("current"):
784                 current = int(url.split()[-1])
785             else:
786                 uzbl.new_tab(url, False)
787     else:
788         uzbl.new_tab()
789
790     uzbl.run()
791
792