3 # Uzbl tabbing wrapper using a fifo socket interface
4 # Copyright (c) 2009, Tom Adams <tom@holizz.com>
5 # Copyright (c) 2009, Chris van Dijk <cn.vandijk@hotmail.com>
6 # Copyright (c) 2009, Mason Larobina <mason.larobina@gmail.com>
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.
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.
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/>.
23 # Tom Adams <tom@holizz.com>
24 # Wrote the original uzbl_tabbed.py as a proof of concept.
26 # Chris van Dijk (quigybo) <cn.vandijk@hotmail.com>
27 # Made signifigant headway on the old uzbl_tabbing.py script on the
28 # uzbl wiki <http://www.uzbl.org/wiki/uzbl_tabbed>
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.
35 # mxey <mxey@ghosthacking.net>
36 # uzbl_config path now honors XDG_CONFIG_HOME if it exists.
38 # Romain Bignon <romain@peerfuse.org>
39 # Fix for session restoration code.
43 # Because this version of uzbl_tabbed is able to inherit options from your main
44 # uzbl configuration file you may wish to configure uzbl tabbed from there.
45 # Here is a list of configuration options that can be customised and some
46 # example values for each:
48 # General tabbing options:
52 # gtk_tab_pos = (top|left|bottom|right)
53 # switch_to_new_tabs = 1
57 # new_tab_title = Loading
65 # icon_path = $HOME/.local/share/uzbl/uzbl.png
66 # session_file = $HOME/.local/share/uzbl/session
69 # status_background = #303030
70 # window_size = 800,800
72 # And the key bindings:
74 # bind_tab_from_clip = gY
75 # bind_tab_from_uri = go _
80 # bind_goto_first = g<
83 # And uzbl_tabbed.py takes care of the actual binding of the commands via each
84 # instances fifo socket.
87 # tab_colours = foreground = "#888" background = "#303030"
88 # tab_text_colours = foreground = "#bbb"
89 # selected_tab = foreground = "#fff"
90 # selected_tab_text = foreground = "green"
91 # tab_indicate_https = 1
92 # https_colours = foreground = "#888"
93 # https_text_colours = foreground = "#9c8e2d"
94 # selected_https = foreground = "#fff"
95 # selected_https_text = foreground = "gold"
97 # How these styling values are used are soley defined by the syling policy
98 # handler below (the function in the config section). So you can for example
99 # turn the tab text colour Firetruck-Red in the event "error" appears in the
100 # tab title or some other arbitrary event. You may wish to make a trusted
101 # hosts file and turn tab titles of tabs visiting trusted hosts purple.
105 # - new windows are not caught and opened in a new tab.
106 # - when uzbl_tabbed.py crashes it takes all the children with it.
107 # - when a new tab is opened when using gtk tabs the tab button itself
108 # grabs focus from its child for a few seconds.
109 # - when switch_to_new_tabs is not selected the notebook page is
110 # maintained but the new window grabs focus (try as I might to stop it).
114 # - add command line options to use a different session file, not use a
115 # session file and or open a uri on starup.
116 # - ellipsize individual tab titles when the tab-list becomes over-crowded
117 # - add "<" & ">" arrows to tablist to indicate that only a subset of the
118 # currently open tabs are being displayed on the tablist.
119 # - add the small tab-list display when both gtk tabs and text vim-like
120 # tablist are hidden (I.e. [ 1 2 3 4 5 ])
122 # - pass a uzbl socketid to uzbl_tabbed.py and have it assimilated into
123 # the collective. Resistance is futile!
124 # - on demand store the session to file (need binding & command for that)
145 sys.stderr.write("%s\n"%msg)
148 # ============================================================================
149 # ::: Default configuration section ::::::::::::::::::::::::::::::::::::::::::
150 # ============================================================================
152 # Location of your uzbl data directory.
153 if 'XDG_DATA_HOME' in os.environ.keys() and os.environ['XDG_DATA_HOME']:
154 data_dir = os.path.join(os.environ['XDG_DATA_HOME'], 'uzbl/')
156 data_dir = os.path.join(os.environ['HOME'], '.local/share/uzbl/')
157 if not os.path.exists(data_dir):
158 error("Warning: uzbl data_dir does not exist: %r" % data_dir)
160 # Location of your uzbl configuration file.
161 if 'XDG_CONFIG_HOME' in os.environ.keys() and os.environ['XDG_CONFIG_HOME']:
162 uzbl_config = os.path.join(os.environ['XDG_CONFIG_HOME'], 'uzbl/config')
164 uzbl_config = os.path.join(os.environ['HOME'],'.config/uzbl/config')
165 if not os.path.exists(uzbl_config):
166 error("Warning: Cannot locate your uzbl_config file %r" % uzbl_config)
168 # All of these settings can be inherited from your uzbl config file.
171 'show_tablist': True, # Show text uzbl like statusbar tab-list
172 'show_gtk_tabs': False, # Show gtk notebook tabs
173 'tablist_top': True, # Display tab-list at top of window
174 'gtk_tab_pos': 'top', # Gtk tab position (top|left|bottom|right)
175 'switch_to_new_tabs': True, # Upon opening a new tab switch to it
178 'tab_titles': True, # Display tab titles (else only tab-nums)
179 'new_tab_title': 'Loading', # New tab title
180 'max_title_len': 50, # Truncate title at n characters
181 'show_ellipsis': True, # Show ellipsis when truncating titles
184 'save_session': True, # Save session in file when quit
185 'fifo_dir': '/tmp', # Path to look for uzbl fifo
186 'socket_dir': '/tmp', # Path to look for uzbl socket
187 'icon_path': os.path.join(data_dir, 'uzbl.png'),
188 'session_file': os.path.join(data_dir, 'session'),
191 'status_background': "#303030", # Default background for all panels
192 'window_size': "800,800", # width,height in pixels
195 'bind_new_tab': 'gn', # Open new tab.
196 'bind_tab_from_clip': 'gY', # Open tab from clipboard.
197 'bind_tab_from_uri': 'go _', # Open new tab and goto entered uri.
198 'bind_close_tab': 'gC', # Close tab.
199 'bind_next_tab': 'gt', # Next tab.
200 'bind_prev_tab': 'gT', # Prev tab.
201 'bind_goto_tab': 'gi_', # Goto tab by tab-number (in title)
202 'bind_goto_first': 'g<', # Goto first tab
203 'bind_goto_last': 'g>', # Goto last tab
205 # Add custom tab style definitions to be used by the tab colour policy
206 # handler here. Because these are added to the config dictionary like
207 # any other uzbl_tabbed configuration option remember that they can
208 # be superseeded from your main uzbl config file.
209 'tab_colours': 'foreground = "#888" background = "#303030"',
210 'tab_text_colours': 'foreground = "#bbb"',
211 'selected_tab': 'foreground = "#fff"',
212 'selected_tab_text': 'foreground = "green"',
213 'tab_indicate_https': True,
214 'https_colours': 'foreground = "#888"',
215 'https_text_colours': 'foreground = "#9c8e2d"',
216 'selected_https': 'foreground = "#fff"',
217 'selected_https_text': 'foreground = "gold"',
219 } # End of config dict.
221 # This is the tab style policy handler. Every time the tablist is updated
222 # this function is called to determine how to colourise that specific tab
223 # according the simple/complex rules as defined here. You may even wish to
224 # move this function into another python script and import it using:
225 # from mycustomtabbingconfig import colour_selector
226 # Remember to rename, delete or comment out this function if you do that.
228 def colour_selector(tabindex, currentpage, uzbl):
229 '''Tablist styling policy handler. This function must return a tuple of
230 the form (tab style, text style).'''
232 # Just as an example:
233 # if 'error' in uzbl.title:
234 # if tabindex == currentpage:
235 # return ('foreground="#fff"', 'foreground="red"')
236 # return ('foreground="#888"', 'foreground="red"')
238 # Style tabs to indicate connected via https.
239 if config['tab_indicate_https'] and uzbl.uri.startswith("https://"):
240 if tabindex == currentpage:
241 return (config['selected_https'], config['selected_https_text'])
242 return (config['https_colours'], config['https_text_colours'])
244 # Style to indicate selected.
245 if tabindex == currentpage:
246 return (config['selected_tab'], config['selected_tab_text'])
249 return (config['tab_colours'], config['tab_text_colours'])
252 # ============================================================================
253 # ::: End of configuration section :::::::::::::::::::::::::::::::::::::::::::
254 # ============================================================================
257 def readconfig(uzbl_config, config):
258 '''Loads relevant config from the users uzbl config file into the global
259 config dictionary.'''
261 if not os.path.exists(uzbl_config):
262 error("Unable to load config %r" % uzbl_config)
265 # Define parsing regular expressions
266 isint = re.compile("^(\-|)[0-9]+$").match
267 findsets = re.compile("^set\s+([^\=]+)\s*\=\s*(.+)$",\
268 re.MULTILINE).findall
270 h = open(os.path.expandvars(uzbl_config), 'r')
274 for (key, value) in findsets(rawconfig):
275 key, value = key.strip(), value.strip()
276 if key not in config.keys(): continue
277 if isint(value): value = int(value)
280 # Ensure that config keys that relate to paths are expanded.
281 expand = ['fifo_dir', 'socket_dir', 'session_file', 'icon_path']
283 config[key] = os.path.expandvars(config[key])
287 '''Recursively make directories.
288 I.e. `mkdir -p /some/nonexistant/path/`'''
290 path, sep = os.path.realpath(path), os.path.sep
291 dirs = path.split(sep)
292 for i in range(2,len(dirs)+1):
293 dir = os.path.join(sep,sep.join(dirs[:i]))
294 if not os.path.exists(dir):
299 '''To infinity and beyond!'''
308 '''Replaces html markup in tab titles that screw around with pango.'''
310 for (split, glue) in [('&','&'), ('<', '<'), ('>', '>')]:
311 s = s.replace(split, glue)
316 '''Generates a random md5 for socket message-termination endmarkers.'''
318 return hashlib.md5(str(random.random()*time.time())).hexdigest()
322 '''A tabbed version of uzbl using gtk.Notebook'''
325 '''Uzbl instance meta-data/meta-action object.'''
327 def __init__(self, parent, tab, fifo_socket, socket_file, pid,\
332 self.fifo_socket = fifo_socket
333 self.socket_file = socket_file
335 self.title = config['new_tab_title']
343 # Switch to tab after loading
344 self._switch = switch
345 # fifo/socket files exists and socket connected.
346 self._connected = False
350 # Message termination endmarker.
351 self._marker = gen_endmarker()
353 # Gen probe commands string
355 probe = probes.append
356 probe('print uri %d @uri %s' % (self.pid, self._marker))
357 probe('print title %d @<document.title>@ %s' % (self.pid,\
359 self._probecmds = '\n'.join(probes)
361 # Enqueue keybinding config for child uzbl instance
362 self.parent.config_uzbl(self)
365 def flush(self, timer_call=False):
366 '''Flush messages from the socket-out and fifo-out queues.'''
373 error("Flush called on dead tab.")
376 if len(self._fifoout):
377 if os.path.exists(self.fifo_socket):
378 h = open(self.fifo_socket, 'w')
379 while len(self._fifoout):
380 msg = self._fifoout.pop(0)
384 if len(self._socketout):
385 if not self._socket and os.path.exists(self.socket_file):
386 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
387 sock.connect(self.socket_file)
391 while len(self._socketout):
392 msg = self._socketout.pop(0)
393 self._socket.send("%s\n"%msg)
395 if not self._connected and timer_call:
396 if not len(self._fifoout + self._socketout):
397 self._connected = True
399 if timer_call in self.timers.keys():
400 gobject.source_remove(self.timers[timer_call])
401 del self.timers[timer_call]
406 return len(self._fifoout + self._socketout)
410 '''Steal parent focus and switch the notebook to my own tab.'''
412 tabs = list(self.parent.notebook)
413 tabid = tabs.index(self.tab)
414 self.parent.goto_tab(tabid)
418 '''Probes the client for information about its self.'''
421 self.send(self._probecmds)
422 self._lastprobe = time.time()
425 def write(self, msg):
426 '''Child fifo write function.'''
428 self._fifoout.append(msg)
429 # Flush messages from the queue if able.
434 '''Child socket send function.'''
436 self._socketout.append(msg)
437 # Flush messages from queue if able.
442 '''Create tablist, window and notebook.'''
448 # Once a second is updated with the latest tabs' uris so that when the
449 # window is killed the session is saved.
451 # And index of current page in self._tabsuris
454 # Holds metadata on the uzbl childen open.
457 # Generates a unique id for uzbl socket filenames.
458 self.next_pid = counter().next
461 self.window = gtk.Window()
463 window_size = map(int, config['window_size'].split(','))
464 self.window.set_default_size(*window_size)
467 error("Invalid value for default_size in config file.")
469 self.window.set_title("Uzbl Browser")
470 self.window.set_border_width(0)
472 # Set main window icon
473 icon_path = config['icon_path']
474 if os.path.exists(icon_path):
475 self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path))
478 icon_path = '/usr/share/uzbl/examples/data/uzbl/uzbl.png'
479 if os.path.exists(icon_path):
480 self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path))
482 # Attach main window event handlers
483 self.window.connect("delete-event", self.quit)
486 if config['show_tablist']:
488 self.window.add(vbox)
489 ebox = gtk.EventBox()
490 self.tablist = gtk.Label()
491 self.tablist.set_use_markup(True)
492 self.tablist.set_justify(gtk.JUSTIFY_LEFT)
493 self.tablist.set_line_wrap(False)
494 self.tablist.set_selectable(False)
495 self.tablist.set_padding(2,2)
496 self.tablist.set_alignment(0,0)
497 self.tablist.set_ellipsize(pango.ELLIPSIZE_END)
498 self.tablist.set_text(" ")
500 ebox.add(self.tablist)
502 bgcolor = gtk.gdk.color_parse(config['status_background'])
503 ebox.modify_bg(gtk.STATE_NORMAL, bgcolor)
506 self.notebook = gtk.Notebook()
507 self.notebook.set_show_tabs(config['show_gtk_tabs'])
510 allposes = {'left': gtk.POS_LEFT, 'right':gtk.POS_RIGHT,
511 'top':gtk.POS_TOP, 'bottom':gtk.POS_BOTTOM}
512 if config['gtk_tab_pos'] in allposes.keys():
513 self.notebook.set_tab_pos(allposes[config['gtk_tab_pos']])
515 self.notebook.set_show_border(False)
516 self.notebook.set_scrollable(True)
517 self.notebook.set_border_width(0)
519 self.notebook.connect("page-removed", self.tab_closed)
520 self.notebook.connect("switch-page", self.tab_changed)
521 self.notebook.connect("page-added", self.tab_opened)
524 if config['show_tablist']:
525 if config['tablist_top']:
526 vbox.pack_start(ebox, False, False, 0)
527 vbox.pack_end(self.notebook, True, True, 0)
530 vbox.pack_start(self.notebook, True, True, 0)
531 vbox.pack_end(ebox, False, False, 0)
536 self.window.add(self.notebook)
539 self.wid = self.notebook.window.xid
541 # Create the uzbl_tabbed fifo
542 fifo_filename = 'uzbltabbed_%d' % os.getpid()
543 self.fifo_socket = os.path.join(config['fifo_dir'], fifo_filename)
544 self._create_fifo_socket(self.fifo_socket)
545 self._setup_fifo_watcher(self.fifo_socket)
548 def _create_fifo_socket(self, fifo_socket):
549 '''Create interprocess communication fifo socket.'''
551 if os.path.exists(fifo_socket):
552 if not os.access(fifo_socket, os.F_OK | os.R_OK | os.W_OK):
553 os.mkfifo(fifo_socket)
556 basedir = os.path.dirname(self.fifo_socket)
557 if not os.path.exists(basedir):
559 os.mkfifo(self.fifo_socket)
561 print "Listening on %s" % self.fifo_socket
564 def _setup_fifo_watcher(self, fifo_socket):
565 '''Open fifo socket fd and setup gobject IO_IN & IO_HUP watchers.
566 Also log the creation of a fd and store the the internal
567 self._watchers dictionary along with the filename of the fd.'''
569 if fifo_socket in self._fifos.keys():
570 fd, watchers = self._fifos[fifo_socket]
572 for watcherid in watchers.keys():
573 gobject.source_remove(watchers[watcherid])
574 del watchers[watcherid]
576 del self._fifos[fifo_socket]
578 # Re-open fifo and add listeners.
579 fd = os.open(fifo_socket, os.O_RDONLY | os.O_NONBLOCK)
581 self._fifos[fifo_socket] = (fd, watchers)
582 watcher = lambda key, id: watchers.__setitem__(key, id)
584 # Watch for incoming data.
585 gid = gobject.io_add_watch(fd, gobject.IO_IN, self.main_fifo_read)
586 watcher('main-fifo-read', gid)
588 # Watch for fifo hangups.
589 gid = gobject.io_add_watch(fd, gobject.IO_HUP, self.main_fifo_hangup)
590 watcher('main-fifo-hangup', gid)
594 '''UzblTabbed main function that calls the gtk loop.'''
596 # Update tablist timer
597 #timer = "update-tablist"
598 #timerid = gobject.timeout_add(500, self.update_tablist,timer)
599 #self._timers[timer] = timerid
601 # Probe clients every second for window titles and location
602 timer = "probe-clients"
603 timerid = gobject.timeout_add(1000, self.probe_clients, timer)
604 self._timers[timer] = timerid
609 def probe_clients(self, timer_call):
610 '''Probe all uzbl clients for up-to-date window titles and uri's.'''
614 tabskeys = self.tabs.keys()
615 notebooklist = list(self.notebook)
617 for tab in notebooklist:
618 if tab not in tabskeys: continue
619 uzbl = self.tabs[tab]
620 uriinventory.append(uzbl.uri)
623 sockd[uzbl._socket] = uzbl
625 self._tabsuris = uriinventory
626 self._curpage = self.notebook.get_current_page()
628 sockets = sockd.keys()
629 (reading, _, errors) = select.select(sockets, [], sockets, 0)
633 uzbl._buffer = sock.recv(1024).replace('\n',' ')
634 temp = uzbl._buffer.split(uzbl._marker)
635 self._buffer = temp.pop()
636 cmds = [s.strip().split() for s in temp if len(s.strip())]
640 self.parse_command(cmd)
643 error("parse_command: invalid command %s" % ' '.join(cmd))
649 def main_fifo_hangup(self, fd, cb_condition):
650 '''Handle main fifo socket hangups.'''
652 # Close fd, re-open fifo_socket and watch.
653 self._setup_fifo_watcher(self.fifo_socket)
655 # And to kill any gobject event handlers calling this function:
659 def main_fifo_read(self, fd, cb_condition):
660 '''Read from main fifo socket.'''
662 self._buffer = os.read(fd, 1024)
663 temp = self._buffer.split("\n")
664 self._buffer = temp.pop()
665 cmds = [s.strip().split() for s in temp if len(s.strip())]
670 self.parse_command(cmd)
673 error("parse_command: invalid command %s" % ' '.join(cmd))
679 def parse_command(self, cmd):
680 '''Parse instructions from uzbl child processes.'''
682 # Commands ( [] = optional, {} = required )
684 # open new tab and head to optional uri.
686 # close current tab or close via tab id.
688 # open next tab or n tabs down. Supports negative indexing.
690 # open prev tab or n tabs down. Supports negative indexing.
697 # title {pid} {document-title}
698 # updates tablist title.
699 # uri {pid} {document-location}
708 elif cmd[0] == "newfromclip":
709 uri = subprocess.Popen(['xclip','-selection','clipboard','-o'],\
710 stdout=subprocess.PIPE).communicate()[0]
714 elif cmd[0] == "close":
716 self.close_tab(int(cmd[1]))
721 elif cmd[0] == "next":
723 self.next_tab(int(cmd[1]))
728 elif cmd[0] == "prev":
730 self.prev_tab(int(cmd[1]))
735 elif cmd[0] == "goto":
736 self.goto_tab(int(cmd[1]))
738 elif cmd[0] == "first":
741 elif cmd[0] == "last":
744 elif cmd[0] in ["title", "uri"]:
746 uzbl = self.get_tab_by_pid(int(cmd[1]))
748 old = getattr(uzbl, cmd[0])
749 new = ' '.join(cmd[2:])
750 setattr(uzbl, cmd[0], new)
752 self.update_tablist()
754 error("parse_command: no uzbl with pid %r" % int(cmd[1]))
756 error("parse_command: unknown command %r" % ' '.join(cmd))
759 def get_tab_by_pid(self, pid):
760 '''Return uzbl instance by pid.'''
762 for tab in self.tabs.keys():
763 if self.tabs[tab].pid == pid:
764 return self.tabs[tab]
769 def new_tab(self, uri='', switch=None):
770 '''Add a new tab to the notebook and start a new instance of uzbl.
771 Use the switch option to negate config['switch_to_new_tabs'] option
772 when you need to load multiple tabs at a time (I.e. like when
773 restoring a session from a file).'''
775 pid = self.next_pid()
778 self.notebook.append_page(tab)
781 fifo_filename = 'uzbl_fifo_%s_%0.2d' % (self.wid, pid)
782 fifo_socket = os.path.join(config['fifo_dir'], fifo_filename)
783 socket_filename = 'uzbl_socket_%s_%0.2d' % (self.wid, pid)
784 socket_file = os.path.join(config['socket_dir'], socket_filename)
787 switch = config['switch_to_new_tabs']
790 # Create meta-instance and spawn child
792 uri = '--uri %s' % uri
794 uzbl = self.UzblInstance(self, tab, fifo_socket, socket_file, pid,\
796 self.tabs[tab] = uzbl
797 cmd = 'uzbl -s %s -n %s_%0.2d %s &' % (sid, self.wid, pid, uri)
798 subprocess.Popen([cmd], shell=True) # TODO: do i need close_fds=True ?
800 # Add gobject timer to make sure the config is pushed when fifo socket
802 timerid = gobject.timeout_add(100, uzbl.flush, "flush-initial-config")
803 uzbl.timers['flush-initial-config'] = timerid
805 self.update_tablist()
808 def config_uzbl(self, uzbl):
809 '''Send bind commands for tab new/close/next/prev to a uzbl
813 bind_format = 'bind %s = sh "echo \\\"%s\\\" > \\\"%s\\\""'
814 bind = lambda key, action: binds.append(bind_format % (key, action, \
817 # Keys are defined in the config section
818 # bind ( key , command back to fifo )
819 bind(config['bind_new_tab'], 'new')
820 bind(config['bind_tab_from_clip'], 'newfromclip')
821 bind(config['bind_tab_from_uri'], 'new %s')
822 bind(config['bind_close_tab'], 'close')
823 bind(config['bind_next_tab'], 'next')
824 bind(config['bind_prev_tab'], 'prev')
825 bind(config['bind_goto_tab'], 'goto %s')
826 bind(config['bind_goto_first'], 'goto 0')
827 bind(config['bind_goto_last'], 'goto -1')
829 # uzbl.send via socket or uzbl.write via fifo, I'll try send.
830 uzbl.send("\n".join(binds))
833 def goto_tab(self, index):
834 '''Goto tab n (supports negative indexing).'''
836 tabs = list(self.notebook)
837 if 0 <= index < len(tabs):
838 self.notebook.set_current_page(index)
839 self.update_tablist()
844 # Update index because index might have previously been a
846 index = tabs.index(tab)
847 self.notebook.set_current_page(index)
848 self.update_tablist()
854 def next_tab(self, step=1):
855 '''Switch to next tab or n tabs right.'''
858 error("next_tab: invalid step %r" % step)
861 ntabs = self.notebook.get_n_pages()
862 tabn = (self.notebook.get_current_page() + step) % ntabs
863 self.notebook.set_current_page(tabn)
864 self.update_tablist()
867 def prev_tab(self, step=1):
868 '''Switch to prev tab or n tabs left.'''
871 error("prev_tab: invalid step %r" % step)
874 ntabs = self.notebook.get_n_pages()
875 tabn = self.notebook.get_current_page() - step
876 while tabn < 0: tabn += ntabs
877 self.notebook.set_current_page(tabn)
878 self.update_tablist()
881 def close_tab(self, tabn=None):
882 '''Closes current tab. Supports negative indexing.'''
885 tabn = self.notebook.get_current_page()
889 tab = list(self.notebook)[tabn]
892 error("close_tab: invalid index %r" % tabn)
895 self.notebook.remove_page(tabn)
898 def tab_opened(self, notebook, tab, index):
899 '''Called upon tab creation. Called by page-added signal.'''
901 if config['switch_to_new_tabs']:
902 self.notebook.set_focus_child(tab)
905 oldindex = self.notebook.get_current_page()
906 oldtab = self.notebook.get_nth_page(oldindex)
907 self.notebook.set_focus_child(oldtab)
910 def tab_closed(self, notebook, tab, index):
911 '''Close the window if no tabs are left. Called by page-removed
914 if tab in self.tabs.keys():
915 uzbl = self.tabs[tab]
916 for timer in uzbl.timers.keys():
917 error("tab_closed: removing timer %r" % timer)
918 gobject.source_remove(uzbl.timers[timer])
929 if self.notebook.get_n_pages() == 0:
932 self.update_tablist()
937 def tab_changed(self, notebook, page, index):
938 '''Refresh tab list. Called by switch-page signal.'''
940 tab = self.notebook.get_nth_page(index)
941 self.notebook.set_focus_child(tab)
942 self.update_tablist(index)
946 def update_tablist(self, curpage=None):
947 '''Upate tablist status bar.'''
949 show_tablist = config['show_tablist']
950 show_gtk_tabs = config['show_gtk_tabs']
951 tab_titles = config['tab_titles']
952 show_ellipsis = config['show_ellipsis']
953 if not show_tablist and not show_gtk_tabs:
956 tabs = self.tabs.keys()
958 curpage = self.notebook.get_current_page()
960 title_format = "%s - Uzbl Browser"
961 max_title_len = config['max_title_len']
965 normal = (config['tab_colours'], config['tab_text_colours'])
966 selected = (config['selected_tab'], config['selected_tab_text'])
968 tab_format = "<span %s> [ %d <span %s> %s</span> ] </span>"
970 tab_format = "<span %s> [ <span %s>%d</span> ] </span>"
973 gtk_tab_format = "%d %s"
975 for index, tab in enumerate(self.notebook):
976 if tab not in tabs: continue
977 uzbl = self.tabs[tab]
980 self.window.set_title(title_format % uzbl.title)
982 tabtitle = uzbl.title[:max_title_len]
983 if show_ellipsis and len(tabtitle) != len(uzbl.title):
984 tabtitle = "%s\xe2\x80\xa6" % tabtitle[:-1] # Show Ellipsis
988 self.notebook.set_tab_label_text(tab,\
989 gtk_tab_format % (index, tabtitle))
991 self.notebook.set_tab_label_text(tab, str(index))
994 style = colour_selector(index, curpage, uzbl)
995 (tabc, textc) = style
998 pango += tab_format % (tabc, index, textc,\
1001 pango += tab_format % (tabc, textc, index)
1004 self.tablist.set_markup(pango)
1009 def quit(self, *args):
1010 '''Cleanup the application and quit. Called by delete-event signal.'''
1012 for fifo_socket in self._fifos.keys():
1013 fd, watchers = self._fifos[fifo_socket]
1015 for watcherid in watchers.keys():
1016 gobject.source_remove(watchers[watcherid])
1017 del watchers[watcherid]
1019 del self._fifos[fifo_socket]
1021 for timerid in self._timers.keys():
1022 gobject.source_remove(self._timers[timerid])
1023 del self._timers[timerid]
1025 if os.path.exists(self.fifo_socket):
1026 os.unlink(self.fifo_socket)
1027 print "Unlinked %s" % self.fifo_socket
1029 if config['save_session']:
1030 session_file = config['session_file']
1031 if len(self._tabsuris):
1032 if not os.path.isfile(session_file):
1033 dirname = os.path.dirname(session_file)
1034 if not os.path.isdir(dirname):
1035 # Recursive mkdir not rmdir.
1038 sessionstr = '\n'.join(self._tabsuris)
1039 h = open(session_file, 'w')
1040 h.write('current = %s\n%s' % (self._curpage, sessionstr))
1044 # Notebook has no pages so delete session file if it exists.
1045 if os.path.isfile(session_file):
1046 os.remove(session_file)
1051 if __name__ == "__main__":
1053 # Read from the uzbl config into the global config dictionary.
1054 readconfig(uzbl_config, config)
1058 if os.path.isfile(os.path.expandvars(config['session_file'])):
1059 h = open(os.path.expandvars(config['session_file']),'r')
1060 lines = [line.strip() for line in h.readlines()]
1065 if line.startswith("current"):
1066 current = int(line.split()[-1])
1069 urls.append(line.strip())
1071 for (index, url) in enumerate(urls):
1072 if current == index:
1073 uzbl.new_tab(line, True)
1076 uzbl.new_tab(line, False)