diff options
Diffstat (limited to '.config/uzbl/scripts/uzbl_tabbed.py')
-rwxr-xr-x | .config/uzbl/scripts/uzbl_tabbed.py | 1474 |
1 files changed, 0 insertions, 1474 deletions
diff --git a/.config/uzbl/scripts/uzbl_tabbed.py b/.config/uzbl/scripts/uzbl_tabbed.py deleted file mode 100755 index cd5ef4f..0000000 --- a/.config/uzbl/scripts/uzbl_tabbed.py +++ /dev/null @@ -1,1474 +0,0 @@ -#!/usr/bin/env python - -# Uzbl tabbing wrapper using a fifo socket interface -# Copyright (c) 2009, Tom Adams <tom@holizz.com> -# Copyright (c) 2009, Chris van Dijk <cn.vandijk@hotmail.com> -# Copyright (c) 2009, Mason Larobina <mason.larobina@gmail.com> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - - -# Author(s): -# Tom Adams <tom@holizz.com> -# Wrote the original uzbl_tabbed.py as a proof of concept. -# -# Chris van Dijk (quigybo) <cn.vandijk@hotmail.com> -# Made signifigant headway on the old uzbl_tabbing.py script on the -# uzbl wiki <http://www.uzbl.org/wiki/uzbl_tabbed> -# -# Mason Larobina <mason.larobina@gmail.com> -# Rewrite of the uzbl_tabbing.py script to use a fifo socket interface -# and inherit configuration options from the user's uzbl config. -# -# Contributor(s): -# mxey <mxey@ghosthacking.net> -# uzbl_config path now honors XDG_CONFIG_HOME if it exists. -# -# Romain Bignon <romain@peerfuse.org> -# Fix for session restoration code. -# -# Jake Probst <jake.probst@gmail.com> -# Wrote a patch that overflows tabs in the tablist on to new lines when -# running of room. -# -# Devon Jones <devon.jones@gmail.com> -# Fifo command bring_to_front which brings the gtk window to focus. - - -# Dependencies: -# pygtk - python bindings for gtk. -# pango - python bindings needed for text rendering & layout in gtk widgets. -# pygobject - GLib's GObject bindings for python. -# -# Optional dependencies: -# simplejson - save uzbl_tabbed.py sessions & presets in json. -# -# Note: I haven't included version numbers with this dependency list because -# I've only ever tested uzbl_tabbed.py on the latest stable versions of these -# packages in Gentoo's portage. Package names may vary on different systems. - - -# Configuration: -# Because this version of uzbl_tabbed is able to inherit options from your main -# uzbl configuration file you may wish to configure uzbl tabbed from there. -# Here is a list of configuration options that can be customised and some -# example values for each: -# -# General tabbing options: -# show_tablist = 1 -# show_gtk_tabs = 0 -# tablist_top = 1 -# gtk_tab_pos = (top|left|bottom|right) -# gtk_refresh = 1000 -# switch_to_new_tabs = 1 -# capture_new_windows = 1 -# multiline_tabs = 1 -# -# Tab title options: -# tab_titles = 1 -# new_tab_title = Loading -# max_title_len = 50 -# show_ellipsis = 1 -# -# Session options: -# save_session = 1 -# json_session = 0 -# session_file = $HOME/.local/share/uzbl/session -# -# Inherited uzbl options: -# fifo_dir = /tmp -# socket_dir = /tmp -# icon_path = $HOME/.local/share/uzbl/uzbl.png -# status_background = #303030 -# -# Misc options: -# window_size = 800,800 -# verbose = 0 -# -# And the key bindings: -# bind_new_tab = gn -# bind_tab_from_clip = gY -# bind_tab_from_uri = go _ -# bind_close_tab = gC -# bind_next_tab = gt -# bind_prev_tab = gT -# bind_goto_tab = gi_ -# bind_goto_first = g< -# bind_goto_last = g> -# bind_clean_slate = gQ -# bind_exit = gZ -# -# Session preset key bindings: -# bind_save_preset = gsave _ -# bind_load_preset = gload _ -# bind_del_preset = gdel _ -# bind_list_presets = glist -# -# And uzbl_tabbed.py takes care of the actual binding of the commands via each -# instances fifo socket. -# -# Custom tab styling: -# tab_colours = foreground = "#888" background = "#303030" -# tab_text_colours = foreground = "#bbb" -# selected_tab = foreground = "#fff" -# selected_tab_text = foreground = "green" -# tab_indicate_https = 1 -# https_colours = foreground = "#888" -# https_text_colours = foreground = "#9c8e2d" -# selected_https = foreground = "#fff" -# selected_https_text = foreground = "gold" -# -# How these styling values are used are soley defined by the syling policy -# handler below (the function in the config section). So you can for example -# turn the tab text colour Firetruck-Red in the event "error" appears in the -# tab title or some other arbitrary event. You may wish to make a trusted -# hosts file and turn tab titles of tabs visiting trusted hosts purple. - - -# Issues: -# - new windows are not caught and opened in a new tab. -# - when uzbl_tabbed.py crashes it takes all the children with it. -# - when a new tab is opened when using gtk tabs the tab button itself -# grabs focus from its child for a few seconds. -# - when switch_to_new_tabs is not selected the notebook page is -# maintained but the new window grabs focus (try as I might to stop it). - - -# Todo: -# - add command line options to use a different session file, not use a -# session file and or open a uri on starup. -# - ellipsize individual tab titles when the tab-list becomes over-crowded -# - add "<" & ">" arrows to tablist to indicate that only a subset of the -# currently open tabs are being displayed on the tablist. -# - add the small tab-list display when both gtk tabs and text vim-like -# tablist are hidden (I.e. [ 1 2 3 4 5 ]) -# - check spelling. -# - pass a uzbl socketid to uzbl_tabbed.py and have it assimilated into -# the collective. Resistance is futile! - - -import pygtk -import gtk -import subprocess -import os -import re -import time -import getopt -import pango -import select -import sys -import gobject -import socket -import random -import hashlib -import atexit -import types - -from gobject import io_add_watch, source_remove, timeout_add, IO_IN, IO_HUP -from signal import signal, SIGTERM, SIGINT -from optparse import OptionParser, OptionGroup - - -pygtk.require('2.0') - -_SCRIPTNAME = os.path.basename(sys.argv[0]) -def error(msg): - sys.stderr.write("%s: error: %s\n" % (_SCRIPTNAME, msg)) - -# ============================================================================ -# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - -def xdghome(key, default): - '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise - use $HOME and the default path.''' - - xdgkey = "XDG_%s_HOME" % key - if xdgkey in os.environ.keys() and os.environ[xdgkey]: - return os.environ[xdgkey] - - return os.path.join(os.environ['HOME'], default) - -# Setup xdg paths. -DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') -CONFIG_DIR = os.path.join(xdghome('CONFIG', '.config/'), 'uzbl/') - -# Ensure uzbl xdg paths exist -for path in [DATA_DIR, CONFIG_DIR]: - if not os.path.exists(path): - os.makedirs(path) - -# Path to uzbl config -UZBL_CONFIG = os.path.join(CONFIG_DIR, 'config') -if not os.path.exists(UZBL_CONFIG): - error("cannot find uzbl config file at %r" % UZBL_CONFIG) - sys.exit(1) - -# All of these settings can be inherited from your uzbl config file. -config = { - # Tab options - 'show_tablist': True, # Show text uzbl like statusbar tab-list - 'show_gtk_tabs': False, # Show gtk notebook tabs - 'tablist_top': True, # Display tab-list at top of window - 'gtk_tab_pos': 'top', # Gtk tab position (top|left|bottom|right) - 'gtk_refresh': 1000, # Tablist refresh millisecond interval - 'switch_to_new_tabs': True, # Upon opening a new tab switch to it - 'capture_new_windows': True, # Use uzbl_tabbed to catch new windows - 'multiline_tabs': True, # Tabs overflow onto new tablist lines. - - # Tab title options - 'tab_titles': True, # Display tab titles (else only tab-nums) - 'new_tab_title': 'Loading', # New tab title - 'max_title_len': 50, # Truncate title at n characters - 'show_ellipsis': True, # Show ellipsis when truncating titles - - # Session options - 'save_session': True, # Save session in file when quit - 'json_session': False, # Use json to save session. - 'saved_sessions_dir': os.path.join(DATA_DIR, 'sessions/'), - 'session_file': os.path.join(DATA_DIR, 'session'), - - # Inherited uzbl options - 'fifo_dir': '/tmp', # Path to look for uzbl fifo. - 'socket_dir': '/tmp', # Path to look for uzbl socket. - 'icon_path': os.path.join(DATA_DIR, 'uzbl.png'), - 'status_background': "#303030", # Default background for all panels. - - # Misc options - 'window_size': "800,800", # width,height in pixels. - 'verbose': False, # Print verbose output. - - # Key bindings - 'bind_new_tab': 'gn', # Open new tab. - 'bind_tab_from_clip': 'gY', # Open tab from clipboard. - 'bind_tab_from_uri': 'go _', # Open new tab and goto entered uri. - 'bind_close_tab': 'gC', # Close tab. - 'bind_next_tab': 'gt', # Next tab. - 'bind_prev_tab': 'gT', # Prev tab. - 'bind_goto_tab': 'gi_', # Goto tab by tab-number (in title). - 'bind_goto_first': 'g<', # Goto first tab. - 'bind_goto_last': 'g>', # Goto last tab. - 'bind_clean_slate': 'gQ', # Close all tabs and open new tab. - 'bind_exit': 'gZ', # Exit nicely. - - # Session preset key bindings - 'bind_save_preset': 'gsave _', # Save session to file %s. - 'bind_load_preset': 'gload _', # Load preset session from file %s. - 'bind_del_preset': 'gdel _', # Delete preset session %s. - 'bind_list_presets': 'glist', # List all session presets. - - # Add custom tab style definitions to be used by the tab colour policy - # handler here. Because these are added to the config dictionary like - # any other uzbl_tabbed configuration option remember that they can - # be superseeded from your main uzbl config file. - 'tab_colours': 'foreground = "#888" background = "#303030"', - 'tab_text_colours': 'foreground = "#bbb"', - 'selected_tab': 'foreground = "#fff"', - 'selected_tab_text': 'foreground = "green"', - 'tab_indicate_https': True, - 'https_colours': 'foreground = "#888"', - 'https_text_colours': 'foreground = "#9c8e2d"', - 'selected_https': 'foreground = "#fff"', - 'selected_https_text': 'foreground = "gold"', - -} # End of config dict. - -# This is the tab style policy handler. Every time the tablist is updated -# this function is called to determine how to colourise that specific tab -# according the simple/complex rules as defined here. You may even wish to -# move this function into another python script and import it using: -# from mycustomtabbingconfig import colour_selector -# Remember to rename, delete or comment out this function if you do that. - -def colour_selector(tabindex, currentpage, uzbl): - '''Tablist styling policy handler. This function must return a tuple of - the form (tab style, text style).''' - - # Just as an example: - # if 'error' in uzbl.title: - # if tabindex == currentpage: - # return ('foreground="#fff"', 'foreground="red"') - # return ('foreground="#888"', 'foreground="red"') - - # Style tabs to indicate connected via https. - if config['tab_indicate_https'] and uzbl.uri.startswith("https://"): - if tabindex == currentpage: - return (config['selected_https'], config['selected_https_text']) - return (config['https_colours'], config['https_text_colours']) - - # Style to indicate selected. - if tabindex == currentpage: - return (config['selected_tab'], config['selected_tab_text']) - - # Default tab style. - return (config['tab_colours'], config['tab_text_colours']) - -# ============================================================================ -# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - -def echo(msg): - if config['verbose']: - sys.stderr.write("%s: %s\n" % (_SCRIPTNAME, msg)) - - -def readconfig(uzbl_config, config): - '''Loads relevant config from the users uzbl config file into the global - config dictionary.''' - - if not os.path.exists(uzbl_config): - error("Unable to load config %r" % uzbl_config) - return None - - # Define parsing regular expressions - isint = re.compile("^(\-|)[0-9]+$").match - findsets = re.compile("^set\s+([^\=]+)\s*\=\s*(.+)$",\ - re.MULTILINE).findall - - h = open(os.path.expandvars(uzbl_config), 'r') - rawconfig = h.read() - h.close() - - configkeys, strip = config.keys(), str.strip - for (key, value) in findsets(rawconfig): - key, value = strip(key), strip(value) - if key not in configkeys: continue - if isint(value): value = int(value) - config[key] = value - - # Ensure that config keys that relate to paths are expanded. - pathkeys = ['fifo_dir', 'socket_dir', 'session_file', 'icon_path', - 'saved_sessions_dir'] - for key in pathkeys: - config[key] = os.path.expandvars(config[key]) - - -def counter(): - '''To infinity and beyond!''' - - i = 0 - while True: - i += 1 - yield i - - -def escape(s): - '''Replaces html markup in tab titles that screw around with pango.''' - - for (split, glue) in [('&','&'), ('<', '<'), ('>', '>')]: - s = s.replace(split, glue) - return s - - -def gen_endmarker(): - '''Generates a random md5 for socket message-termination endmarkers.''' - - return hashlib.md5(str(random.random()*time.time())).hexdigest() - - -class UzblTabbed: - '''A tabbed version of uzbl using gtk.Notebook''' - - class UzblInstance: - '''Uzbl instance meta-data/meta-action object.''' - - def __init__(self, parent, tab, fifo_socket, socket_file, pid,\ - uri, title, switch): - - self.parent = parent - self.tab = tab - self.fifo_socket = fifo_socket - self.socket_file = socket_file - self.pid = pid - self.title = title - self.uri = uri - self.timers = {} - self._lastprobe = 0 - self._fifoout = [] - self._socketout = [] - self._socket = None - self._buffer = "" - # Switch to tab after loading - self._switch = switch - # fifo/socket files exists and socket connected. - self._connected = False - # The kill switch - self._kill = False - - # Message termination endmarker. - self._marker = gen_endmarker() - - # Gen probe commands string - probes = [] - probe = probes.append - probe('print uri %d @uri %s' % (self.pid, self._marker)) - probe('print title %d @<document.title>@ %s' % (self.pid,\ - self._marker)) - self._probecmds = '\n'.join(probes) - - # Enqueue keybinding config for child uzbl instance - self.parent.config_uzbl(self) - - - def flush(self, timer_call=False): - '''Flush messages from the socket-out and fifo-out queues.''' - - if self._kill: - if self._socket: - self._socket.close() - self._socket = None - - error("Flush called on dead tab.") - return False - - if len(self._fifoout): - if os.path.exists(self.fifo_socket): - h = open(self.fifo_socket, 'w') - while len(self._fifoout): - msg = self._fifoout.pop(0) - h.write("%s\n"%msg) - h.close() - - if len(self._socketout): - if not self._socket and os.path.exists(self.socket_file): - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect(self.socket_file) - self._socket = sock - - if self._socket: - while len(self._socketout): - msg = self._socketout.pop(0) - self._socket.send("%s\n"%msg) - - if not self._connected and timer_call: - if not len(self._fifoout + self._socketout): - self._connected = True - - if timer_call in self.timers.keys(): - source_remove(self.timers[timer_call]) - del self.timers[timer_call] - - if self._switch: - self.grabfocus() - - return len(self._fifoout + self._socketout) - - - def grabfocus(self): - '''Steal parent focus and switch the notebook to my own tab.''' - - tabs = list(self.parent.notebook) - tabid = tabs.index(self.tab) - self.parent.goto_tab(tabid) - - - def probe(self): - '''Probes the client for information about its self.''' - - if self._connected: - self.send(self._probecmds) - self._lastprobe = time.time() - - - def write(self, msg): - '''Child fifo write function.''' - - self._fifoout.append(msg) - # Flush messages from the queue if able. - return self.flush() - - - def send(self, msg): - '''Child socket send function.''' - - self._socketout.append(msg) - # Flush messages from queue if able. - return self.flush() - - - def __init__(self): - '''Create tablist, window and notebook.''' - - # Store information about the applications fifo_socket. - self._fifo = None - - self._timers = {} - self._buffer = "" - self._killed = False - - # A list of the recently closed tabs - self._closed = [] - - # Holds metadata on the uzbl childen open. - self.tabs = {} - - # Generates a unique id for uzbl socket filenames. - self.next_pid = counter().next - - # Create main window - self.window = gtk.Window() - try: - window_size = map(int, config['window_size'].split(',')) - self.window.set_default_size(*window_size) - - except: - error("Invalid value for default_size in config file.") - - self.window.set_title("Uzbl Browser") - self.window.set_border_width(0) - - # Set main window icon - icon_path = config['icon_path'] - if os.path.exists(icon_path): - self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path)) - - else: - icon_path = '/usr/share/uzbl/examples/data/uzbl/uzbl.png' - if os.path.exists(icon_path): - self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path)) - - # Attach main window event handlers - self.window.connect("delete-event", self.quitrequest) - - # Create tab list - if config['show_tablist']: - vbox = gtk.VBox() - self.window.add(vbox) - ebox = gtk.EventBox() - self.tablist = gtk.Label() - - self.tablist.set_use_markup(True) - self.tablist.set_justify(gtk.JUSTIFY_LEFT) - self.tablist.set_line_wrap(False) - self.tablist.set_selectable(False) - self.tablist.set_padding(2,2) - self.tablist.set_alignment(0,0) - self.tablist.set_ellipsize(pango.ELLIPSIZE_END) - self.tablist.set_text(" ") - self.tablist.show() - ebox.add(self.tablist) - ebox.show() - bgcolor = gtk.gdk.color_parse(config['status_background']) - ebox.modify_bg(gtk.STATE_NORMAL, bgcolor) - - # Create notebook - self.notebook = gtk.Notebook() - self.notebook.set_show_tabs(config['show_gtk_tabs']) - - # Set tab position - allposes = {'left': gtk.POS_LEFT, 'right':gtk.POS_RIGHT, - 'top':gtk.POS_TOP, 'bottom':gtk.POS_BOTTOM} - if config['gtk_tab_pos'] in allposes.keys(): - self.notebook.set_tab_pos(allposes[config['gtk_tab_pos']]) - - self.notebook.set_show_border(False) - self.notebook.set_scrollable(True) - self.notebook.set_border_width(0) - - self.notebook.connect("page-removed", self.tab_closed) - self.notebook.connect("switch-page", self.tab_changed) - self.notebook.connect("page-added", self.tab_opened) - - self.notebook.show() - if config['show_tablist']: - if config['tablist_top']: - vbox.pack_start(ebox, False, False, 0) - vbox.pack_end(self.notebook, True, True, 0) - - else: - vbox.pack_start(self.notebook, True, True, 0) - vbox.pack_end(ebox, False, False, 0) - - vbox.show() - - else: - self.window.add(self.notebook) - - self.window.show() - self.wid = self.notebook.window.xid - # Generate the fifo socket filename. - fifo_filename = 'uzbltabbed_%d' % os.getpid() - self.fifo_socket = os.path.join(config['fifo_dir'], fifo_filename) - - # Now initialise the fifo socket at self.fifo_socket - self.init_fifo_socket() - - # If we are using sessions then load the last one if it exists. - if config['save_session']: - self.load_session() - - - def run(self): - '''UzblTabbed main function that calls the gtk loop.''' - - if not len(self.tabs): - self.new_tab() - - gtk_refresh = int(config['gtk_refresh']) - if gtk_refresh < 100: - gtk_refresh = 100 - - # Update tablist timer - timerid = timeout_add(gtk_refresh, self.update_tablist) - self._timers["update-tablist"] = timerid - - # Probe clients every second for window titles and location - timerid = timeout_add(gtk_refresh, self.probe_clients) - self._timers["probe-clients"] = timerid - - # Make SIGTERM act orderly. - signal(SIGTERM, lambda signum, stack_frame: self.terminate(SIGTERM)) - - # Catch keyboard interrupts - signal(SIGINT, lambda signum, stack_frame: self.terminate(SIGINT)) - - try: - gtk.main() - - except: - error("encounted error %r" % sys.exc_info()[1]) - - # Unlink fifo socket - self.unlink_fifo_socket() - - # Attempt to close all uzbl instances nicely. - self.quitrequest() - - # Allow time for all the uzbl instances to quit. - time.sleep(1) - - raise - - - def terminate(self, termsig=None): - '''Handle termination signals and exit safely and cleanly.''' - - # Not required but at least it lets the user know what killed his - # browsing session. - if termsig == SIGTERM: - error("caught SIGTERM signal") - - elif termsig == SIGINT: - error("caught keyboard interrupt") - - else: - error("caught unknown signal") - - error("commencing infanticide!") - - # Sends the exit signal to all uzbl instances. - self.quitrequest() - - - def init_fifo_socket(self): - '''Create interprocess communication fifo socket.''' - - if os.path.exists(self.fifo_socket): - if not os.access(self.fifo_socket, os.F_OK | os.R_OK | os.W_OK): - os.mkfifo(self.fifo_socket) - - else: - basedir = os.path.dirname(self.fifo_socket) - if not os.path.exists(basedir): - os.makedirs(basedir) - - os.mkfifo(self.fifo_socket) - - # Add event handlers for IO_IN & IO_HUP events. - self.setup_fifo_watchers() - - echo("listening at %r" % self.fifo_socket) - - # Add atexit register to destroy the socket on program termination. - atexit.register(self.unlink_fifo_socket) - - - def unlink_fifo_socket(self): - '''Unlink the fifo socket. Note: This function is called automatically - on exit by an atexit register.''' - - # Make sure the fifo_socket fd is closed. - self.close_fifo() - - # And unlink if the real fifo_socket exists. - if os.path.exists(self.fifo_socket): - os.unlink(self.fifo_socket) - echo("unlinked %r" % self.fifo_socket) - - - def close_fifo(self): - '''Remove all event handlers watching the fifo and close the fd.''' - - # Already closed - if self._fifo is None: return - - (fd, watchers) = self._fifo - os.close(fd) - - # Stop all gobject io watchers watching the fifo. - for gid in watchers: - source_remove(gid) - - self._fifo = None - - - def setup_fifo_watchers(self): - '''Open fifo socket fd and setup gobject IO_IN & IO_HUP event - handlers.''' - - # Close currently open fifo_socket fd and kill all watchers - self.close_fifo() - - fd = os.open(self.fifo_socket, os.O_RDONLY | os.O_NONBLOCK) - - # Add gobject io event handlers to the fifo socket. - watchers = [io_add_watch(fd, IO_IN, self.main_fifo_read),\ - io_add_watch(fd, IO_HUP, self.main_fifo_hangup)] - - self._fifo = (fd, watchers) - - - def main_fifo_hangup(self, fd, cb_condition): - '''Handle main fifo socket hangups.''' - - # Close old fd, open new fifo socket and add io event handlers. - self.setup_fifo_watchers() - - # Kill the gobject event handler calling this handler function. - return False - - - def main_fifo_read(self, fd, cb_condition): - '''Read from main fifo socket.''' - - self._buffer = os.read(fd, 1024) - temp = self._buffer.split("\n") - self._buffer = temp.pop() - cmds = [s.strip().split() for s in temp if len(s.strip())] - - for cmd in cmds: - try: - #print cmd - self.parse_command(cmd) - - except: - error("parse_command: invalid command %s" % ' '.join(cmd)) - raise - - return True - - - def probe_clients(self): - '''Probe all uzbl clients for up-to-date window titles and uri's.''' - - save_session = config['save_session'] - - sockd = {} - tabskeys = self.tabs.keys() - notebooklist = list(self.notebook) - - for tab in notebooklist: - if tab not in tabskeys: continue - uzbl = self.tabs[tab] - uzbl.probe() - if uzbl._socket: - sockd[uzbl._socket] = uzbl - - sockets = sockd.keys() - (reading, _, errors) = select.select(sockets, [], sockets, 0) - - for sock in reading: - uzbl = sockd[sock] - uzbl._buffer = sock.recv(1024).replace('\n',' ') - temp = uzbl._buffer.split(uzbl._marker) - self._buffer = temp.pop() - cmds = [s.strip().split() for s in temp if len(s.strip())] - for cmd in cmds: - try: - #print cmd - self.parse_command(cmd) - - except: - error("parse_command: invalid command %s" % ' '.join(cmd)) - raise - - return True - - - def parse_command(self, cmd): - '''Parse instructions from uzbl child processes.''' - - # Commands ( [] = optional, {} = required ) - # new [uri] - # open new tab and head to optional uri. - # close [tab-num] - # close current tab or close via tab id. - # next [n-tabs] - # open next tab or n tabs down. Supports negative indexing. - # prev [n-tabs] - # open prev tab or n tabs down. Supports negative indexing. - # goto {tab-n} - # goto tab n. - # first - # goto first tab. - # last - # goto last tab. - # title {pid} {document-title} - # updates tablist title. - # uri {pid} {document-location} - # updates tablist uri - # bring_to_front - # brings the gtk window to focus. - # exit - # exits uzbl_tabbed.py - - if cmd[0] == "new": - if len(cmd) == 2: - self.new_tab(cmd[1]) - - else: - self.new_tab() - - elif cmd[0] == "newfromclip": - uri = subprocess.Popen(['xclip','-selection','clipboard','-o'],\ - stdout=subprocess.PIPE).communicate()[0] - if uri: - self.new_tab(uri) - - elif cmd[0] == "close": - if len(cmd) == 2: - self.close_tab(int(cmd[1])) - - else: - self.close_tab() - - elif cmd[0] == "next": - if len(cmd) == 2: - self.next_tab(int(cmd[1])) - - else: - self.next_tab() - - elif cmd[0] == "prev": - if len(cmd) == 2: - self.prev_tab(int(cmd[1])) - - else: - self.prev_tab() - - elif cmd[0] == "goto": - self.goto_tab(int(cmd[1])) - - elif cmd[0] == "first": - self.goto_tab(0) - - elif cmd[0] == "last": - self.goto_tab(-1) - - elif cmd[0] in ["title", "uri"]: - if len(cmd) > 2: - uzbl = self.get_tab_by_pid(int(cmd[1])) - if uzbl: - old = getattr(uzbl, cmd[0]) - new = ' '.join(cmd[2:]) - setattr(uzbl, cmd[0], new) - if old != new: - self.update_tablist() - - else: - error("parse_command: no uzbl with pid %r" % int(cmd[1])) - - elif cmd[0] == "preset": - if len(cmd) < 3: - error("parse_command: invalid preset command") - - elif cmd[1] == "save": - path = os.path.join(config['saved_sessions_dir'], cmd[2]) - self.save_session(path) - - elif cmd[1] == "load": - path = os.path.join(config['saved_sessions_dir'], cmd[2]) - self.load_session(path) - - elif cmd[1] == "del": - path = os.path.join(config['saved_sessions_dir'], cmd[2]) - if os.path.isfile(path): - os.remove(path) - - else: - error("parse_command: preset %r does not exist." % path) - - elif cmd[1] == "list": - uzbl = self.get_tab_by_pid(int(cmd[2])) - if uzbl: - if not os.path.isdir(config['saved_sessions_dir']): - js = "js alert('No saved presets.');" - uzbl.send(js) - - else: - listdir = os.listdir(config['saved_sessions_dir']) - listdir = "\\n".join(listdir) - js = "js alert('Session presets:\\n\\n%s');" % listdir - uzbl.send(js) - - else: - error("parse_command: unknown tab pid.") - - else: - error("parse_command: unknown parse command %r"\ - % ' '.join(cmd)) - - elif cmd[0] == "bring_to_front": - self.window.present() - - elif cmd[0] == "clean": - self.clean_slate() - - elif cmd[0] == "exit": - self.quitrequest() - - else: - error("parse_command: unknown command %r" % ' '.join(cmd)) - - - def get_tab_by_pid(self, pid): - '''Return uzbl instance by pid.''' - - for (tab, uzbl) in self.tabs.items(): - if uzbl.pid == pid: - return uzbl - - return False - - - def new_tab(self, uri='', title='', switch=None): - '''Add a new tab to the notebook and start a new instance of uzbl. - Use the switch option to negate config['switch_to_new_tabs'] option - when you need to load multiple tabs at a time (I.e. like when - restoring a session from a file).''' - - pid = self.next_pid() - tab = gtk.Socket() - tab.show() - self.notebook.append_page(tab) - sid = tab.get_id() - uri = uri.strip() - - fifo_filename = 'uzbl_fifo_%s_%0.2d' % (self.wid, pid) - fifo_socket = os.path.join(config['fifo_dir'], fifo_filename) - socket_filename = 'uzbl_socket_%s_%0.2d' % (self.wid, pid) - socket_file = os.path.join(config['socket_dir'], socket_filename) - - if switch is None: - switch = config['switch_to_new_tabs'] - - if not title: - title = config['new_tab_title'] - - uzbl = self.UzblInstance(self, tab, fifo_socket, socket_file, pid,\ - uri, title, switch) - - if len(uri): - uri = "--uri %r" % uri - - self.tabs[tab] = uzbl - cmd = 'uzbl -s %s -n %s_%0.2d %s &' % (sid, self.wid, pid, uri) - subprocess.Popen([cmd], shell=True) # TODO: do i need close_fds=True ? - - # Add gobject timer to make sure the config is pushed when fifo socket - # has been created. - timerid = timeout_add(100, uzbl.flush, "flush-initial-config") - uzbl.timers['flush-initial-config'] = timerid - - self.update_tablist() - - - def clean_slate(self): - '''Close all open tabs and open a fresh brand new one.''' - - self.new_tab() - tabs = self.tabs.keys() - for tab in list(self.notebook)[:-1]: - if tab not in tabs: continue - uzbl = self.tabs[tab] - uzbl.send("exit") - - - def config_uzbl(self, uzbl): - '''Send bind commands for tab new/close/next/prev to a uzbl - instance.''' - - binds = [] - bind_format = r'bind %s = sh "echo \"%s\" > \"%s\""' - bind = lambda key, action: binds.append(bind_format % (key, action,\ - self.fifo_socket)) - - sets = [] - set_format = r'set %s = sh \"echo \\"%s\\" > \\"%s\\""' - set = lambda key, action: binds.append(set_format % (key, action,\ - self.fifo_socket)) - - # Bind definitions here - # bind(key, command back to fifo) - bind(config['bind_new_tab'], 'new') - bind(config['bind_tab_from_clip'], 'newfromclip') - bind(config['bind_tab_from_uri'], 'new %s') - bind(config['bind_close_tab'], 'close') - bind(config['bind_next_tab'], 'next') - bind(config['bind_prev_tab'], 'prev') - bind(config['bind_goto_tab'], 'goto %s') - bind(config['bind_goto_first'], 'goto 0') - bind(config['bind_goto_last'], 'goto -1') - bind(config['bind_clean_slate'], 'clean') - bind(config['bind_save_preset'], 'preset save %s') - bind(config['bind_load_preset'], 'preset load %s') - bind(config['bind_del_preset'], 'preset del %s') - bind(config['bind_list_presets'], 'preset list %d' % uzbl.pid) - bind(config['bind_exit'], 'exit') - - # Set definitions here - # set(key, command back to fifo) - if config['capture_new_windows']: - set("new_window", r'new $8') - - # Send config to uzbl instance via its socket file. - uzbl.send("\n".join(binds+sets)) - - - def goto_tab(self, index): - '''Goto tab n (supports negative indexing).''' - - tabs = list(self.notebook) - if 0 <= index < len(tabs): - self.notebook.set_current_page(index) - self.update_tablist() - return None - - try: - tab = tabs[index] - # Update index because index might have previously been a - # negative index. - index = tabs.index(tab) - self.notebook.set_current_page(index) - self.update_tablist() - - except IndexError: - pass - - - def next_tab(self, step=1): - '''Switch to next tab or n tabs right.''' - - if step < 1: - error("next_tab: invalid step %r" % step) - return None - - ntabs = self.notebook.get_n_pages() - tabn = (self.notebook.get_current_page() + step) % ntabs - self.notebook.set_current_page(tabn) - self.update_tablist() - - - def prev_tab(self, step=1): - '''Switch to prev tab or n tabs left.''' - - if step < 1: - error("prev_tab: invalid step %r" % step) - return None - - ntabs = self.notebook.get_n_pages() - tabn = self.notebook.get_current_page() - step - while tabn < 0: tabn += ntabs - self.notebook.set_current_page(tabn) - self.update_tablist() - - - def close_tab(self, tabn=None): - '''Closes current tab. Supports negative indexing.''' - - if tabn is None: - tabn = self.notebook.get_current_page() - - else: - try: - tab = list(self.notebook)[tabn] - - except IndexError: - error("close_tab: invalid index %r" % tabn) - return None - - self.notebook.remove_page(tabn) - - - def tab_opened(self, notebook, tab, index): - '''Called upon tab creation. Called by page-added signal.''' - - if config['switch_to_new_tabs']: - self.notebook.set_focus_child(tab) - - else: - oldindex = self.notebook.get_current_page() - oldtab = self.notebook.get_nth_page(oldindex) - self.notebook.set_focus_child(oldtab) - - - def tab_closed(self, notebook, tab, index): - '''Close the window if no tabs are left. Called by page-removed - signal.''' - - if tab in self.tabs.keys(): - uzbl = self.tabs[tab] - for (timer, gid) in uzbl.timers.items(): - error("tab_closed: removing timer %r" % timer) - source_remove(gid) - del uzbl.timers[timer] - - if uzbl._socket: - uzbl._socket.close() - uzbl._socket = None - - uzbl._fifoout = [] - uzbl._socketout = [] - uzbl._kill = True - self._closed.append((uzbl.uri, uzbl.title)) - self._closed = self._closed[-10:] - del self.tabs[tab] - - if self.notebook.get_n_pages() == 0: - if not self._killed and config['save_session']: - if os.path.exists(config['session_file']): - os.remove(config['session_file']) - - self.quit() - - self.update_tablist() - - return True - - - def tab_changed(self, notebook, page, index): - '''Refresh tab list. Called by switch-page signal.''' - - tab = self.notebook.get_nth_page(index) - self.notebook.set_focus_child(tab) - self.update_tablist(index) - return True - - - def update_tablist(self, curpage=None): - '''Upate tablist status bar.''' - - show_tablist = config['show_tablist'] - show_gtk_tabs = config['show_gtk_tabs'] - tab_titles = config['tab_titles'] - show_ellipsis = config['show_ellipsis'] - multiline_tabs = config['multiline_tabs'] - - if multiline_tabs: - multiline = [] - - if not show_tablist and not show_gtk_tabs: - return True - - tabs = self.tabs.keys() - if curpage is None: - curpage = self.notebook.get_current_page() - - title_format = "%s - Uzbl Browser" - max_title_len = config['max_title_len'] - - if show_tablist: - pango = "" - normal = (config['tab_colours'], config['tab_text_colours']) - selected = (config['selected_tab'], config['selected_tab_text']) - - if tab_titles: - tab_format = "<span %s> [ %d <span %s> %s</span> ] </span>" - - else: - tab_format = "<span %s> [ <span %s>%d</span> ] </span>" - - if show_gtk_tabs: - gtk_tab_format = "%d %s" - - for index, tab in enumerate(self.notebook): - if tab not in tabs: continue - uzbl = self.tabs[tab] - - if index == curpage: - self.window.set_title(title_format % uzbl.title) - - # Unicode heavy strings do not like being truncated/sliced so by - # re-encoding the string sliced of limbs are removed. - tabtitle = uzbl.title[:max_title_len + int(show_ellipsis)] - if type(tabtitle) != types.UnicodeType: - tabtitle = unicode(tabtitle, 'utf-8', 'ignore') - - tabtitle = tabtitle.encode('utf-8', 'ignore').strip() - - if show_ellipsis and len(tabtitle) != len(uzbl.title): - tabtitle += "\xe2\x80\xa6" - - if show_gtk_tabs: - if tab_titles: - self.notebook.set_tab_label_text(tab, - gtk_tab_format % (index, tabtitle)) - - else: - self.notebook.set_tab_label_text(tab, str(index)) - - if show_tablist: - style = colour_selector(index, curpage, uzbl) - (tabc, textc) = style - - if multiline_tabs: - opango = pango - - if tab_titles: - pango += tab_format % (tabc, index, textc, - escape(tabtitle)) - - else: - pango += tab_format % (tabc, textc, index) - - self.tablist.set_markup(pango) - listwidth = self.tablist.get_layout().get_pixel_size()[0] - winwidth = self.window.get_size()[0] - - if listwidth > (winwidth - 20): - multiline.append(opango) - pango = tab_format % (tabc, index, textc, - escape(tabtitle)) - - elif tab_titles: - pango += tab_format % (tabc, index, textc, - escape(tabtitle)) - - else: - pango += tab_format % (tabc, textc, index) - - if show_tablist: - if multiline_tabs: - multiline.append(pango) - self.tablist.set_markup(' '.join(multiline)) - - else: - self.tablist.set_markup(pango) - - return True - - - def save_session(self, session_file=None): - '''Save the current session to file for restoration on next load.''' - - strip = str.strip - - if session_file is None: - session_file = config['session_file'] - - tabs = self.tabs.keys() - state = [] - for tab in list(self.notebook): - if tab not in tabs: continue - uzbl = self.tabs[tab] - if not uzbl.uri: continue - state += [(uzbl.uri, uzbl.title),] - - session = {'curtab': self.notebook.get_current_page(), - 'tabs': state} - - if config['json_session']: - raw = json.dumps(session) - - else: - lines = ["curtab = %d" % session['curtab'],] - for (uri, title) in session['tabs']: - lines += ["%s\t%s" % (strip(uri), strip(title)),] - - raw = "\n".join(lines) - - if not os.path.isfile(session_file): - dirname = os.path.dirname(session_file) - if not os.path.isdir(dirname): - os.makedirs(dirname) - - h = open(session_file, 'w') - h.write(raw) - h.close() - - - def load_session(self, session_file=None): - '''Load a saved session from file.''' - - default_path = False - strip = str.strip - json_session = config['json_session'] - delete_loaded = False - - if session_file is None: - default_path = True - delete_loaded = True - session_file = config['session_file'] - - if not os.path.isfile(session_file): - return False - - h = open(session_file, 'r') - raw = h.read() - h.close() - if json_session: - if sum([1 for s in raw.split("\n") if strip(s)]) != 1: - error("Warning: The session file %r does not look json. "\ - "Trying to load it as a non-json session file."\ - % session_file) - json_session = False - - if json_session: - try: - session = json.loads(raw) - curtab, tabs = session['curtab'], session['tabs'] - - except: - error("Failed to load jsonifed session from %r"\ - % session_file) - return None - - else: - tabs = [] - strip = str.strip - curtab, tabs = 0, [] - lines = [s for s in raw.split("\n") if strip(s)] - if len(lines) < 2: - error("Warning: The non-json session file %r looks invalid."\ - % session_file) - return None - - try: - for line in lines: - if line.startswith("curtab"): - curtab = int(line.split()[-1]) - - else: - uri, title = line.split("\t",1) - tabs += [(strip(uri), strip(title)),] - - except: - error("Warning: failed to load session file %r" % session_file) - return None - - session = {'curtab': curtab, 'tabs': tabs} - - # Now populate notebook with the loaded session. - for (index, (uri, title)) in enumerate(tabs): - self.new_tab(uri=uri, title=title, switch=(curtab==index)) - - # A saved session has been loaded now delete it. - if delete_loaded and os.path.exists(session_file): - os.remove(session_file) - - # There may be other state information in the session dict of use to - # other functions. Of course however the non-json session object is - # just a dummy object of no use to no one. - return session - - - def quitrequest(self, *args): - '''Attempt to close all uzbl instances nicely and exit.''' - - self._killed = True - - if config['save_session']: - if len(list(self.notebook)) > 1: - self.save_session() - - else: - # Notebook has one page open so delete the session file. - if os.path.isfile(config['session_file']): - os.remove(config['session_file']) - - for (tab, uzbl) in self.tabs.items(): - uzbl.send("exit") - - # Add a gobject timer to make sure the application force-quits after a - # reasonable period. Calling quit when all the tabs haven't had time to - # close should be a last resort. - timer = "force-quit" - timerid = timeout_add(5000, self.quit, timer) - self._timers[timer] = timerid - - - def quit(self, *args): - '''Cleanup and quit. Called by delete-event signal.''' - - # Close the fifo socket, remove any gobject io event handlers and - # delete socket. - self.unlink_fifo_socket() - - # Remove all gobject timers that are still ticking. - for (timerid, gid) in self._timers.items(): - source_remove(gid) - del self._timers[timerid] - - try: - gtk.main_quit() - - except: - pass - - -if __name__ == "__main__": - - # Read from the uzbl config into the global config dictionary. - readconfig(UZBL_CONFIG, config) - - # Build command line parser - usage = "usage: %prog [OPTIONS] {URIS}..." - parser = OptionParser(usage=usage) - parser.add_option('-n', '--no-session', dest='nosession', - action='store_true', help="ignore session saving a loading.") - parser.add_option('-v', '--verbose', dest='verbose', - action='store_true', help='print verbose output.') - - # Parse command line options - (options, uris) = parser.parse_args() - - if options.nosession: - config['save_session'] = False - - if options.verbose: - config['verbose'] = True - - if config['json_session']: - try: - import simplejson as json - - except: - error("Warning: json_session set but cannot import the python "\ - "module simplejson. Fix: \"set json_session = 0\" or "\ - "install the simplejson python module to remove this warning.") - config['json_session'] = False - - if config['verbose']: - import pprint - sys.stderr.write("%s\n" % pprint.pformat(config)) - - uzbl = UzblTabbed() - - # All extra arguments given to uzbl_tabbed.py are interpreted as - # web-locations to opened in new tabs. - lasturi = len(uris)-1 - for (index,uri) in enumerate(uris): - uzbl.new_tab(uri, switch=(index==lasturi)) - - uzbl.run() |