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