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