aboutsummaryrefslogtreecommitdiffhomepage
path: root/examples
diff options
context:
space:
mode:
authorGravatar Mason Larobina <mason.larobina@gmail.com>2009-09-06 01:33:36 +0800
committerGravatar Mason Larobina <mason.larobina@gmail.com>2009-09-06 01:33:36 +0800
commitaa600e2b7611e9c81ced9b405ecd1b2dc14a1ff2 (patch)
tree4f716fc83200b9f113191550697d84631b971617 /examples
parentb9013fb779379c502b7707169eeac7dc15cfdd51 (diff)
event_messages.py rewrite with plugin support and two example plugins.
Diffstat (limited to 'examples')
-rwxr-xr-xexamples/data/uzbl/scripts/event_manager.py523
-rw-r--r--examples/data/uzbl/scripts/plugins/dump_config.py11
-rw-r--r--examples/data/uzbl/scripts/plugins/echo_keys.py16
3 files changed, 487 insertions, 63 deletions
diff --git a/examples/data/uzbl/scripts/event_manager.py b/examples/data/uzbl/scripts/event_manager.py
index d6f4a36..65089c1 100755
--- a/examples/data/uzbl/scripts/event_manager.py
+++ b/examples/data/uzbl/scripts/event_manager.py
@@ -1,55 +1,90 @@
#!/usr/bin/env python
-# Uzbl sample event manager
+# Event Manager for Uzbl
+# Copyright (c) 2009, Mason Larobina <mason.larobina@gmail.com>
+# Copyright (c) 2009, Dieter Plaetinck <diterer@plaetinck.be>
#
# 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
+# 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.
+# (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
+# 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/>.
'''
-The Python Event Manager
-========================
-Sample event manager written in python
+E V E N T _ M A N A G E R . P Y
+===============================
+
+Some descriptive text here.
Usage
+=====
+
+ uzbl | $XDG_DATA_HOME/uzbl/scripts/event_manager.py
+
+Todo
====
-uzbl | <path to event_manager.py>
+
+ - Command line options including supplying a list of plugins to load or not
+ load (default is load all plugins in the plugin_dir).
+ - Spell checking.
+
'''
-import sys
+import imp
import os
+import sys
+import select
+import re
+import types
+import pprint
+import socket
+from traceback import print_exc
-# config dir. needed for bindings config
-if 'XDG_CONFIG_HOME' in os.environ.keys() and os.environ['XDG_CONFIG_HOME']:
- CONFIG_DIR = os.path.join(os.environ['XDG_CONFIG_HOME'], 'uzbl/')
-else:
- CONFIG_DIR = os.path.join(os.environ['HOME'], '.config/uzbl/')
+# ============================================================================
+# ::: Default configuration section ::::::::::::::::::::::::::::::::::::::::::
+# ============================================================================
-# Default config
-config = {
- 'uzbl_fifo': '',
- 'verbose': True,
+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]
-} # End of config dictionary.
+ return os.path.join(os.environ['HOME'], default)
-# buffer for building up commands
-keycmd = ''
+# Setup xdg paths.
+DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/')
+# Config dict (NOT the same as the uzbl.config).
+config = {
+ 'verbose': True,
+ 'plugin_dir': "$XDG_DATA_HOME/uzbl/scripts/plugins/"
+}
+
+
+# ============================================================================
+# ::: End of configuration section :::::::::::::::::::::::::::::::::::::::::::
+# ============================================================================
+
+# Define some globals.
+_VALIDSETKEY = re.compile("^[a-zA-Z][a-zA-Z0-9_]*$").match
_SCRIPTNAME = os.path.basename(sys.argv[0])
+
+
def echo(msg):
'''Prints only if the verbose flag has been set.'''
@@ -57,57 +92,419 @@ def echo(msg):
sys.stderr.write("%s: %s\n" % (_SCRIPTNAME, msg))
-def error(msg):
- '''Prints error message and exits.'''
+def counter():
+ '''Generate unique object id's.'''
+
+ i = 0
+ while True:
+ i += 1
+ yield i
+
+
+class PluginManager(dict):
+ def __init__(self):
+
+ plugin_dir = os.path.expandvars(config['plugin_dir'])
+ self.plugin_dir = os.path.realpath(plugin_dir)
+ if not os.path.exists(self.plugin_dir):
+ os.makedirs(self.plugin_dir)
+
+ # Load all plugins in the plugin_dir.
+ self.load_plugins()
+
+
+ def _find_plugins(self):
+ '''Find all python scripts in plugin dir and return a list of
+ locations and imp moduleinfo's.'''
+
+ dirlist = os.listdir(self.plugin_dir)
+ pythonfiles = filter(lambda s: s.endswith('.py'), dirlist)
+
+ plugins = []
+
+ for filename in pythonfiles:
+ plugins.append(filename[:-3])
+
+ return plugins
+
+
+ def _unload_plugin(self, plugin, remove_pyc=True):
+ '''Unload specific plugin and remove all waste in sys.modules
+
+ Notice: manual manipulation of sys.modules is very un-pythonic but I
+ see no other way to make sure you have 100% unloaded the module. Also
+ this allows us to implement a reload plugins function.'''
+
+ allmodules = sys.modules.keys()
+ allrefs = filter(lambda s: s.startswith("%s." % plugin), allmodules)
+
+ for ref in allrefs:
+ del sys.modules[ref]
+
+ if plugin in sys.modules.keys():
+ del sys.modules[plugin]
+
+ if plugin in self.keys():
+ dict.__delitem__(self, plugin)
+
+ if remove_pyc:
+ # Now remove bytecode.
+ pyc = os.path.join(self.plugin_dir, '%s.pyc' % plugin)
+ if os.path.exists(pyc):
+ os.remove(pyc)
+
+
+ def load_plugins(self):
+
+ # Get a list of python files in the plugin_dir.
+ pluginlist = self._find_plugins()
+
+ # Load the plugins
+ for name in pluginlist:
+ try:
+ # Make sure the plugin isn't already loaded.
+ self._unload_plugin(name)
+
+ except:
+ print_exc()
+
+ try:
+ moduleinfo = imp.find_module(name, [self.plugin_dir,])
+ plugin = imp.load_module(name, *moduleinfo)
+ dict.__setitem__(self, name, plugin)
+
+ # Check it has the init function.
+ if not hasattr(plugin, 'init'):
+ raise ImportError('plugin missing main "init" function.')
+
+ print "Loaded plugin: %r" % name
+
+ except:
+ print_exc()
+ self._unload_plugin(name)
+
+
+ def reload_plugins(self):
+ '''Unload all loaded plugins then run load_plugins() again.
+
+ IMPORTANT: It is crucial that the event handler be deleted if you
+ are going to unload any modules because there is now way to track
+ which module created wich handler.'''
+
+ for plugin in self.keys():
+ self._unload_plugin(plugin)
+
+ self.load_plugins()
+
+
+class UzblInstance:
+ '''Event manager for a uzbl instance.'''
+
+ # Singleton plugin manager.
+ plugins = None
+
+ def __init__(self):
+ '''Initialise event manager.'''
+
+ class ConfigDict(dict):
+ def __init__(self, setcmd):
+ self._setcmd = setcmd
+
+ def __setitem__(self, key, value):
+ '''Updates the config dict and relays any changes back to the
+ uzbl instance via the set function.'''
+
+ self._setcmd(key, value)
+ dict.__setitem__(self, key, value)
+
+ self._config = ConfigDict(self.set)
+
+ # Keep track of keys typed.
+ self.cmdbuffer = ""
+
+ # Keep track of non-meta keys held.
+ self.heldkeys = []
+
+ # Keep track of meta keys held.
+ self.metaheld = []
+
+ # Hold classic bind commands.
+ self.binds = {}
+
+ # Keep track of the mode.
+ self.mode = "command"
+
+ # Event handlers
+ self.handlers = {}
+
+ # Handler object id generator
+ self.nextid = counter().next
+
+ # Fifo socket and socket file locations.
+ self.fifo_socket = None
+ self.socket_file = None
+
+ # Outgoing socket
+ self._socketout = []
+ self._socket = None
+
+ # Outgoing fifo
+ self._fifoout = []
+
+ # Default send method
+ self.send = self._send_socket
+
+ # Running flag
+ self._running = None
+
+ # Incoming message buffer
+ self._buffer = ""
+
+ # Initialise plugin manager
+ if not self.plugins:
+ self.plugins = PluginManager()
- sys.stderr.write("%s: error: %s\n" % (_SCRIPTNAME, msg))
- sys.exit(1)
+ # Call the init() function in every plugin.
+ self._init_plugins()
-def fifo(msg):
- '''Writes commands to uzbl's fifo, if the fifo path is known'''
- echo ('Fifo msg: ' + msg + '(fifo path: ' + config['uzbl_fifo'] + ')')
- if config['uzbl_fifo']:
- fd = os.open(config['uzbl_fifo'], os.O_WRONLY)
- os.write(fd, msg)
- os.close(fd)
+ def _get_config(self):
+ '''Return the uzbl config dictionary.'''
-def submit_keycmd():
- '''Sends the updated keycmd to uzbl, which can render it and stuff'''
+ return self._config
- fifo ('set keycmd = ' + keycmd)
+ # Set read-only config dict getter.
+ config = property(_get_config)
-def main():
- '''Main function.'''
+ def _init_plugins(self):
+ '''Call the init() function in every plugin.'''
- echo ("Init eventhandler")
+ pprint.pprint(self.plugins)
+
+ for plugin in self.plugins.keys():
+ try:
+ self.plugins[plugin].init(self)
+
+ except:
+ print_exc()
+
+
+ def _flush(self):
+ '''Flush messages from the outgoing queue to the uzbl instance.'''
+
+ if len(self._fifoout) and self.fifo_socket:
+ if os.path.exists(self.fifo_socket):
+ h = open(self.fifo_socket, 'w')
+ while len(self._fifoout):
+ msg = self._fifoout.pop(0)
+ print "Sending via fifo: %r" % msg
+ h.write("%s\n" % msg)
+ h.close()
+
+ if len(self._socketout) and self.socket_file:
+ 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)
+ print "Sending via socket: %r" % msg
+ self._socket.send("%s\n" % msg)
+
+
+ def _send_fifo(self, msg):
+ '''Send a command to the uzbl instance via the fifo socket.'''
+
+ self._fifoout.append(msg)
+ self._flush()
+
+
+ def _send_socket(self, msg):
+ '''Send a command to the uzbl instance via the socket file.'''
+
+ self._socketout.append(msg)
+ self._flush()
+
+
+ def connect(self, event, handler, *args, **kargs):
+ '''Connect event with handler and return unique handler id. It goes
+ without saying that if you connect handlers with non-existent events
+ nothing will happen so be careful.
+
+ If you choose the handler may be a uzbl command and upon receiving the
+ event the chosen command will be executed by the uzbl instance.'''
+
+ if event not in self.handlers.keys():
+ self.handlers[event] = {}
+
+ id = self.nextid()
+ d = {'handler': handler, 'args': args, 'kargs': kargs}
+
+ self.handlers[event][id] = d
+ print "Added handler:", event, d
+
+ # The unique id is returned so that the newly created event handler can
+ # be destroyed if need be.
+ return id
+
+
+ def remove(self, id):
+ '''Remove connected event handler by unique handler id.'''
+
+ for event in self.handlers.keys():
+ if id in self.handlers[event].keys():
+ print "Removed handler:", self.handlers[event][id]
+ del self.handlers[event][id]
+
+
+ def bind(self, glob, cmd=None):
+ '''Support for classic uzbl binds.
+
+ For example:
+ bind ZZ = exit -> bind('ZZ', 'exit')
+ bind o _ = uri %s -> bind('o _', 'uri %s')
+ bind fl* = sh 'echo %s' -> bind('fl*', "sh 'echo %s'")
+ bind fl* = -> bind('fl*')
+
+ And it is also possible to execute a function on activation:
+ bind('DD', myhandler)
+
+ NOTE: This wont work yet but the groundwork has been layed out.
+ '''
+
+ if not cmd:
+ if glob in self.binds.keys():
+ print "Deleted bind:", self.binds[glob]
+ del self.binds[glob]
+
+ d = {'glob': glob, 'once': True, 'hasargs': True, 'cmd': cmd}
+
+ if glob.endswith('*'):
+ d['pre'] = glob.rstrip('*')
+ d['once'] = False
+
+ elif glob.endswith('_'):
+ d['pre'] = glob.rstrip('_')
+
+ else:
+ d['pre'] = glob
+ d['hasargs'] = False
+
+ self.binds[glob] = d
+ print "Added bind:", d
+
+
+ def set(self, key, value):
+ '''Sets key "key" with value "value" in the uzbl instance.'''
+
+ # TODO: Make a real escaping function.
+ escape = str
+
+ if not _VALIDSETKEY(key):
+ raise KeyError("%r" % key)
+
+ if '\n' in value:
+ raise ValueError("invalid character: \\n")
+
+ self.send('set %s = %s' % (key, escape(value)))
+
+
+ def listen_from_fd(self, fd):
+ '''Main loop reading event messages from stdin.'''
+
+ self._running = True
+ try:
+ while self._running:
+
+ # Poll for reading & errors from fd.
+ if select.select([fd,], [], [], 1)[0]:
+ self.read_from_fd(fd)
+ continue
+
+ # Check that all messages have been purged from the out queue.
+ self._flush()
+
+ except KeyboardInterrupt:
+ print
+
+
+ def read_from_fd(self, fd):
+ '''Reads incoming event messages from fd.'''
+
+ raw = fd.readline()
+ if not raw:
+ # Read null byte (i.e. uzbl closed).
+ self._running = False
+ return
+
+ msg = raw.strip().split(' ', 3)
+
+ if not msg or msg[0] != "EVENT":
+ # Not an event message
+ return
+
+ event, args = msg[1], msg[3]
+ self.handle_event(event, args)
+
+
+ def handle_event(self, event, args):
+ '''Handle uzbl events internally before dispatch.'''
+
+ if event == 'VARIABLE_SET':
+ l = args.split(' ', 1)
+ if len(l) == 1:
+ l.append("")
+
+ key, value = l
+ dict.__setitem__(self._config, key, value)
+
+ elif event == 'FIFO_SET':
+ self.fifo_socket = args
+
+ # Workaround until SOCKET_SET is implemented.
+ self.socket_file = args.replace("fifo", "socket")
+
+ elif event == 'SOCKET_SET':
+ self.socket_file = args
+
+ # Now dispatch event to plugin's event handlers.
+ self.dispatch_event(event, args)
+
+
+ def dispatch_event(self, event, args):
+ '''Now send the event to any event handlers added with the connect
+ function. In other words: handle plugin's event hooks.'''
+ unhandled = True
+
+ if event in self.handlers.keys():
+ for hid in self.handlers[event]:
+ try:
+ unhandled = False
+ handler = self.handlers[event][hid]
+ print "Executing handler:", event, handler
+ self.exc_handler(handler, args)
+
+ except:
+ print_exc()
+
+ if unhandled:
+ print "Unhandled event:", event, args
+
+
+ def exc_handler(self, d, args):
+ '''Handle handler.'''
+
+ if type(d['handler']) == types.FunctionType:
+ handler = d['handler']
+ handler(self, args, *d['args'], **d['kargs'])
- for line in sys.stdin:
- line = line.strip()
- data = line.partition('EVENT ')
- if (data[0] == ""):
- line = data[2]
- echo ("Got event: " + line)
- data = line.partition(' ')
- event_name = data[0]
- event_data = data[2]
else:
- echo ("Non-event: " + line)
- continue
-
- if (event_name == 'FIFO_SET'):
- config['uzbl_fifo'] = event_data.split()[-1]
- elif (event_name == 'KEY_PRESS'):
- # todo: keep a table of pressed modkeys. do we work with Mod[1-4] here or Alt_L and such?
- key = event_data.split()[-1]
- if (key == 'Escape'):
- keycmd = ''
- submit_keycmd
- elif (event_name == 'KEY_RELEASE'):
- #todo : update table of pressed modkeys
- submit_keycmd
+ cmd = d['handler']
+ self.send(cmd)
+
if __name__ == "__main__":
- main()
- \ No newline at end of file
+ uzbl = UzblInstance().listen_from_fd(sys.stdin)
diff --git a/examples/data/uzbl/scripts/plugins/dump_config.py b/examples/data/uzbl/scripts/plugins/dump_config.py
new file mode 100644
index 0000000..381dbf2
--- /dev/null
+++ b/examples/data/uzbl/scripts/plugins/dump_config.py
@@ -0,0 +1,11 @@
+import pprint
+
+def dump_config(uzbl, args):
+ '''Dump the config every time the page finishes loading.'''
+
+ print "%s\n" % pprint.pformat(uzbl.config)
+
+
+def init(uzbl):
+ id = uzbl.connect('LOAD_FINISH', dump_config)
+ print "Dump config id:", id
diff --git a/examples/data/uzbl/scripts/plugins/echo_keys.py b/examples/data/uzbl/scripts/plugins/echo_keys.py
new file mode 100644
index 0000000..e1a1850
--- /dev/null
+++ b/examples/data/uzbl/scripts/plugins/echo_keys.py
@@ -0,0 +1,16 @@
+def echo_keys(uzbl, key, print_meta=True):
+ '''Prints key-presses to the terminal.'''
+
+ keys_pressed = int(uzbl.config['keys_pressed']) + 1
+ print "You pressed:", key, "Total keys pressed:", keys_pressed
+ uzbl.config['keys_pressed'] = str(keys_pressed)
+
+
+def init(uzbl):
+ '''In this function attach all your event hooks using uzbl.connect and
+ uzbl.bind functions.'''
+
+ id = uzbl.connect('KEY_PRESS', echo_keys)
+ print "echo_keys hook id:", id
+
+ uzbl.config['keys_pressed'] = str(0)