aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Makefile5
-rw-r--r--examples/data/uzbl/plugins/bind.py (renamed from examples/data/uzbl/scripts/plugins/bind.py)30
-rw-r--r--examples/data/uzbl/plugins/config.py (renamed from examples/data/uzbl/scripts/plugins/config.py)0
-rw-r--r--examples/data/uzbl/plugins/keycmd.py (renamed from examples/data/uzbl/scripts/plugins/keycmd.py)0
-rw-r--r--examples/data/uzbl/plugins/mode.py (renamed from examples/data/uzbl/scripts/plugins/mode.py)0
-rw-r--r--examples/data/uzbl/plugins/on_event.py (renamed from examples/data/uzbl/scripts/plugins/on_event.py)0
-rw-r--r--examples/data/uzbl/plugins/plugin_template.py (renamed from examples/data/uzbl/scripts/plugins/plugin_template.py)0
-rw-r--r--examples/data/uzbl/plugins/progress_bar.py (renamed from examples/data/uzbl/scripts/plugins/progress_bar.py)0
-rwxr-xr-xexamples/data/uzbl/scripts/event_manager.py865
-rwxr-xr-xuzbl-browser31
-rwxr-xr-xuzbl-daemon20
11 files changed, 552 insertions, 399 deletions
diff --git a/Makefile b/Makefile
index 167881f..65a2760 100644
--- a/Makefile
+++ b/Makefile
@@ -37,7 +37,6 @@ uzbl-core: ${OBJ}
@echo ... done.
-
uzbl-browser: uzbl-core
PREFIX?=$(DESTDIR)/usr/local
@@ -58,8 +57,10 @@ test-dev: uzbl-core
test-dev-browser: uzbl-browser
XDG_DATA_HOME=./examples/data XDG_CACHE_HOME=./examples/cache XDG_CONFIG_HOME=./examples/config PATH="`pwd`:$$PATH" ./examples/data/uzbl/scripts/cookie_daemon.py start -nv &
+ XDG_DATA_HOME=./examples/data XDG_CACHE_HOME=./examples/cache XDG_CONFIG_HOME=./examples/config PATH="`pwd`:$$PATH" ./uzbl-daemon start -nv &
XDG_DATA_HOME=./examples/data XDG_CACHE_HOME=./examples/cache XDG_CONFIG_HOME=./examples/config PATH="`pwd`:$$PATH" ./uzbl-browser --uri http://www.uzbl.org --verbose
XDG_DATA_HOME=./examples/data XDG_CACHE_HOME=./examples/cache XDG_CONFIG_HOME=./examples/config PATH="`pwd`:$$PATH" ./examples/data/uzbl/scripts/cookie_daemon.py stop -v
+ XDG_DATA_HOME=./examples/data XDG_CACHE_HOME=./examples/cache XDG_CONFIG_HOME=./examples/config PATH="`pwd`:$$PATH" ./uzbl-daemon stop -v
test-share: uzbl-core
XDG_DATA_HOME=${PREFIX}/share/uzbl/examples/data XDG_CONFIG_HOME=${PREFIX}/share/uzbl/examples/config ./uzbl-core --uri http://www.uzbl.org --verbose
@@ -84,6 +85,7 @@ install: all
cp -rp examples $(PREFIX)/share/uzbl/
install -m755 uzbl-core $(PREFIX)/bin/uzbl-core
install -m755 uzbl-browser $(PREFIX)/bin/uzbl-browser
+ install -m755 uzbl-daemon $(PREFIX)/bin/uzbl-daemon
install -m644 AUTHORS $(PREFIX)/share/uzbl/docs
install -m644 README $(PREFIX)/share/uzbl/docs
@@ -91,4 +93,3 @@ install: all
uninstall:
rm -rf $(PREFIX)/bin/uzbl-*
rm -rf $(PREFIX)/share/uzbl
-
diff --git a/examples/data/uzbl/scripts/plugins/bind.py b/examples/data/uzbl/plugins/bind.py
index 15f6f8e..d62872f 100644
--- a/examples/data/uzbl/scripts/plugins/bind.py
+++ b/examples/data/uzbl/plugins/bind.py
@@ -207,6 +207,32 @@ class Bind(object):
return '<Bind(%s)>' % ', '.join(args)
+def exec_bind(uzbl, bind, *args, **kargs):
+ '''Execute bind objects.'''
+
+ if bind.callable:
+ args += bind.args
+ kargs = dict(bind.kargs.items()+kargs.items())
+ bind.function(uzbl, *args, **kargs)
+ return
+
+ if kargs:
+ raise ArgumentError('cannot supply kargs for uzbl commands')
+
+ commands = []
+
+ for cmd in bind.commands:
+ if '%s' in cmd:
+ if len(args) > 1:
+ for arg in args:
+ cmd = cmd.replace('%s', arg, 1)
+
+ elif len(args) == 1:
+ cmd = cmd.replace('%s', args[0])
+
+ uzbl.send(cmd)
+
+
def bind(uzbl, glob, handler, *args, **kargs):
'''Add a bind handler object.'''
@@ -292,7 +318,7 @@ def match_and_exec(uzbl, bind, depth, keycmd):
execindex = len(bind.stack)-1
if execindex == depth == 0:
- uzbl.exec_handler(bind, *args)
+ exec_bind(uzbl, bind, *args)
if not has_args:
uzbl.clear_keycmd()
@@ -312,7 +338,7 @@ def match_and_exec(uzbl, bind, depth, keycmd):
return False
args = bind_dict['args'] + args
- uzbl.exec_handler(bind, *args)
+ exec_bind(uzbl, bind, *args)
if on_exec:
uzbl.set_mode()
diff --git a/examples/data/uzbl/scripts/plugins/config.py b/examples/data/uzbl/plugins/config.py
index 22803b4..22803b4 100644
--- a/examples/data/uzbl/scripts/plugins/config.py
+++ b/examples/data/uzbl/plugins/config.py
diff --git a/examples/data/uzbl/scripts/plugins/keycmd.py b/examples/data/uzbl/plugins/keycmd.py
index 3dd6f37..3dd6f37 100644
--- a/examples/data/uzbl/scripts/plugins/keycmd.py
+++ b/examples/data/uzbl/plugins/keycmd.py
diff --git a/examples/data/uzbl/scripts/plugins/mode.py b/examples/data/uzbl/plugins/mode.py
index ad0d9a8..ad0d9a8 100644
--- a/examples/data/uzbl/scripts/plugins/mode.py
+++ b/examples/data/uzbl/plugins/mode.py
diff --git a/examples/data/uzbl/scripts/plugins/on_event.py b/examples/data/uzbl/plugins/on_event.py
index 242f9b0..242f9b0 100644
--- a/examples/data/uzbl/scripts/plugins/on_event.py
+++ b/examples/data/uzbl/plugins/on_event.py
diff --git a/examples/data/uzbl/scripts/plugins/plugin_template.py b/examples/data/uzbl/plugins/plugin_template.py
index 03cb748..03cb748 100644
--- a/examples/data/uzbl/scripts/plugins/plugin_template.py
+++ b/examples/data/uzbl/plugins/plugin_template.py
diff --git a/examples/data/uzbl/scripts/plugins/progress_bar.py b/examples/data/uzbl/plugins/progress_bar.py
index b6fcb1b..b6fcb1b 100644
--- a/examples/data/uzbl/scripts/plugins/progress_bar.py
+++ b/examples/data/uzbl/plugins/progress_bar.py
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/uzbl-browser b/uzbl-browser
index 202db11..02ea6c9 100755
--- a/uzbl-browser
+++ b/uzbl-browser
@@ -1,5 +1,5 @@
#!/bin/sh
-# this script implements are more useful "browsing experience".
+# this script implements are more useful "browsing experience".
# We are assuming you want to use the event_manager.py and cookie_daemon.py.
# So, you must have them in the appropriate place, and cookie_daemon_socket must be configured in the default location
@@ -10,29 +10,30 @@
if [ -z "$XDG_DATA_HOME" ]
then
- XDG_DATA_HOME=$HOME/.local/share
+ export XDG_DATA_HOME=$HOME/.local/share
fi
if [ -z "$XDG_CACHE_HOME" ]
then
- XDG_CACHE_HOME=$HOME/.cache
+ export XDG_CACHE_HOME=$HOME/.cache
fi
if [ ! -S $XDG_CACHE_HOME/uzbl/cookie_daemon_socket ]
then
- $XDG_DATA_HOME/uzbl/scripts/cookie_daemon.py
+ if [ -f "$XDG_DATA_HOME/uzbl/scripts/cookie_daemon.py" ]
+ then
+ $XDG_DATA_HOME/uzbl/scripts/cookie_daemon.py
+ else
+ /usr/local/share/uzbl/examples/data/uzbl/scripts/cookie_daemon.py
+ fi
fi
+DAEMON_SOCKET=$XDG_CACHE_HOME/uzbl/event_daemon
+DAEMON_PID=$XDG_CACHE_HOME/uzbl/event_daemon.pid
-SOCKET_ID="$RANDOM$RANDOM"
-SOCKET_DIR="/tmp"
-SOCKET_PATH="$SOCKET_DIR/uzbl_socket_$SOCKET_ID"
+#if [ -f "$DAEMON_PID" ]
+#then
+ uzbl-daemon start
+#fi
-uzbl-core "$@" -n $SOCKET_ID &
-$XDG_DATA_HOME/uzbl/scripts/event_manager.py -vs $SOCKET_PATH
-
-# TODO: make posix sh compliant. [ -S ] is said to not work. what about test -S ?
-if [[ -S $SOCKETPATH ]]
-then
- rm $SOCKET_PATH
-fi
+uzbl-core "$@" --connect-socket $DAEMON_SOCKET
diff --git a/uzbl-daemon b/uzbl-daemon
new file mode 100755
index 0000000..0e4c0e1
--- /dev/null
+++ b/uzbl-daemon
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+# TODO: Fix up the launcher to check the following paths in order to find the
+# correct event_manager.py to run:
+# 1. $XDG_DATA_HOME/uzbl/scripts/event_manager.py
+# 2. /usr/local/share/uzbl/examples/data/uzbl/scripts/event_manager.py
+
+if [ -z "$XDG_DATA_HOME" ]
+then
+ XDG_DATA_HOME=$HOME/.local/share
+fi
+
+if [ -z "$XDG_CACHE_HOME" ]
+then
+ XDG_CACHE_HOME=$HOME/.cache
+fi
+
+EVENT_MANAGER=/usr/local/share/uzbl/examples/data/uzbl/scripts/event_manager.py
+
+$EVENT_MANAGER -v "$@"