aboutsummaryrefslogtreecommitdiffhomepage
path: root/examples
diff options
context:
space:
mode:
authorGravatar Simon Lipp <sloonz@gmail.com>2009-12-28 23:49:35 +0100
committerGravatar Mason Larobina <mason.larobina@gmail.com>2010-01-03 00:25:20 +0800
commit6a2b62a798bbaca10ded2d95d739b877b735b10e (patch)
treea1cddf45fc2e310ed4972dd0c123de77cc477f76 /examples
parentf2f1600e33e6f9487ea5cc96dcefb576cb5684d3 (diff)
uzbl-tabbed changes :
* Remove bind_* options: does not work with current uzbl revisions, and a better implementation will come soon. * Remove reading of uzbl config file (same reason) * Better IPC handling (to sumarize: less periodic polling): * Better separation between network layers (communication is in SocketClient, protocol in UzblInstance, user interface in UzblTabbed) * use io_add_watch instead of select for reading uzbl events * does not use a generated hash to separate events, but the LF character * get rid of all the flush()ing logic * does not probe periodically for the title anymore; use uzbl events * create a /tmp/uzbltabbed_socket instead of polling the /tmp/uzbl_socket_* socket
Diffstat (limited to 'examples')
-rwxr-xr-xexamples/data/uzbl/scripts/uzbl-tabbed442
1 files changed, 183 insertions, 259 deletions
diff --git a/examples/data/uzbl/scripts/uzbl-tabbed b/examples/data/uzbl/scripts/uzbl-tabbed
index 6bab849..9d8e1e6 100755
--- a/examples/data/uzbl/scripts/uzbl-tabbed
+++ b/examples/data/uzbl/scripts/uzbl-tabbed
@@ -96,25 +96,6 @@
# 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.
#
@@ -209,12 +190,6 @@ 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
@@ -249,25 +224,6 @@ config = {
'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
@@ -371,108 +327,132 @@ def escape(s):
return s
-def gen_endmarker():
- '''Generates a random md5 for socket message-termination endmarkers.'''
+class SocketClient:
+ '''Represents a Uzbl instance, which is not necessarly linked with a UzblInstance'''
+
+ # List of UzblInstance objects not already linked with a SocketClient
+ instances_queue = {}
+
+ def __init__(self, socket):
+ self._buffer = ""
+ self._socket = socket
+ self._watchers = [io_add_watch(socket, IO_IN, self._socket_recv),\
+ io_add_watch(socket, IO_HUP, self._socket_closed)]
+ self.uzbl = None
+
+
+ def _socket_recv(self, fd, condition):
+ '''Data available on socket, process it'''
+
+ self._feed(self._socket.recv(1024)) #TODO: is io_add_watch edge or level-triggered ?
+ return True
+
+
+ def _socket_closed(self, fd, condition):
+ '''Remote client exited'''
+ self.uzbl.close()
+ return False
- return hashlib.md5(str(random.random()*time.time())).hexdigest()
+
+ def _feed(self, data):
+ '''An Uzbl instance sent some data, parse it'''
+
+ self._buffer += data
+ if self.uzbl:
+ if "\n" in self._buffer:
+ cmds = self._buffer.split("\n")
+
+ if cmds[-1]: # Last command has been received incomplete, don't process it
+ self._buffer, cmds = cmds[-1], cmds[:-1]
+ else:
+ self._buffer = ""
+
+ for cmd in cmds:
+ if cmd:
+ self.uzbl.parse_command(cmd)
+ else:
+ name = re.findall('^EVENT \[(\d+-\d+)\] INSTANCE_START \d+$', self._buffer, re.M)
+ uzbl = self.instances_queue.get(name[0])
+ if uzbl:
+ del self.instances_queue[name[0]]
+ self.uzbl = uzbl
+ self.uzbl.got_socket(self)
+ self._feed("")
+
+ def send(self, data):
+ '''Child socket send function.'''
+
+ self._socket.send(data + "\n")
+
+ def close(self):
+ '''Close the connection'''
+
+ if self._socket:
+ self._socket.close()
+ self._socket = None
+ map(source_remove, self._watchers)
+ self._watchers = []
class UzblInstance:
'''Uzbl instance meta-data/meta-action object.'''
- def __init__(self, parent, tab, socket_file, pid, uri, title, switch):
+ def __init__(self, parent, tab, name, uri, title, switch):
self.parent = parent
self.tab = tab
- self.socket_file = socket_file
- self.pid = pid
+ self.name = name
self.title = title
self.uri = uri
- self.timers = {}
- self._lastprobe = 0
- self._socketout = []
- self._socket = None
- self._buffer = ""
- # Switch to tab after loading
- self._switch = switch
- # 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)
+ self._client = None
+ self._switch = switch # Switch to tab after loading ?
- def flush(self, timer_call=False):
- '''Flush messages from the socket-out queue.'''
+ def got_socket(self, client):
+ '''Uzbl instance is now connected'''
+ self._client = client
- if self._kill:
- if self._socket:
- self._socket.close()
- self._socket = None
+ self.parent.tabs[self.tab] = self
+ self.parent.update_tablist()
- error("Flush called on dead tab.")
- return False
-
- 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
+ self.parent.config_uzbl(self)
- if self._socket:
- while len(self._socketout):
- msg = self._socketout.pop(0)
- self._socket.send("%s\n"%msg)
+ if self._switch:
+ tabs = list(self.parent.notebook)
+ tabid = tabs.index(self.tab)
+ self.parent.goto_tab(tabid)
- if not self._connected and timer_call:
- if not self._socketout:
- self._connected = True
- if timer_call in self.timers.keys():
- source_remove(self.timers[timer_call])
- del self.timers[timer_call]
+ def set(self, key, val):
+ ''' Send the SET command to Uzbl '''
- if self._switch:
- self.grabfocus()
+ self._client.send('set %s = %s') #TODO: escape chars ?
- return len(self._socketout)
+ def exit(sedf):
+ ''' Ask the Uzbl instance to close '''
- def grabfocus(self):
- '''Steal parent focus and switch the notebook to my own tab.'''
+ self._client.send('exit') #TODO: escape chars ?
- tabs = list(self.parent.notebook)
- tabid = tabs.index(self.tab)
- self.parent.goto_tab(tabid)
+ def parse_command(self, cmd):
+ ''' Parse event givent by the Uzbl instance '''
- def probe(self):
- '''Probes the client for information about its self.'''
+ type, _, args = cmd.split(" ", 2)
+ if type == "EVENT":
+ type, args = args.split(" ", 1)
+ if type == "TITLE_CHANGED":
+ self.title = args
+ self.parent.update_tablist()
- if self._connected:
- self.send(self._probecmds)
- self._lastprobe = time.time()
+ def close(self):
+ '''The remote instance exited'''
- def send(self, msg):
- '''Child socket send function.'''
+ if self._client:
+ self._client.close()
+ self._client = None
- self._socketout.append(msg)
- # Flush messages from queue if able.
- return self.flush()
class UzblTabbed:
'''A tabbed version of uzbl using gtk.Notebook'''
@@ -480,9 +460,6 @@ class UzblTabbed:
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
@@ -493,6 +470,9 @@ class UzblTabbed:
# Holds metadata on the uzbl childen open.
self.tabs = {}
+ # Uzbl sockets (socket => SocketClient)
+ self.clients = {}
+
# Generates a unique id for uzbl socket filenames.
self.next_pid = counter().next
@@ -577,12 +557,18 @@ class UzblTabbed:
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()
+ # Store information about the applications fifo and socket.
+ fifo_filename = 'uzbltabbed_%d.fifo' % os.getpid()
+ socket_filename = 'uzbltabbed_%d.socket' % os.getpid()
+ self._fifo = None
+ self._socket = None
+ self.fifo_path = os.path.join(config['fifo_dir'], fifo_filename)
+ self.socket_path = os.path.join(config['socket_dir'], socket_filename)
+
+ # Now initialise the fifo and the socket
+ self.init_fifo()
+ self.init_socket()
# If we are using sessions then load the last one if it exists.
if config['save_session']:
@@ -592,7 +578,7 @@ class UzblTabbed:
def run(self):
'''UzblTabbed main function that calls the gtk loop.'''
- if not len(self.tabs):
+ if not self.clients and not SocketClient.instances_queue and not self.tabs:
self.new_tab()
gtk_refresh = int(config['gtk_refresh'])
@@ -603,10 +589,6 @@ class UzblTabbed:
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))
@@ -620,7 +602,7 @@ class UzblTabbed:
error("encounted error %r" % sys.exc_info()[1])
# Unlink fifo socket
- self.unlink_fifo_socket()
+ self.unlink_fifo()
# Attempt to close all uzbl instances nicely.
self.quitrequest()
@@ -651,40 +633,72 @@ class UzblTabbed:
self.quitrequest()
- def init_fifo_socket(self):
- '''Create interprocess communication fifo socket.'''
+ def init_socket(self):
+ '''Create interprocess communication socket.'''
+
+ def accept(sock, condition):
+ '''A new uzbl instance was created'''
+
+ client, _ = sock.accept()
+ self.clients[client] = SocketClient(client)
+
+ return True
+
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ sock.bind(self.socket_path)
+ sock.listen(1)
+
+ # Add event handler for IO_IN event.
+ self._socket = (sock, io_add_watch(sock, IO_IN, accept))
+
+ echo("[socket] listening at %r" % self.socket_path)
+
+ # Add atexit register to destroy the socket on program termination.
+ atexit.register(self.close_socket)
+
+
+ def close_socket(self):
+ '''Close the socket when closing the application'''
+
+ (fd, watcher) = self._socket
+ source_remove(watcher)
+ fd.close()
+
+
+ def init_fifo(self):
+ '''Create interprocess communication fifo.'''
- 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)
+ if os.path.exists(self.fifo_path):
+ if not os.access(self.fifo_path, os.F_OK | os.R_OK | os.W_OK):
+ os.mkfifo(self.fifo_path)
else:
- basedir = os.path.dirname(self.fifo_socket)
+ basedir = os.path.dirname(self.fifo_path)
if not os.path.exists(basedir):
os.makedirs(basedir)
- os.mkfifo(self.fifo_socket)
+ os.mkfifo(self.fifo_path)
# Add event handlers for IO_IN & IO_HUP events.
self.setup_fifo_watchers()
- echo("listening at %r" % self.fifo_socket)
+ echo("[fifo] listening at %r" % self.fifo_path)
- # Add atexit register to destroy the socket on program termination.
- atexit.register(self.unlink_fifo_socket)
+ # Add atexit register to destroy the fifo on program termination.
+ atexit.register(self.unlink_fifo)
- def unlink_fifo_socket(self):
+ def unlink_fifo(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.
+ # Make sure the fifo 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)
+ # And unlink if the real fifo exists.
+ if os.path.exists(self.fifo_path):
+ os.unlink(self.fifo_path)
+ echo("unlinked %r" % self.fifo_path)
def close_fifo(self):
@@ -707,10 +721,10 @@ class UzblTabbed:
'''Open fifo socket fd and setup gobject IO_IN & IO_HUP event
handlers.'''
- # Close currently open fifo_socket fd and kill all watchers
+ # Close currently open fifo fd and kill all watchers
self.close_fifo()
- fd = os.open(self.fifo_socket, os.O_RDONLY | os.O_NONBLOCK)
+ fd = os.open(self.fifo_path, 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),\
@@ -749,43 +763,6 @@ class UzblTabbed:
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.'''
@@ -858,7 +835,7 @@ class UzblTabbed:
elif cmd[0] in ["title", "uri"]:
if len(cmd) > 2:
- uzbl = self.get_tab_by_pid(int(cmd[1]))
+ uzbl = self.get_tab_by_name(int(cmd[1]))
if uzbl:
old = getattr(uzbl, cmd[0])
new = ' '.join(cmd[2:])
@@ -867,7 +844,7 @@ class UzblTabbed:
self.update_tablist()
else:
- error("parse_command: no uzbl with pid %r" % int(cmd[1]))
+ error("parse_command: no uzbl with name %r" % int(cmd[1]))
elif cmd[0] == "preset":
if len(cmd) < 3:
@@ -890,20 +867,20 @@ class UzblTabbed:
error("parse_command: preset %r does not exist." % path)
elif cmd[1] == "list":
- uzbl = self.get_tab_by_pid(int(cmd[2]))
+ uzbl = self.get_tab_by_name(int(cmd[2]))
if uzbl:
if not os.path.isdir(config['saved_sessions_dir']):
js = "js alert('No saved presets.');"
- uzbl.send(js)
+ uzbl._client.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)
+ uzbl._client.send(js)
else:
- error("parse_command: unknown tab pid.")
+ error("parse_command: unknown tab name.")
else:
error("parse_command: unknown parse command %r"\
@@ -922,11 +899,11 @@ class UzblTabbed:
error("parse_command: unknown command %r" % ' '.join(cmd))
- def get_tab_by_pid(self, pid):
- '''Return uzbl instance by pid.'''
+ def get_tab_by_name(self, name):
+ '''Return uzbl instance by name.'''
for (tab, uzbl) in self.tabs.items():
- if uzbl.pid == pid:
+ if uzbl.name == name:
return uzbl
return False
@@ -938,15 +915,12 @@ class UzblTabbed:
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()
-
- socket_filename = 'uzbl_socket_%s_%0.2d' % (self.wid, pid)
- socket_file = os.path.join(config['socket_dir'], socket_filename)
+ name = "%d-%d" % (os.getpid(), self.next_pid())
if switch is None:
switch = config['switch_to_new_tabs']
@@ -954,22 +928,12 @@ class UzblTabbed:
if not title:
title = config['new_tab_title']
- uzbl = self.UzblInstance(self, tab, socket_file, pid,\
- uri, title, switch)
-
- if len(uri):
- uri = "--uri %r" % uri
-
- self.tabs[tab] = uzbl
- cmd = 'uzbl-browser -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 socket
- # has been created.
- timerid = timeout_add(100, uzbl.flush, "flush-initial-config")
- uzbl.timers['flush-initial-config'] = timerid
+ cmd = ['uzbl-browser', '-n', name, '-s', str(sid),
+ '--connect-socket', self.socket_path, '--uri', uri]
+ subprocess.Popen(cmd) # TODO: do i need close_fds=True ?
- self.update_tablist()
+ uzbl = UzblInstance(self, tab, name, uri, title, switch)
+ SocketClient.instances_queue[name] = uzbl
def clean_slate(self):
@@ -980,48 +944,17 @@ class UzblTabbed:
for tab in list(self.notebook)[:-1]:
if tab not in tabs: continue
uzbl = self.tabs[tab]
- uzbl.send("exit")
+ uzbl.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))
+ uzbl.set("new_window", r'new $8')
def goto_tab(self, index):
@@ -1107,17 +1040,8 @@ class UzblTabbed:
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.close()
- uzbl._socketout = []
- uzbl._kill = True
self._closed.append((uzbl.uri, uzbl.title))
self._closed = self._closed[-10:]
del self.tabs[tab]
@@ -1375,7 +1299,7 @@ class UzblTabbed:
os.remove(config['session_file'])
for (tab, uzbl) in self.tabs.items():
- uzbl.send("exit")
+ uzbl.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
@@ -1390,7 +1314,7 @@ class UzblTabbed:
# Close the fifo socket, remove any gobject io event handlers and
# delete socket.
- self.unlink_fifo_socket()
+ self.unlink_fifo()
# Remove all gobject timers that are still ticking.
for (timerid, gid) in self._timers.items():