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