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