aboutsummaryrefslogtreecommitdiffhomepage
path: root/examples/data/uzbl/scripts
diff options
context:
space:
mode:
authorGravatar Mason Larobina <mason.larobina@gmail.com>2009-10-15 00:08:45 +0800
committerGravatar Mason Larobina <mason.larobina@gmail.com>2009-10-15 00:08:45 +0800
commitfdd8d07eba9e483baf618b6d8fd928fefb8465f3 (patch)
tree26ea2f4359db95f27ec80224d7f9b13766845eb0 /examples/data/uzbl/scripts
parent847ddc83ccbd7964898980f3f4dd520937ea3bbe (diff)
Added multi-instance managing to event_manager.py
1. Moved plugin directory from './examples/data/scripts/plugins' to './examples/data/'. 2. Broke up the plugin manager class into two small functions. 3. Removed the handler objects ability to have non-callable handlers given that there is a perfectly good on_event.py plugin which can do exactly the same. 4. Gave event_manager daemon abilities similar to the cookie_daemon. 5. Using pid to track the event manager daemons running status. 6. Added the ability to load plugins from multiple locations. 7. Removed all outgoing message queues as this work-around is no longer required after the newly added --connect-socket uzbl-core ability. 8. Removed native stdin/fifo reading ability. Use socat if required. 9. Updated uzbl-browser script to load example cookie_daemon if cookie_daemon is not in $XDG_DATA_HOME/uzbl/scripts/ 10. Added a new event_manager.py launcher uzbl-daemon. 11. Updated make test-dev-browser target to test uzbl-daemon also. 12. Added init like {start|stop|restart} to the event manager. 13. Added a fourth 'list' option to {start|stop|..} to list the plugins and dirs of each plugin that would be loaded by the event manager.
Diffstat (limited to 'examples/data/uzbl/scripts')
-rwxr-xr-xexamples/data/uzbl/scripts/event_manager.py865
-rw-r--r--examples/data/uzbl/scripts/plugins/bind.py374
-rw-r--r--examples/data/uzbl/scripts/plugins/config.py87
-rw-r--r--examples/data/uzbl/scripts/plugins/keycmd.py382
-rw-r--r--examples/data/uzbl/scripts/plugins/mode.py159
-rw-r--r--examples/data/uzbl/scripts/plugins/on_event.py117
-rw-r--r--examples/data/uzbl/scripts/plugins/plugin_template.py75
-rw-r--r--examples/data/uzbl/scripts/plugins/progress_bar.py158
8 files changed, 485 insertions, 1732 deletions
diff --git a/examples/data/uzbl/scripts/event_manager.py b/examples/data/uzbl/scripts/event_manager.py
index 2e84ded..271c65e 100755
--- a/examples/data/uzbl/scripts/event_manager.py
+++ b/examples/data/uzbl/scripts/event_manager.py
@@ -24,30 +24,19 @@ E V E N T _ M A N A G E R . P Y
Event manager for uzbl written in python.
-Usage
-=====
-
- uzbl | $XDG_DATA_HOME/uzbl/scripts/event_manager.py
-
-Todo
-====
-
- - 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 imp
import os
import sys
-import select
import re
import types
import socket
import pprint
import time
+import atexit
+from select import select
+from signal import signal, SIGTERM
from optparse import OptionParser
from traceback import print_exc
@@ -69,13 +58,21 @@ def xdghome(key, default):
# Setup xdg paths.
DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/')
+CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/')
# Config dict (NOT the same as the uzbl.config).
config = {
- 'verbose': False,
- 'plugin_dir': "$XDG_DATA_HOME/uzbl/scripts/plugins/",
- 'plugins_load': [],
- 'plugins_ignore': [],
+ 'verbose': False,
+ 'daemon_mode': True,
+
+ 'plugins_load': [],
+ 'plugins_ignore': [],
+
+ 'plugin_dirs': [os.path.join(DATA_DIR, 'plugins/'),
+ '/usr/local/share/uzbl/examples/data/uzbl/plugins/'],
+
+ 'server_socket': os.path.join(CACHE_DIR, 'event_daemon'),
+ 'pid_file': os.path.join(CACHE_DIR, 'event_daemon.pid'),
}
@@ -122,223 +119,265 @@ def isiterable(obj):
return hasattr(obj, "__iter__")
-class PluginManager(dict):
- def __init__(self):
+def find_plugins(plugin_dirs):
+ '''Find all event manager plugins in the plugin dirs and return a
+ dictionary of {'plugin-name.py': '/full/path/to/plugin-name.py', ...}'''
- 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)
+ plugins = {}
+
+ for plugin_dir in plugin_dirs:
+ plugin_dir = os.path.realpath(os.path.expandvars(plugin_dir))
+ if not os.path.isdir(plugin_dir):
+ continue
- self.load_plugins()
+ for file in os.listdir(plugin_dir):
+ if not file.lower().endswith('.py'):
+ continue
+ path = os.path.join(plugin_dir, file)
+ if not os.path.isfile(path):
+ continue
- def _find_all_plugins(self):
- '''Find all python scripts in plugin dir and return a list of
- locations and imp moduleinfo's.'''
+ if file not in plugins:
+ plugins[file] = plugin_dir
- dirlist = os.listdir(self.plugin_dir)
- pythonfiles = filter(lambda s: s.endswith('.py'), dirlist)
+ return plugins
- plugins = []
- for filename in pythonfiles:
- plugins.append(filename[:-3])
+def load_plugins(plugin_dirs, load=[], ignore=[]):
+ '''Load event manager plugins found in the plugin_dirs.'''
- return plugins
+ # Find the plugins in the plugin_dirs.
+ found = find_plugins(plugin_dirs)
+ if load:
+ # Ignore anything not in the load list.
+ for plugin in found.keys():
+ if plugin not in load:
+ del found[plugin]
- def _unload_plugin(self, name, remove_pyc=True):
- '''Unload specific plugin and remove all waste in sys.modules
+ if ignore:
+ # Ignore anything in the ignore list.
+ for plugin in found.keys():
+ if plugin in ignore:
+ del found[plugin]
- 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.'''
+ # Print plugin list to be loaded.
+ pprint.pprint(found)
- allmodules = sys.modules.keys()
- allrefs = filter(lambda s: s.startswith("%s." % name), allmodules)
+ loaded = {}
+ # Load all found plugins into the loaded dict.
+ for (filename, dir) in found.items():
+ name = filename[:-3]
+ info = imp.find_module(name, [dir,])
+ plugin = imp.load_module(name, *info)
+ loaded[(dir, filename)] = plugin
- for ref in allrefs:
- del sys.modules[ref]
+ return loaded
- if name in sys.modules.keys():
- del sys.modules[name]
- if name in self:
- del self[name]
+def daemonize():
+ '''Daemonize the process using the Stevens' double-fork magic.'''
- if remove_pyc:
- pyc = os.path.join(self.plugin_dir, '%s.pyc' % name)
- if os.path.exists(pyc):
- os.remove(pyc)
+ try:
+ if os.fork():
+ os._exit(0)
+ except OSError:
+ print_exc()
+ sys.stderr.write("fork #1 failed")
+ sys.exit(1)
- def load_plugins(self):
+ os.chdir('/')
+ os.setsid()
+ os.umask(0)
- if config['plugins_load']:
- pluginlist = config['plugins_load']
+ try:
+ if os.fork():
+ os._exit(0)
- else:
- pluginlist = self._find_all_plugins()
- for name in config['plugins_ignore']:
- if name in pluginlist:
- pluginlist.remove(name)
+ except OSError:
+ print_exc()
+ sys.stderr.write("fork #2 failed")
+ sys.exit(1)
- for name in pluginlist:
- # Make sure the plugin isn't already loaded.
- self._unload_plugin(name)
+ sys.stdout.flush()
+ sys.stderr.flush()
- try:
- moduleinfo = imp.find_module(name, [self.plugin_dir,])
- plugin = imp.load_module(name, *moduleinfo)
- self[name] = plugin
+ devnull = '/dev/null'
+ stdin = file(devnull, 'r')
+ stdout = file(devnull, 'a+')
+ stderr = file(devnull, 'a+', 0)
- except:
- raise
+ os.dup2(stdin.fileno(), sys.stdin.fileno())
+ os.dup2(stdout.fileno(), sys.stdout.fileno())
+ os.dup2(stderr.fileno(), sys.stderr.fileno())
- if self.keys():
- echo("loaded plugin(s): %s" % ', '.join(self.keys()))
+def make_dirs(path):
+ '''Make all basedirs recursively as required.'''
- def reload_plugins(self):
- '''Unload all loaded plugins then run load_plugins() again.
+ dirname = os.path.dirname(path)
+ if not os.path.isdir(dirname):
+ os.makedirs(dirname)
- 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)
+def make_pid_file(pid_file):
+ '''Make pid file at given pid_file location.'''
- self.load_plugins()
+ make_dirs(pid_file)
+ file = open(pid_file, 'w')
+ file.write('%d' % os.getpid())
+ file.close()
-class CallPrepender(object):
- '''Execution argument modifier. Takes (arg, function) then modifies the
- function call:
+def del_pid_file(pid_file):
+ '''Delete pid file at given pid_file location.'''
- -> function(*args, **kargs) -> function(arg, *args, **kargs) ->'''
+ if os.path.isfile(pid_file):
+ os.remove(pid_file)
- def __init__(self, uzbl, function):
- self.function = function
- self.uzbl = uzbl
- def call(self, *args, **kargs):
- return self.function(self.uzbl, *args, **kargs)
+def get_pid(pid_file):
+ '''Read pid from pid_file.'''
+ try:
+ file = open(pid_file, 'r')
+ strpid = file.read()
+ file.close()
+ pid = int(strpid.strip())
+ return pid
-class Handler(object):
+ except:
+ print_exc()
+ return None
- nexthid = counter().next
- def __init__(self, event, handler, *args, **kargs):
- self.callable = iscallable(handler)
- if self.callable:
- self.function = handler
- self.args = args
- self.kargs = kargs
+def pid_running(pid):
+ '''Returns True if a process with the given pid is running.'''
- elif kargs:
- raise ArgumentError("cannot supply kargs with a uzbl command")
+ try:
+ os.kill(pid, 0)
- elif isiterable(handler):
- self.commands = handler
+ except OSError:
+ return False
- else:
- self.commands = [handler,] + list(args)
+ else:
+ return True
+
+
+def term_process(pid):
+ '''Send a SIGTERM signal to the process with the given pid.'''
+
+ if not pid_running(pid):
+ return False
+
+ os.kill(pid, SIGTERM)
+
+ start = time.time()
+ while True:
+ if not pid_running(pid):
+ return True
+
+ if time.time() - start > 5:
+ raise OSError('failed to stop process with pid: %d' % pid)
+
+ time.sleep(0.25)
+
+
+def prepender(function, *pre_args):
+ '''Creates a wrapper around a callable object injecting a list of
+ arguments before the called arguments.'''
+ locals = (function, pre_args)
+ def _prepender(*args, **kargs):
+ (function, pre_args) = locals
+ return function(*(pre_args + args), **kargs)
+
+ return _prepender
+
+
+class EventHandler(object):
+
+ nexthid = counter().next
+
+ def __init__(self, event, handler, *args, **kargs):
+ if not iscallable(handler):
+ raise ArgumentError("EventHandler object requires a callable "
+ "object function for the handler argument not: %r" % handler)
+
+ self.function = handler
+ self.args = args
+ self.kargs = kargs
self.event = event
self.hid = self.nexthid()
def __repr__(self):
- args = ["event=%s" % self.event, "hid=%d" % self.hid]
+ args = ["event=%s" % self.event, "hid=%d" % self.hid,
+ "function=%r" % self.function]
- if self.callable:
- args.append("function=%r" % self.function)
- if self.args:
- args.append("args=%r" % self.args)
+ if self.args:
+ args.append("args=%r" % self.args)
- if self.kargs:
- args.append("kargs=%r" % self.kargs)
-
- else:
- cmdlen = len(self.commands)
- cmds = self.commands[0] if cmdlen == 1 else self.commands
- args.append("command%s=%r" % ("s" if cmdlen-1 else "", cmds))
+ if self.kargs:
+ args.append("kargs=%r" % self.kargs)
return "<EventHandler(%s)>" % ', '.join(args)
class UzblInstance(object):
- '''Event manager for a uzbl instance.'''
-
- # Singleton plugin manager.
- plugins = None
+ def __init__(self, parent, client_socket):
- def __init__(self):
- '''Initialise event manager.'''
-
- # Hold functions exported by plugins.
+ # Internal variables.
self._exports = {}
- self._running = None
- self._buffer = ''
-
self._handlers = {}
+ self._parent = parent
+ self._client_socket = client_socket
- # Variables needed for fifo & socket communication with uzbl.
- self.uzbl_fifo = None
- self.uzbl_socket = None
- self._fifo_cmd_queue = []
- self._socket_cmd_queue = []
- self._socket = None
- self.send = self._send_socket
+ self.buffer = ''
- if not self.plugins:
- self.plugins = PluginManager()
-
- # Call the init() function in every plugin which then setup their
- # respective hooks (event handlers, binds or timers).
+ # Call the init() function in every plugin. Inside the init function
+ # is where the plugins insert the hooks into the event system.
self._init_plugins()
- def __getattribute__(self, name):
+ def __getattribute__(self, attr):
'''Expose any exported functions before class functions.'''
- if not name.startswith('_'):
+ if not attr.startswith('_'):
exports = object.__getattribute__(self, '_exports')
- if name in exports:
- return exports[name]
+ if attr in exports:
+ return exports[attr]
- return object.__getattribute__(self, name)
+ return object.__getattribute__(self, attr)
def _init_plugins(self):
'''Call the init() function in every plugin and expose all exposable
functions in the plugins root namespace.'''
+ plugins = self._parent['plugins']
+
# Map all plugin exports
- for (name, plugin) in self.plugins.items():
+ for (name, plugin) in plugins.items():
if not hasattr(plugin, '__export__'):
continue
for export in plugin.__export__:
if export in self._exports:
- orig = self._exports[export]
- raise KeyError("already exported attribute: %r" % export)
+ raise KeyError("conflicting export: %r" % export)
obj = getattr(plugin, export)
if iscallable(obj):
- # Wrap the function in the CallPrepender object to make
- # the exposed functions act like instance methods.
- obj = CallPrepender(self, obj).call
+ obj = prepender(obj, self)
self._exports[export] = obj
echo("exposed attribute(s): %s" % ', '.join(self._exports.keys()))
# Now call the init function in all plugins.
- for (name, plugin) in self.plugins.items():
+ for (name, plugin) in plugins.items():
try:
plugin.init(self)
@@ -347,82 +386,15 @@ class UzblInstance(object):
raise
- def _init_uzbl_socket(self, uzbl_socket=None, timeout=None):
- '''Store socket location and open socket connection to uzbl socket.'''
-
- if uzbl_socket is None:
- uzbl_socket = self.uzbl_socket
-
- if not uzbl_socket:
- error("no socket location.")
- return
-
- if not os.path.exists(uzbl_socket):
- if timeout is None:
- error("uzbl socket doesn't exist: %r" % uzbl_socket)
- return
-
- waitlimit = time.time() + timeout
- echo("waiting for uzbl socket: %r" % uzbl_socket)
- while not os.path.exists(uzbl_socket):
- time.sleep(0.25)
- if time.time() > waitlimit:
- error("timed out waiting for socket: %r" % uzbl_socket)
- return
-
- self.uzbl_socket = uzbl_socket
- sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- sock.connect(self.uzbl_socket)
- self._socket = sock
-
-
- def _close_socket(self):
- '''Close the socket used for communication with the uzbl instance.
- This function is normally called upon receiving the INSTANCE_EXIT
- event.'''
-
- if self._socket:
- self._socket.close()
-
- self.uzbl_socket = self._socket = None
-
-
- def _flush(self):
- '''Flush messages from the outgoing queue to the uzbl instance.'''
-
- if len(self._fifo_cmd_queue) and self.uzbl_fifo:
- if os.path.exists(self.uzbl_fifo):
- h = open(self.uzbl_fifo, 'w')
- while len(self._fifo_cmd_queue):
- msg = self._fifo_cmd_queue.pop(0)
- print '<-- %s' % msg
- h.write(("%s\n" % msg).encode('utf-8'))
-
- h.close()
-
- if len(self._socket_cmd_queue) and self.uzbl_socket:
- if not self._socket and os.path.exists(self.uzbl_socket):
- self._init_uzbl_socket()
-
- if self._socket:
- while len(self._socket_cmd_queue):
- msg = self._socket_cmd_queue.pop(0)
- print '<-- %s' % msg
- self._socket.send(("%s\n" % msg).encode('utf-8'))
-
-
- def _send_fifo(self, msg):
- '''Send a command to the uzbl instance via the fifo socket.'''
-
- self._fifo_cmd_queue.append(msg)
- self._flush()
-
-
- def _send_socket(self, msg):
+ def send(self, msg):
'''Send a command to the uzbl instance via the socket file.'''
- self._socket_cmd_queue.append(msg)
- self._flush()
+ if self._client_socket:
+ print '<-- %s' % msg
+ self._client_socket.send(("%s\n" % msg).encode('utf-8'))
+
+ else:
+ print '!-- %s' % msg
def connect(self, event, handler, *args, **kargs):
@@ -432,11 +404,9 @@ class UzblInstance(object):
if event not in self._handlers.keys():
self._handlers[event] = []
- handler = Handler(event, handler, *args, **kargs)
- self._handlers[event].append(handler)
-
- print handler
- return handler
+ handlerobj = EventHandler(event, handler, *args, **kargs)
+ self._handlers[event].append(handlerobj)
+ print handlerobj
def connect_dict(self, connect_dict):
@@ -477,216 +447,356 @@ class UzblInstance(object):
echo('unable to find & remove handler: %r' % handler)
- def listen_from_fd(self, fd):
- '''Polls for event messages from fd.'''
+ def exec_handler(self, handler, *args, **kargs):
+ '''Execute event handler function.'''
+
+ args += handler.args
+ kargs = dict(handler.kargs.items()+kargs.items())
+ handler.function(self, *args, **kargs)
+
+
+ def event(self, event, *args, **kargs):
+ '''Raise a custom event.'''
+
+ # Silence _printing_ of geo events while debugging.
+ if event != "GEOMETRY_CHANGED":
+ print "--> %s %s %s" % (event, args, '' if not kargs else kargs)
+
+ if event not in self._handlers:
+ return
+
+ for handler in self._handlers[event]:
+ try:
+ self.exec_handler(handler, *args, **kargs)
+
+ except:
+ print_exc()
+
+
+ def close(self):
+ '''Close the client socket and clean up.'''
try:
- self._running = True
- while self._running:
- if select.select([fd,], [], [], 1)[0]:
- self.read_from_fd(fd)
- continue
+ self._client_socket.close()
- self._flush()
+ except:
+ pass
- except KeyboardInterrupt:
- print
+ for (name, plugin) in self._parent['plugins'].items():
+ if hasattr(plugin, 'cleanup'):
+ plugin.cleanup(self)
+
+ del self._exports
+ del self._handlers
+ del self._client_socket
+
+
+class UzblEventDaemon(dict):
+ def __init__(self):
+
+ # Init variables and dict keys.
+ dict.__init__(self, {'uzbls': {}})
+ self.running = None
+ self.server_socket = None
+ self.socket_location = None
+
+ # Register that the event daemon server has started by creating the
+ # pid file.
+ make_pid_file(config['pid_file'])
+
+ # Register a function to clean up the socket and pid file on exit.
+ atexit.register(self.quit)
+
+ # Make SIGTERM act orderly.
+ signal(SIGTERM, lambda signum, stack_frame: sys.exit(1))
+
+ # Load plugins, first-build of the plugins may be a costly operation.
+ self['plugins'] = load_plugins(config['plugin_dirs'],
+ config['plugins_load'], config['plugins_ignore'])
+
+
+ def _create_server_socket(self):
+ '''Create the event manager daemon socket for uzbl instance duplex
+ communication.'''
+
+ server_socket = config['server_socket']
+ server_socket = os.path.realpath(os.path.expandvars(server_socket))
+ self.socket_location = server_socket
+
+ # Delete socket if it exists.
+ if os.path.exists(server_socket):
+ os.remove(server_socket)
+
+ self.server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.server_socket.bind(server_socket)
+ self.server_socket.listen(5)
+
+
+ def _close_server_socket(self):
+ '''Close and delete the server socket.'''
+
+ try:
+ self.server_socket.close()
+ self.server_socket = None
+
+ if os.path.exists(self.socket_location):
+ os.remove(self.socket_location)
except:
- #print_exc()
- raise
+ pass
- def read_from_fd(self, fd):
- '''Reads event messages from a single fd.'''
+ def run(self):
+ '''Main event daemon loop.'''
- raw = fd.readline()
- if not raw:
- # Read null byte (i.e. uzbl closed).
- self._running = False
- return
+ if config['daemon_mode']:
+ echo('entering daemon mode.')
+ daemonize()
+ # The pid has changed so update the pid file.
+ make_pid_file(config['pid_file'])
- msg = raw.strip().split(' ', 3)
+ # Create event daemon socket.
+ self._create_server_socket()
+ echo('listening on: %s' % self.socket_location)
- if not msg or msg[0] != "EVENT":
- # Not an event message
- print "---", raw.rstrip()
- return
+ # Now listen for incoming connections and or data.
+ self.listen()
- event, args = msg[1], msg[3]
- self.handle_event(event, args)
+ # Clean up.
+ self.quit()
- def listen_from_uzbl_socket(self, uzbl_socket):
- '''Polls for event messages from a single uzbl socket.'''
+ def listen(self):
+ '''Accept incoming connections and constantly poll instance sockets
+ for incoming data.'''
- self._init_uzbl_socket(uzbl_socket, 10)
+ self.running = True
+ while self.running:
- if not self._socket:
- error("failed to init socket: %r" % uzbl_socket)
- return
+ sockets = [self.server_socket,] + self['uzbls'].keys()
+
+ read, _, error = select(sockets, [], sockets, 1)
+
+ if self.server_socket in read:
+ self.accept_connection()
+ read.remove(self.server_socket)
+
+ for client in read:
+ self.read_socket(client)
+
+ for client in error:
+ error('Unknown error on socket: %r' % client)
+ self.close_connection(client)
+
+
+ def read_socket(self, client):
+ '''Read data from an instance socket and pass to the uzbl objects
+ event handler function.'''
- self._flush()
try:
- self._running = True
- while self._running:
- if select.select([self._socket], [], [], 1):
- self.read_from_uzbl_socket()
- continue
+ uzbl = self['uzbls'][client]
+ raw = unicode(client.recv(8192), 'utf-8', 'ignore')
+ if not raw:
+ # Read null byte, close socket.
+ return self.close_connection(client)
- self._flush()
+ uzbl.buffer += raw
+ msgs = uzbl.buffer.split('\n')
+ uzbl.buffer = msgs.pop()
- except KeyboardInterrupt:
- print
+ for msg in msgs:
+ self.parse_msg(uzbl, msg)
except:
- #print_exc()
raise
- def read_from_uzbl_socket(self):
- '''Reads event messages from a uzbl socket.'''
+ def parse_msg(self, uzbl, msg):
+ '''Parse an incoming msg from a uzbl instance. All non-event messages
+ will be printed here and not be passed to the uzbl instance event
+ handler function.'''
- raw = unicode(self._socket.recv(8192), 'utf-8', 'ignore')
- if not raw:
- # Read null byte
- self._running = False
+ msg = msg.strip()
+ if not msg:
return
- self._buffer += raw
- msgs = self._buffer.split("\n")
- self._buffer = msgs.pop()
+ cmd = _RE_FINDSPACES.split(msg, 3)
+ if not cmd or cmd[0] != 'EVENT':
+ # Not an event message.
+ print '---', msg
+ return
- for msg in msgs:
- msg = msg.rstrip()
- if not msg:
- continue
+ if len(cmd) < 4:
+ cmd.append('')
- cmd = _RE_FINDSPACES.split(msg, 3)
- if not cmd or cmd[0] != "EVENT":
- # Not an event message
- print msg.rstrip()
- continue
+ event, args = cmd[2], cmd[3]
- if len(cmd) < 4:
- cmd.append('')
+ try:
+ uzbl.event(event, args)
- event, args = cmd[2], cmd[3]
- try:
- self.handle_event(event, args)
+ except:
+ print_exc()
- except:
- #print_exc()
- raise
+ def accept_connection(self):
+ '''Accept incoming connection to the server socket.'''
- def handle_event(self, event, args):
- '''Handle uzbl events internally before dispatch.'''
+ client_socket = self.server_socket.accept()[0]
- if event == 'FIFO_SET':
- self.uzbl_fifo = args
- self._flush()
+ uzbl = UzblInstance(self, client_socket)
+ self['uzbls'][client_socket] = uzbl
- elif event == 'SOCKET_SET':
- if not self.uzbl_socket or not self._socket:
- self._init_uzbl_socket(args)
- self._flush()
- elif event == 'INSTANCE_EXIT':
- self._close_socket()
- self._running = False
- for (name, plugin) in self.plugins.items():
- if hasattr(plugin, "cleanup"):
- plugin.cleanup(uzbl)
+ def close_connection(self, client):
+ '''Clean up after instance close.'''
- # Now handle the event "publically".
- self.event(event, args)
+ try:
+ if client not in self['uzbls']:
+ return
+ uzbl = self['uzbls'][client]
+ uzbl.close()
+ del self['uzbls'][client]
- def exec_handler(self, handler, *args, **kargs):
- '''Execute either the handler function or send the handlers uzbl
- commands via the socket.'''
+ except:
+ print_exc()
- if handler.callable:
- args = args + handler.args
- kargs = dict(handler.kargs.items()+kargs.items())
- handler.function(uzbl, *args, **kargs)
- else:
- if kargs:
- raise ArgumentError('cannot supply kargs for uzbl commands')
+ def quit(self):
+ '''Close all instance socket objects, server socket and delete the
+ pid file.'''
- for command in handler.commands:
- if '%s' in command:
- if len(args) > 1:
- for arg in args:
- command = command.replace('%s', arg, 1)
+ echo('shutting down event manager.')
- elif len(args) == 1:
- command = command.replace('%s', args[0])
+ for client in self['uzbls'].keys():
+ self.close_connection(client)
- uzbl.send(command)
+ echo('unlinking: %r' % self.socket_location)
+ self._close_server_socket()
+ echo('deleting pid file: %r' % config['pid_file'])
+ del_pid_file(config['pid_file'])
- def event(self, event, *args, **kargs):
- '''Raise a custom event.'''
- # Silence _printing_ of geo events while still debugging.
- if event != "GEOMETRY_CHANGED":
- print "--> %s %s %s" % (event, args, '' if not kargs else kargs)
+def stop():
+ '''Stop the event manager daemon.'''
- if event in self._handlers:
- for handler in self._handlers[event]:
- try:
- self.exec_handler(handler, *args, **kargs)
+ pid_file = config['pid_file']
+ if not os.path.isfile(pid_file):
+ return echo('no running daemon found.')
- except:
- #print_exc()
- raise
+ echo('found pid file: %r' % pid_file)
+ pid = get_pid(pid_file)
+ if not pid_running(pid):
+ echo('no process with pid: %d' % pid)
+ return os.remove(pid_file)
+ echo("terminating process with pid: %d" % pid)
+ term_process(pid)
+ if os.path.isfile(pid_file):
+ os.remove(pid_file)
-if __name__ == "__main__":
- #uzbl = UzblInstance().listen_from_fd(sys.stdin)
+ echo('stopped event daemon.')
+
+
+def start():
+ '''Start the event manager daemon.'''
+
+ pid_file = config['pid_file']
+ if os.path.isfile(pid_file):
+ echo('found pid file: %r' % pid_file)
+ pid = get_pid(pid_file)
+ if pid_running(pid):
+ return echo('event daemon already started with pid: %d' % pid)
+
+ echo('no process with pid: %d' % pid)
+ os.remove(pid_file)
+
+ echo('starting event manager.')
+ UzblEventDaemon().run()
+
+
+def restart():
+ '''Restart the event manager daemon.'''
+
+ echo('restarting event manager daemon.')
+ stop()
+ start()
+
+
+def list_plugins():
+ '''List all the plugins being loaded by the event daemon.'''
- parser = OptionParser()
- parser.add_option('-s', '--uzbl-socket', dest='socket',
- action="store", metavar="SOCKET",
- help="read event messages from uzbl socket.")
+ plugins = find_plugins(config['plugin_dirs'])
+ dirs = {}
+ for (plugin, dir) in plugins.items():
+ if dir not in dirs:
+ dirs[dir] = []
+
+ dirs[dir].append(plugin)
+
+ for (index, (dir, plugin_list)) in enumerate(sorted(dirs.items())):
+ if index:
+ print
+
+ print "%s:" % dir
+ for plugin in sorted(plugin_list):
+ print " %s" % plugin
+
+
+if __name__ == "__main__":
+ usage = "usage: %prog [options] {start|stop|restart|list}"
+ parser = OptionParser(usage=usage)
parser.add_option('-v', '--verbose', dest='verbose', action="store_true",
help="print verbose output.")
- parser.add_option('-d', '--plugin-dir', dest='plugin_dir', action="store",
- metavar="DIR", help="change plugin directory.")
+ parser.add_option('-d', '--plugin-dirs', dest='plugin_dirs', action="store",
+ metavar="DIRS", help="Specify plugin directories in the form of "\
+ "'dir1:dir2:dir3'.")
- parser.add_option('-p', '--load-plugins', dest="load", action="store",
+ parser.add_option('-l', '--load-plugins', dest="load", action="store",
metavar="PLUGINS", help="comma separated list of plugins to load")
parser.add_option('-i', '--ignore-plugins', dest="ignore", action="store",
metavar="PLUGINS", help="comma separated list of plugins to ignore")
- parser.add_option('-l', '--list-plugins', dest='list', action='store_true',
- help="list all the plugins in the plugin dir.")
+ parser.add_option('-p', '--pid-file', dest='pid', action='store',
+ metavar='FILE', help="specify pid file location")
+
+ parser.add_option('-s', '--server-socket', dest='socket', action='store',
+ metavar='SOCKET', help="specify the daemon socket location")
+
+ parser.add_option('-n', '--no-daemon', dest="daemon",
+ action="store_true", help="don't enter daemon mode.")
(options, args) = parser.parse_args()
- if len(args):
- for arg in args:
- error("unknown argument: %r" % arg)
+ # init like {start|stop|..} daemon control section.
+ daemon_controls = {'start': start, 'stop': stop, 'restart': restart,
+ 'list': list_plugins}
+
+ if len(args) == 1:
+ action = args[0]
+ if action not in daemon_controls:
+ error('unknown action: %r' % action)
+ sys.exit(1)
+
+ elif len(args) > 1:
+ error("too many arguments: %r" % args)
+ sys.exit(1)
- raise ArgumentError
+ else:
+ action = 'start'
+ # parse other flags & options.
if options.verbose:
config['verbose'] = True
- if options.plugin_dir:
- plugin_dir = os.path.expandvars(options.plugin_dir)
- if not os.path.isdir(plugin_dir):
- error("%r is not a directory" % plugin_dir)
- sys.exit(1)
-
- config['plugin_dir'] = plugin_dir
- echo("changed plugin dir: %r" % plugin_dir)
+ if options.plugin_dirs:
+ plugin_dirs = map(str.strip, options.plugin_dirs.split(':'))
+ config['plugin_dirs'] = plugin_dirs
+ echo("plugin search dirs: %r" % plugin_dirs)
if options.load and options.ignore:
error("you can't load and ignore at the same time.")
@@ -708,21 +818,16 @@ if __name__ == "__main__":
echo('ignoring plugin(s): %s' % ', '.join(plugins_ignore))
+ if options.pid:
+ config['pid_file'] = options.pid
+ echo("pid file location: %r" % config['pid_file'])
- if options.list:
- plugin_dir = os.path.expandvars(config['plugin_dir'])
- if not os.path.isdir(plugin_dir):
- error("not a directory: %r" % plugin_dir)
- sys.exit(1)
+ if options.socket:
+ config['server_socket'] = options.socket
+ echo("daemon socket location: %s" % config['server_socket'])
- dirlist = filter(lambda p: p.endswith('.py'), os.listdir(plugin_dir))
- print ', '.join([p[:-3] for p in dirlist])
+ if options.daemon:
+ config['daemon_mode'] = False
- else:
- uzbl = UzblInstance()
- if options.socket:
- echo("listen from uzbl socket: %r" % options.socket)
- uzbl.listen_from_uzbl_socket(options.socket)
-
- else:
- uzbl.listen_from_fd(sys.stdin)
+ # Now {start|stop|...}
+ daemon_controls[action]()
diff --git a/examples/data/uzbl/scripts/plugins/bind.py b/examples/data/uzbl/scripts/plugins/bind.py
deleted file mode 100644
index 15f6f8e..0000000
--- a/examples/data/uzbl/scripts/plugins/bind.py
+++ /dev/null
@@ -1,374 +0,0 @@
-'''Plugin provides support for binds in uzbl.
-
-For example:
- event BIND ZZ = exit -> bind('ZZ', 'exit')
- event BIND o _ = uri %s -> bind('o _', 'uri %s')
- event BIND fl* = sh 'echo %s' -> bind('fl*', "sh 'echo %s'")
-
-And it is also possible to execute a function on activation:
- bind('DD', myhandler)
-'''
-
-import sys
-import re
-from event_manager import config, counter, iscallable, isiterable
-
-# Export these variables/functions to uzbl.<name>
-__export__ = ['bind', 'del_bind', 'del_bind_by_glob', 'get_binds']
-
-# Hold the bind lists per uzbl instance.
-UZBLS = {}
-
-# Commonly used regular expressions.
-starts_with_mod = re.compile('^<([A-Z][A-Za-z0-9-_]+)>')
-find_prompts = re.compile('<([^:>]*):>').split
-
-# For accessing a bind glob stack.
-MOD_CMD, ON_EXEC, HAS_ARGS, GLOB = range(4)
-
-
-class BindParseError(Exception):
- pass
-
-
-def echo(msg):
- if config['verbose']:
- print 'bind plugin:', msg
-
-
-def error(msg):
- sys.stderr.write('bind plugin: error: %s\n' % msg)
-
-
-def ismodbind(glob):
- '''Return True if the glob specifies a modbind.'''
-
- return bool(starts_with_mod.match(glob))
-
-
-def sort_mods(glob):
- '''Mods are sorted in the keylet.to_string() result so make sure that
- bind commands also have their mod keys sorted.'''
-
- mods = []
- while True:
- match = starts_with_mod.match(glob)
- if not match:
- break
-
- end = match.span()[1]
- mods.append(glob[:end])
- glob = glob[end:]
-
- return '%s%s' % (''.join(sorted(mods)), glob)
-
-
-def add_instance(uzbl, *args):
- UZBLS[uzbl] = {'binds': [], 'depth': 0, 'filter': [],
- 'args': [], 'last_mode': ''}
-
-
-def del_instance(uzbl, *args):
- if uzbl in UZBLS:
- del UZBLS[uzbl]
-
-
-def get_bind_dict(uzbl):
- '''Return the bind dict for the uzbl instance.'''
-
- if uzbl not in UZBLS:
- add_instance(uzbl)
-
- return UZBLS[uzbl]
-
-
-def get_binds(uzbl):
- '''Return the bind list for the uzbl instance.'''
-
- return get_bind_dict(uzbl)['binds']
-
-
-def get_stack_depth(uzbl):
- '''Return the stack for the uzbl instance.'''
-
- return get_bind_dict(uzbl)['depth']
-
-
-def get_filtered_binds(uzbl):
- '''Return the bind list for the uzbl instance or return the filtered
- bind list thats on the current stack.'''
-
- bind_dict = get_bind_dict(uzbl)
- if bind_dict['depth']:
- return list(bind_dict['filter'])
-
- return list(bind_dict['binds'])
-
-
-def del_bind(uzbl, bind):
- '''Delete bind object if bind in the uzbl binds.'''
-
- binds = get_binds(uzbl)
- if bind in binds:
- binds.remove(bind)
- uzbl.event('DELETED_BIND', bind)
- return True
-
- return False
-
-
-def del_bind_by_glob(uzbl, glob):
- '''Delete bind by glob if bind in the uzbl binds.'''
-
- binds = get_binds(uzbl)
- for bind in list(binds):
- if bind.glob == glob:
- binds.remove(bind)
- uzbl.event('DELETED_BIND', bind)
- return True
-
- return False
-
-
-class Bind(object):
-
- nextbid = counter().next
-
- def __init__(self, glob, handler, *args, **kargs):
- self.callable = iscallable(handler)
-
- if not glob:
- raise ArgumentError('glob cannot be blank')
-
- if self.callable:
- self.function = handler
- self.args = args
- self.kargs = kargs
-
- elif kargs:
- raise ArgumentError('cannot supply kargs for uzbl commands')
-
- elif isiterable(handler):
- self.commands = handler
-
- else:
- self.commands = [handler,] + list(args)
-
- self.glob = glob
- self.bid = self.nextbid()
-
- self.split = split = find_prompts(glob)
- self.prompts = split[1::2]
-
- # Check that there is nothing like: fl*<int:>*
- for glob in split[:-1:2]:
- if glob.endswith('*'):
- msg = "token '*' not at the end of a prompt bind: %r" % split
- raise BindParseError(msg)
-
- # Check that there is nothing like: fl<prompt1:><prompt2:>_
- for glob in split[2::2]:
- if not glob:
- msg = 'found null segment after first prompt: %r' % split
- raise BindParseError(msg)
-
- self.stack = []
-
- for glob in split[::2]:
- # Is the binding a MODCMD or KEYCMD:
- mod_cmd = ismodbind(glob)
-
- # Execute the command on UPDATES or EXEC's:
- on_exec = True if glob.endswith('_') else False
-
- # Does the command store arguments:
- has_args = True if glob[-1] in ['*', '_'] else False
- glob = glob[:-1] if has_args else glob
-
- self.stack.append((mod_cmd, on_exec, has_args, glob))
-
-
- def __repr__(self):
- args = ['glob=%r' % self.glob, 'bid=%d' % self.bid]
-
- if self.callable:
- args.append('function=%r' % self.function)
- if self.args:
- args.append('args=%r' % self.args)
-
- if self.kargs:
- args.append('kargs=%r' % self.kargs)
-
- else:
- cmdlen = len(self.commands)
- cmds = self.commands[0] if cmdlen == 1 else self.commands
- args.append('command%s=%r' % ('s' if cmdlen-1 else '', cmds))
-
- return '<Bind(%s)>' % ', '.join(args)
-
-
-def bind(uzbl, glob, handler, *args, **kargs):
- '''Add a bind handler object.'''
-
- # Mods come from the keycmd sorted so make sure the modkeys in the bind
- # command are sorted too.
- glob = sort_mods(glob)
-
- del_bind_by_glob(uzbl, glob)
- binds = get_binds(uzbl)
-
- bind = Bind(glob, handler, *args, **kargs)
- binds.append(bind)
-
- print bind
- uzbl.event('ADDED_BIND', bind)
-
-
-def parse_bind_event(uzbl, args):
- '''Break "event BIND fl* = js follownums.js" into (glob, command).'''
-
- if not args:
- return error('missing bind arguments')
-
- split = map(unicode.strip, args.split('=', 1))
- if len(split) != 2:
- return error('missing "=" in bind definition: %r' % args)
-
- glob, command = split
- bind(uzbl, glob, command)
-
-
-def set_stack_mode(uzbl, prompt):
- if uzbl.get_mode() != 'stack':
- uzbl.set_mode('stack')
-
- if prompt:
- prompt = "%s: " % prompt
-
- uzbl.set('keycmd_prompt', prompt)
-
-
-def clear_stack(uzbl, mode):
- bind_dict = get_bind_dict(uzbl)
- if mode != "stack" and bind_dict['last_mode'] == "stack":
- uzbl.set('keycmd_prompt', '')
-
- if mode != "stack":
- bind_dict = get_bind_dict(uzbl)
- bind_dict['filter'] = []
- bind_dict['depth'] = 0
- bind_dict['args'] = []
-
- bind_dict['last_mode'] = mode
-
-
-def filter_bind(uzbl, bind_dict, bind):
- '''Remove a bind from the stack filter list.'''
-
- if bind in bind_dict['filter']:
- bind_dict['filter'].remove(bind)
-
- if not bind_dict['filter']:
- uzbl.set_mode()
-
-
-def match_and_exec(uzbl, bind, depth, keycmd):
- bind_dict = get_bind_dict(uzbl)
- mode_cmd, on_exec, has_args, glob = bind.stack[depth]
-
- if has_args:
- if not keycmd.startswith(glob):
- filter_bind(uzbl, bind_dict, bind)
- return False
-
- args = [keycmd[len(glob):],]
-
- elif keycmd != glob:
- filter_bind(uzbl, bind_dict, bind)
- return False
-
- else:
- args = []
-
- execindex = len(bind.stack)-1
- if execindex == depth == 0:
- uzbl.exec_handler(bind, *args)
- if not has_args:
- uzbl.clear_keycmd()
-
- return True
-
- elif depth != execindex:
- if bind_dict['depth'] == depth:
- bind_dict['filter'] = [bind,]
- bind_dict['args'] += args
- bind_dict['depth'] = depth + 1
-
- else:
- if bind not in bind_dict['filter']:
- bind_dict['filter'].append(bind)
-
- set_stack_mode(uzbl, bind.prompts[depth])
- return False
-
- args = bind_dict['args'] + args
- uzbl.exec_handler(bind, *args)
- if on_exec:
- uzbl.set_mode()
-
- return True
-
-
-def keycmd_update(uzbl, keylet):
- depth = get_stack_depth(uzbl)
- keycmd = keylet.to_string()
- for bind in get_filtered_binds(uzbl):
- t = bind.stack[depth]
- if t[MOD_CMD] or t[ON_EXEC]:
- continue
-
- match_and_exec(uzbl, bind, depth, keycmd)
-
-
-def keycmd_exec(uzbl, keylet):
- depth = get_stack_depth(uzbl)
- keycmd = keylet.to_string()
- for bind in get_filtered_binds(uzbl):
- t = bind.stack[depth]
- if t[MOD_CMD] or not t[ON_EXEC]:
- continue
-
- match_and_exec(uzbl, bind, depth, keycmd)
-
-
-def modcmd_update(uzbl, keylet):
- depth = get_stack_depth(uzbl)
- keycmd = keylet.to_string()
- for bind in get_filtered_binds(uzbl):
- t = bind.stack[depth]
- if not t[MOD_CMD] or t[ON_EXEC]:
- continue
-
- match_and_exec(uzbl, bind, depth, keycmd)
-
-
-def modcmd_exec(uzbl, keylet):
- depth = get_stack_depth(uzbl)
- keycmd = keylet.to_string()
- for bind in get_filtered_binds(uzbl):
- t = bind.stack[depth]
- if not t[MOD_CMD] or not t[ON_EXEC]:
- continue
-
- match_and_exec(uzbl, bind, depth, keycmd)
-
-
-def init(uzbl):
- connects = {'BIND': parse_bind_event,
- 'KEYCMD_UPDATE': keycmd_update,
- 'MODCMD_UPDATE': modcmd_update,
- 'KEYCMD_EXEC': keycmd_exec,
- 'MODCMD_EXEC': modcmd_exec,
- 'MODE_CHANGED': clear_stack}
-
- uzbl.connect_dict(connects)
diff --git a/examples/data/uzbl/scripts/plugins/config.py b/examples/data/uzbl/scripts/plugins/config.py
deleted file mode 100644
index 22803b4..0000000
--- a/examples/data/uzbl/scripts/plugins/config.py
+++ /dev/null
@@ -1,87 +0,0 @@
-import re
-import types
-
-__export__ = ['set', 'get_config']
-
-_VALIDSETKEY = re.compile("^[a-zA-Z][a-zA-Z0-9_]*$").match
-_TYPECONVERT = {'int': int, 'float': float, 'str': unicode}
-
-UZBLS = {}
-
-
-def escape(value):
- '''A real escaping function may be required.'''
-
- return unicode(value)
-
-
-def get_config(uzbl):
- if uzbl not in UZBLS:
- add_instance(uzbl)
-
- return UZBLS[uzbl]
-
-
-def set(uzbl, key, value):
- '''Sends a: "set key = value" command to the uzbl instance.'''
-
- if type(value) == types.BooleanType:
- value = int(value)
-
- if not _VALIDSETKEY(key):
- raise KeyError("%r" % key)
-
- value = escape(value)
- if '\n' in value:
- value = value.replace("\n", "\\n")
-
- uzbl.send('set %s = %s' % (key, value))
-
-
-def add_instance(uzbl, *args):
- UZBLS[uzbl] = ConfigDict(uzbl)
-
-
-def del_instance(uzbl, *args):
- if uzbl in UZBLS:
- del uzbl
-
-
-def get_config(uzbl):
- if uzbl not in UZBLS:
- add_instance(uzbl)
-
- return UZBLS[uzbl]
-
-
-class ConfigDict(dict):
- def __init__(self, uzbl):
- self._uzbl = uzbl
-
- def __setitem__(self, key, value):
- '''Makes "config[key] = value" a wrapper for the set function.'''
-
- if key not in self or unicode(self[key]) != unicode(value):
- set(self._uzbl, key, value)
-
-
-def variable_set(uzbl, args):
- config = get_config(uzbl)
-
- key, type, value = list(args.split(' ', 2) + ['',])[:3]
- old = config[key] if key in config else None
- value = _TYPECONVERT[type](value)
-
- dict.__setitem__(config, key, value)
-
- if old != value:
- uzbl.event("CONFIG_CHANGED", key, value)
-
-
-def init(uzbl):
-
- connects = {'VARIABLE_SET': variable_set,
- 'INSTANCE_START': add_instance,
- 'INSTANCE_EXIT': del_instance}
-
- uzbl.connect_dict(connects)
diff --git a/examples/data/uzbl/scripts/plugins/keycmd.py b/examples/data/uzbl/scripts/plugins/keycmd.py
deleted file mode 100644
index 3dd6f37..0000000
--- a/examples/data/uzbl/scripts/plugins/keycmd.py
+++ /dev/null
@@ -1,382 +0,0 @@
-import re
-
-# Map these functions/variables in the plugins namespace to the uzbl object.
-__export__ = ['clear_keycmd', 'set_keycmd', 'set_cursor_pos', 'get_keylet']
-
-# Regular expression compile cache.
-_RE_CACHE = {}
-
-# Hold the keylets.
-UZBLS = {}
-
-# Simple key names map.
-_SIMPLEKEYS = {
- 'Control': 'Ctrl',
- 'ISO_Left_Tab': 'Shift-Tab',
- 'space':'Space',
-}
-
-# Keycmd format which includes the markup for the cursor.
-KEYCMD_FORMAT = "%s<span @cursor_style>%s</span>%s"
-
-
-def escape(str):
- '''Prevent outgoing keycmd values from expanding inside the
- status_format.'''
-
- if not str:
- return ''
-
- for char in ['\\', '@']:
- if char in str:
- str = str.replace(char, '\\'+char)
-
- return "@[%s]@" % str
-
-
-def get_regex(regex):
- '''Compiling regular expressions is a very time consuming so return a
- pre-compiled regex match object if possible.'''
-
- if regex not in _RE_CACHE:
- _RE_CACHE[regex] = re.compile(regex).match
-
- return _RE_CACHE[regex]
-
-
-class Keylet(object):
- '''Small per-instance object that tracks all the keys held and characters
- typed.'''
-
- def __init__(self):
- self.cmd = ''
- self.cursor = 0
- self.held = []
-
- # to_string() string building cache.
- self._to_string = None
-
- self.modcmd = False
- self.wasmod = False
-
- def __repr__(self):
- return '<Keycmd(%r)>' % self.to_string()
-
-
- def to_string(self):
- '''Return a string representation of the keys held and pressed that
- have been recorded.'''
-
- if self._to_string is not None:
- # Return cached keycmd string.
- return self._to_string
-
- if not self.held:
- self._to_string = self.cmd
-
- else:
- self._to_string = ''.join(['<%s>' % key for key in self.held])
- if self.cmd:
- self._to_string += self.cmd
-
- return self._to_string
-
-
- def match(self, regex):
- '''See if the keycmd string matches the given regex.'''
-
- return bool(get_regex(regex)(self.to_string()))
-
-
-def make_simple(key):
- '''Make some obscure names for some keys friendlier.'''
-
- # Remove left-right discrimination.
- if key.endswith('_L') or key.endswith('_R'):
- key = key[:-2]
-
- if key in _SIMPLEKEYS:
- key = _SIMPLEKEYS[key]
-
- return key
-
-
-def add_instance(uzbl, *args):
- '''Create the Keylet object for this uzbl instance.'''
-
- UZBLS[uzbl] = Keylet()
-
-
-def del_instance(uzbl, *args):
- '''Delete the Keylet object for this uzbl instance.'''
-
- if uzbl in UZBLS:
- del UZBLS[uzbl]
-
-
-def get_keylet(uzbl):
- '''Return the corresponding keylet for this uzbl instance.'''
-
- # Startup events are not correctly captured and sent over the uzbl socket
- # yet so this line is needed because the INSTANCE_START event is lost.
- if uzbl not in UZBLS:
- add_instance(uzbl)
-
- keylet = UZBLS[uzbl]
- keylet._to_string = None
- return keylet
-
-
-def clear_keycmd(uzbl):
- '''Clear the keycmd for this uzbl instance.'''
-
- k = get_keylet(uzbl)
- k.cmd = ''
- k.cursor = 0
- k._to_string = None
-
- if k.modcmd:
- k.wasmod = True
-
- k.modcmd = False
- config = uzbl.get_config()
- if 'keycmd' not in config or config['keycmd'] != '':
- config['keycmd'] = ''
-
- uzbl.event('KEYCMD_CLEAR')
-
-
-def update_event(uzbl, k):
- '''Raise keycmd & modcmd update events.'''
-
- config = uzbl.get_config()
- if k.modcmd:
- keycmd = k.to_string()
- uzbl.event('MODCMD_UPDATE', k)
- if keycmd != k.to_string():
- return
-
- if 'modcmd_updates' in config and config['modcmd_updates'] != '1':
- return
-
- return uzbl.set('keycmd', escape(keycmd))
-
- if 'keycmd_events' in config and config['keycmd_events'] != '1':
- return
-
- keycmd = k.cmd
- uzbl.event('KEYCMD_UPDATE', k)
- if keycmd != k.cmd:
- return
-
- if not k.cmd:
- return uzbl.set('keycmd', '')
-
- # Generate the pango markup for the cursor in the keycmd.
- if k.cursor < len(k.cmd):
- cursor = k.cmd[k.cursor]
-
- else:
- cursor = ' '
-
- chunks = map(escape, [k.cmd[:k.cursor], cursor, k.cmd[k.cursor+1:]])
- uzbl.set('keycmd', KEYCMD_FORMAT % tuple(chunks))
-
-
-def key_press(uzbl, key):
- '''Handle KEY_PRESS events. Things done by this function include:
-
- 1. Ignore all shift key presses (shift can be detected by capital chars)
- 2. Re-enable modcmd var if the user presses another key with at least one
- modkey still held from the previous modcmd (I.e. <Ctrl>+t, clear &
- <Ctrl>+o without having to re-press <Ctrl>)
- 3. In non-modcmd mode:
- a. BackSpace deletes the character before the cursor position.
- b. Delete deletes the character at the cursor position.
- c. End moves the cursor to the end of the keycmd.
- d. Home moves the cursor to the beginning of the keycmd.
- e. Return raises a KEYCMD_EXEC event then clears the keycmd.
- f. Escape clears the keycmd.
- 4. If keycmd and held keys are both empty/null and a modkey was pressed
- set modcmd mode.
- 5. If in modcmd mode only mod keys are added to the held keys list.
- 6. Keycmd is updated and events raised if anything is changed.'''
-
- if key.startswith('Shift_'):
- return
-
- if len(key) > 1:
- key = make_simple(key)
-
- k = get_keylet(uzbl)
- cmdmod = False
- if k.held and k.wasmod:
- k.modcmd = True
- k.wasmod = False
- cmdmod = True
-
- if k.cmd and key == 'Space':
- k.cmd = "%s %s" % (k.cmd[:k.cursor], k.cmd[k.cursor:])
- k.cursor += 1
- cmdmod = True
-
- elif not k.modcmd and k.cmd and key in ['BackSpace', 'Delete']:
- if key == 'BackSpace' and k.cursor > 0:
- k.cursor -= 1
- k.cmd = k.cmd[:k.cursor] + k.cmd[k.cursor+1:]
-
- elif key == 'Delete':
- cmd = k.cmd
- k.cmd = k.cmd[:k.cursor] + k.cmd[k.cursor+1:]
- if k.cmd != cmd:
- cmdmod = True
-
- if not k.cmd:
- clear_keycmd(uzbl)
-
- elif key == 'BackSpace':
- cmdmod = True
-
- elif not k.modcmd and key == 'Return':
- if k.cmd:
- uzbl.event('KEYCMD_EXEC', k)
-
- clear_keycmd(uzbl)
-
- elif not k.modcmd and key == 'Escape':
- clear_keycmd(uzbl)
-
- elif not k.modcmd and k.cmd and key == 'Left':
- if k.cursor > 0:
- k.cursor -= 1
- cmdmod = True
-
- elif not k.modcmd and k.cmd and key == 'Right':
- if k.cursor < len(k.cmd):
- k.cursor += 1
- cmdmod = True
-
- elif not k.modcmd and k.cmd and key == 'End':
- if k.cursor != len(k.cmd):
- k.cursor = len(k.cmd)
- cmdmod = True
-
- elif not k.modcmd and k.cmd and key == 'Home':
- if k.cursor:
- k.cursor = 0
- cmdmod = True
-
- elif not k.held and not k.cmd and len(key) > 1:
- k.modcmd = True
- k.held.append(key)
- cmdmod = True
-
- elif k.modcmd:
- cmdmod = True
- if len(key) > 1:
- if key == 'Shift-Tab' and 'Tab' in k.held:
- k.held.remove('Tab')
-
- if key not in k.held:
- k.held.append(key)
- k.held.sort()
-
- else:
- k.cmd = "%s%s%s" % (k.cmd[:k.cursor], key, k.cmd[k.cursor:])
- k.cursor += 1
-
- else:
- config = uzbl.get_config()
- if 'keycmd_events' not in config or config['keycmd_events'] == '1':
- if len(key) == 1:
- cmdmod = True
- k.cmd = "%s%s%s" % (k.cmd[:k.cursor], key, k.cmd[k.cursor:])
- k.cursor += 1
-
- elif k.cmd:
- cmdmod = True
- k.cmd = ''
- k.cursor = 0
-
- if cmdmod:
- update_event(uzbl, k)
-
-
-def key_release(uzbl, key):
- '''Respond to KEY_RELEASE event. Things done by this function include:
-
- 1. Remove the key from the keylet held list.
- 2. If the key removed was a mod key and it was in a mod-command then
- raise a MODCMD_EXEC event then clear the keycmd.
- 3. Stop trying to restore mod-command status with wasmod if both the
- keycmd and held list are empty/null.
- 4. Update the keycmd uzbl variable if anything changed.'''
-
- if len(key) > 1:
- key = make_simple(key)
-
- k = get_keylet(uzbl)
-
- cmdmod = False
- if key in ['Shift', 'Tab'] and 'Shift-Tab' in k.held:
- key = 'Shift-Tab'
-
- elif key in ['Shift', 'Alt'] and 'Meta' in k.held:
- key = 'Meta'
-
- if key in k.held:
- if k.modcmd:
- uzbl.event('MODCMD_EXEC', k)
-
- k.held.remove(key)
- clear_keycmd(uzbl)
-
- if not k.held and not k.cmd and k.wasmod:
- k.wasmod = False
-
- if cmdmod:
- update_event(uzbl, k)
-
-
-def set_keycmd(uzbl, keycmd):
- '''Allow setting of the keycmd externally.'''
-
- k = get_keylet(uzbl)
- k.wasmod = k.modcmd = False
- k._to_string = None
- k.cmd = keycmd
- k.cursor = len(keycmd)
- update_event(uzbl, k)
-
-
-def set_cursor_pos(uzbl, index):
- '''Allow setting of the cursor position externally. Supports negative
- indexing.'''
-
- cursor = int(index.strip())
- k = get_keylet(uzbl)
-
- if cursor < 0:
- cursor = len(k.cmd) + cursor
-
- if cursor < 0:
- cursor = 0
-
- if cursor > len(k.cmd):
- cursor = len(k.cmd)
-
- k.cursor = cursor
- update_event(uzbl, k)
-
-
-def init(uzbl):
- '''Connect handlers to uzbl events.'''
-
- connects = {'INSTANCE_START': add_instance,
- 'INSTANCE_EXIT': del_instance,
- 'KEY_PRESS': key_press,
- 'KEY_RELEASE': key_release,
- 'SET_KEYCMD': set_keycmd,
- 'SET_CURSOR_POS': set_cursor_pos}
-
- uzbl.connect_dict(connects)
diff --git a/examples/data/uzbl/scripts/plugins/mode.py b/examples/data/uzbl/scripts/plugins/mode.py
deleted file mode 100644
index ad0d9a8..0000000
--- a/examples/data/uzbl/scripts/plugins/mode.py
+++ /dev/null
@@ -1,159 +0,0 @@
-import sys
-import re
-
-__export__ = ['set_mode', 'get_mode']
-
-UZBLS = {}
-
-DEFAULTS = {
- 'mode': '',
- 'default': '',
- 'modes': {
- 'insert': {
- 'forward_keys': True,
- 'keycmd_events': False,
- 'modcmd_updates': False,
- 'indicator': 'I'},
- 'command': {
- 'forward_keys': False,
- 'keycmd_events': True,
- 'modcmd_updates': True,
- 'indicator': 'C'}}}
-
-_RE_FINDSPACES = re.compile("\s+")
-
-
-def error(msg):
- sys.stderr.write("mode plugin: error: %s\n" % msg)
-
-
-def add_instance(uzbl, *args):
- UZBLS[uzbl] = dict(DEFAULTS)
-
-
-def del_instance(uzbl, *args):
- if uzbl in UZBLS:
- del UZBLS[uzbl]
-
-
-def get_mode_dict(uzbl):
- if uzbl not in UZBLS:
- add_instance(uzbl)
-
- return UZBLS[uzbl]
-
-
-def get_mode_config(uzbl, mode):
- modes = get_mode_dict(uzbl)['modes']
- if mode not in modes:
- modes[mode] = {}
-
- return modes[mode]
-
-
-def get_mode(uzbl):
- return get_mode_dict(uzbl)['mode']
-
-
-def key_press(uzbl, key):
- if key != "Escape":
- return
-
- set_mode(uzbl)
-
-
-def set_mode(uzbl, mode=None):
- mode_dict = get_mode_dict(uzbl)
- if mode is None:
- if not mode_dict['default']:
- return error("no default mode to fallback on")
-
- mode = mode_dict['default']
-
- config = uzbl.get_config()
- if 'mode' not in config or config['mode'] != mode:
- config['mode'] = mode
-
- mode_dict['mode'] = mode
- mode_config = get_mode_config(uzbl, mode)
-
- for (key, value) in mode_config.items():
- if key not in config:
- config[key] = value
-
- elif config[key] != value:
- config[key] = value
-
- if 'mode_indicator' not in mode_config:
- config['mode_indicator'] = mode
-
- uzbl.clear_keycmd()
- uzbl.send('update_gui')
- uzbl.event("MODE_CHANGED", mode)
-
-
-def config_changed(uzbl, key, value):
- if key == 'default_mode':
- mode_dict = get_mode_dict(uzbl)
- mode_dict['default'] = value
- if value and not mode_dict['mode']:
- set_mode(uzbl, value)
-
- elif key == 'mode':
- if not value:
- value = None
-
- set_mode(uzbl, value)
-
-
-def mode_config(uzbl, args):
-
- split = map(unicode.strip, _RE_FINDSPACES.split(args.lstrip(), 1))
- if len(split) != 2:
- return error("invalid MODE_CONFIG syntax: %r" % args)
-
- mode, set = split
- split = map(unicode.strip, set.split('=', 1))
- if len(split) != 2:
- return error("invalid MODE_CONFIG set command: %r" % args)
-
- key, value = split
- mode_config = get_mode_config(uzbl, mode)
- mode_config[key] = value
-
- if get_mode(uzbl) == mode:
- uzbl.set(key, value)
-
-
-def load_reset(uzbl, *args):
- config = uzbl.get_config()
- if 'reset_on_commit' not in config or config['reset_on_commit'] == '1':
- set_mode(uzbl)
-
-
-def toggle_modes(uzbl, modes):
-
- modelist = [s.strip() for s in modes.split(' ') if s]
- if not len(modelist):
- return error("no modes specified to toggle")
-
- mode_dict = get_mode_dict(uzbl)
- oldmode = mode_dict['mode']
- if oldmode not in modelist:
- return set_mode(uzbl, modelist[0])
-
- newmode = modelist[(modelist.index(oldmode)+1) % len(modelist)]
- set_mode(uzbl, newmode)
-
-
-def init(uzbl):
-
- connects = {'CONFIG_CHANGED': config_changed,
- 'INSTANCE_EXIT': del_instance,
- 'INSTANCE_START': add_instance,
- 'KEY_PRESS': key_press,
- 'MODE_CONFIG': mode_config,
- 'LOAD_START': load_reset,
- 'TOGGLE_MODES': toggle_modes}
-
- uzbl.connect_dict(connects)
diff --git a/examples/data/uzbl/scripts/plugins/on_event.py b/examples/data/uzbl/scripts/plugins/on_event.py
deleted file mode 100644
index 242f9b0..0000000
--- a/examples/data/uzbl/scripts/plugins/on_event.py
+++ /dev/null
@@ -1,117 +0,0 @@
-'''Plugin provides arbitrary binding of uzbl events to uzbl commands.
-
-Formatting options:
- %@ = space separated string of the arguments
- %1 = argument 1
- %2 = argument 2
- %n = argument n
-
-Usage:
- request ON_EVENT LINK_HOVER set selected_uri = $1
- --> LINK_HOVER http://uzbl.org/
- <-- set selected_uri = http://uzbl.org/
-
- request ON_EVENT CONFIG_CHANGED print Config changed: %1 = %2
- --> CONFIG_CHANGED selected_uri http://uzbl.org/
- <-- print Config changed: selected_uri = http://uzbl.org/
-'''
-
-import sys
-import re
-from event_manager import config
-
-__export__ = ['get_on_events', 'on_event']
-
-UZBLS = {}
-
-def echo(msg):
- if config['verbose']:
- print 'on_event plugin:', msg
-
-
-def error(msg):
- sys.stderr.write('on_event plugin: error: %s\n' % msg)
-
-
-def add_instance(uzbl, *args):
- UZBLS[uzbl] = {}
-
-
-def del_instance(uzbl, *args):
- if uzbl in UZBLS:
- del UZBLS[uzbl]
-
-
-def get_on_events(uzbl):
- if uzbl not in UZBLS:
- add_instance(uzbl)
-
- return UZBLS[uzbl]
-
-
-def expand(cmd, args):
- '''Replaces "%@ %1 %2 %3..." with "<all args> <arg 0> <arg 1>...".'''
-
- if '%@' in cmd:
- cmd = cmd.replace('%@', ' '.join(map(unicode, args)))
-
- for (index, arg) in enumerate(args):
- index += 1
- if '%%%d' % index in cmd:
- cmd = cmd.replace('%%%d' % index, unicode(arg))
-
- return cmd
-
-
-def event_handler(uzbl, *args, **kargs):
- '''This function handles all the events being watched by various
- on_event definitions and responds accordingly.'''
-
- events = get_on_events(uzbl)
- event = kargs['on_event']
- if event not in events:
- return
-
- commands = events[event]
- for cmd in commands:
- cmd = expand(cmd, args)
- uzbl.send(cmd)
-
-
-def on_event(uzbl, event, cmd):
- '''Add a new event to watch and respond to.'''
-
- event = event.upper()
- events = get_on_events(uzbl)
- if event not in events:
- uzbl.connect(event, event_handler, on_event=event)
- events[event] = []
-
- cmds = events[event]
- if cmd not in cmds:
- cmds.append(cmd)
-
-
-def parse_on_event(uzbl, args):
- '''Parse ON_EVENT events and pass them to the on_event function.
-
- Syntax: "event ON_EVENT <EVENT_NAME> commands".'''
-
- if not args:
- return error("missing on_event arguments")
-
- split = args.split(' ', 1)
- if len(split) != 2:
- return error("invalid ON_EVENT syntax: %r" % args)
-
- event, cmd = split
- on_event(uzbl, event, cmd)
-
-
-def init(uzbl):
-
- connects = {'ON_EVENT': parse_on_event,
- 'INSTANCE_START': add_instance,
- 'INSTANCE_EXIT': del_instance}
-
- uzbl.connect_dict(connects)
diff --git a/examples/data/uzbl/scripts/plugins/plugin_template.py b/examples/data/uzbl/scripts/plugins/plugin_template.py
deleted file mode 100644
index 03cb748..0000000
--- a/examples/data/uzbl/scripts/plugins/plugin_template.py
+++ /dev/null
@@ -1,75 +0,0 @@
-'''Plugin template.'''
-
-# A list of functions this plugin exports to be used via uzbl object.
-__export__ = ['myplugin_function',]
-
-# Holds the per-instance data dict.
-UZBLS = {}
-
-# The default instance dict.
-DEFAULTS = {}
-
-
-def add_instance(uzbl, *args):
- '''Add a new instance with default config options.'''
-
- UZBLS[uzbl] = dict(DEFAULTS)
-
-
-def del_instance(uzbl, *args):
- '''Delete data stored for an instance.'''
-
- if uzbl in UZBLS:
- del UZBLS[uzbl]
-
-
-def get_myplugin_dict(uzbl):
- '''Get data stored for an instance.'''
-
- if uzbl not in UZBLS:
- add_instance(uzbl)
-
- return UZBLS[uzbl]
-
-
-def myplugin_function(uzbl, *args, **kargs):
- '''Custom plugin function which is exported by the __export__ list at the
- top of the file for use by other functions/callbacks.'''
-
- print "My plugin function arguments:", args, kargs
-
- # Get the per-instance data object.
- data = get_myplugin_dict(uzbl)
-
- # Function logic goes here.
-
-
-def myplugin_event_parser(uzbl, args):
- '''Parses MYPLUGIN_EVENT raised by uzbl or another plugin.'''
-
- print "Got MYPLUGIN_EVENT with arguments: %r" % args
-
- # Parsing logic goes here.
-
-
-def init(uzbl):
- '''The main function of the plugin which is used to attach all the event
- hooks that are going to be used throughout the plugins life. This function
- is called each time a UzblInstance() object is created in the event
- manager.'''
-
- # Make a dictionary comprising of {"EVENT_NAME": handler, ..} to the event
- # handler stack:
- connects = {
- 'INSTANCE_START': add_instance,
- 'INSTANCE_EXIT': del_instance,
- 'MYPLUGIN_EVENT': myplugin_event_parser,
- }
-
- # And connect the dicts event handlers to the handler stack.
- uzbl.connect_dict(connects)
-
- # Or connect a handler to an event manually and supply additional optional
- # arguments:
-
- #uzbl.connect("MYOTHER_EVENT", myother_event_parser, True, limit=20)
diff --git a/examples/data/uzbl/scripts/plugins/progress_bar.py b/examples/data/uzbl/scripts/plugins/progress_bar.py
deleted file mode 100644
index b6fcb1b..0000000
--- a/examples/data/uzbl/scripts/plugins/progress_bar.py
+++ /dev/null
@@ -1,158 +0,0 @@
-import sys
-
-UZBLS = {}
-
-DEFAULTS = {'width': 8,
- 'done': '=',
- 'pending': '.',
- 'format': '[%d%a%p]%c',
- 'spinner': '-\\|/',
- 'sprites': 'loading',
- 'updates': 0,
- 'progress': 100}
-
-
-def error(msg):
- sys.stderr.write("progress_bar plugin: error: %s\n" % msg)
-
-
-def add_instance(uzbl, *args):
- UZBLS[uzbl] = dict(DEFAULTS)
-
-
-def del_instance(uzbl, *args):
- if uzbl in UZBLS:
- del UZBLS[uzbl]
-
-
-def get_progress_config(uzbl):
- if uzbl not in UZBLS:
- add_instance(uzbl)
-
- return UZBLS[uzbl]
-
-
-def update_progress(uzbl, prog=None):
- '''Updates the progress_format variable on LOAD_PROGRESS update.
-
- The current substitution options are:
- %d = done char * done
- %p = pending char * remaining
- %c = percent done
- %i = int done
- %s = -\|/ spinner
- %t = percent pending
- %o = int pending
- %r = sprites
- '''
-
- prog_config = get_progress_config(uzbl)
- config = uzbl.get_config()
-
- if prog is None:
- prog = prog_config['progress']
-
- else:
- prog = int(prog)
- prog_config['progress'] = prog
-
- prog_config['updates'] += 1
- format = prog_config['format']
- width = prog_config['width']
-
- # Inflate the done and pending bars to stop the progress bar
- # jumping around.
- if '%c' in format or '%i' in format:
- count = format.count('%c') + format.count('%i')
- width += (3-len(str(prog))) * count
-
- if '%t' in format or '%o' in format:
- count = format.count('%t') + format.count('%o')
- width += (3-len(str(100-prog))) * count
-
- done = int(((prog/100.0)*width)+0.5)
- pending = width - done
-
- if '%d' in format:
- format = format.replace('%d', prog_config['done']*done)
-
- if '%p' in format:
- format = format.replace('%p', prog_config['pending']*pending)
-
- if '%c' in format:
- format = format.replace('%c', '%d%%' % prog)
-
- if '%i' in format:
- format = format.replace('%i', '%d' % prog)
-
- if '%t' in format:
- format = format.replace('%t', '%d%%' % (100-prog))
-
- if '%o' in format:
- format = format.replace('%o', '%d' % (100-prog))
-
- if '%s' in format:
- spinner = prog_config['spinner']
- spin = '-' if not spinner else spinner
- index = 0 if prog == 100 else prog_config['updates'] % len(spin)
- char = '\\\\' if spin[index] == '\\' else spin[index]
- format = format.replace('%s', char)
-
- if '%r' in format:
- sprites = prog_config['sprites']
- sprites = '-' if not sprites else sprites
- index = int(((prog/100.0)*len(sprites))+0.5)-1
- sprite = '\\\\' if sprites[index] == '\\' else sprites[index]
- format = format.replace('%r', sprite)
-
- if 'progress_format' not in config or config['progress_format'] != format:
- config['progress_format'] = format
-
-
-def progress_config(uzbl, args):
- '''Parse PROGRESS_CONFIG events from the uzbl instance.
-
- Syntax: event PROGRESS_CONFIG <key> = <value>
- '''
-
- split = args.split('=', 1)
- if len(split) != 2:
- return error("invalid syntax: %r" % args)
-
- key, value = map(unicode.strip, split)
- prog_config = get_progress_config(uzbl)
-
- if key not in prog_config:
- return error("key error: %r" % args)
-
- if type(prog_config[key]) == type(1):
- try:
- value = int(value)
-
- except:
- return error("invalid type: %r" % args)
-
- elif not value:
- value = ' '
-
- prog_config[key] = value
- update_progress(uzbl)
-
-
-def reset_progress(uzbl, args):
- '''Reset the spinner counter, reset the progress int and re-draw the
- progress bar on LOAD_COMMIT.'''
-
- prog_dict = get_progress_config(uzbl)
- prog_dict['updates'] = prog_dict['progress'] = 0
- update_progress(uzbl)
-
-
-def init(uzbl):
- connects = {'LOAD_PROGRESS': update_progress,
- 'INSTANCE_START': add_instance,
- 'INSTANCE_EXIT': del_instance,
- 'PROGRESS_CONFIG': progress_config,
- 'LOAD_COMMIT': reset_progress}
-
- uzbl.connect_dict(connects)