aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Mason Larobina <mason.larobina@gmail.com>2009-08-02 00:00:07 +0800
committerGravatar Mason Larobina <mason.larobina@gmail.com>2009-08-02 00:00:07 +0800
commit67d29a8f08730e7e633315ab36ce82130ec5b026 (patch)
treebfb8e79165a813d558843c2939e58bf60b6a4002
parent2d37b7e5851a1217bb5f77503b8e7ff1284cf2ad (diff)
parent17a60c4e168c56973bbd5b5c6e9a212f4e3ec901 (diff)
Merge branch 'experimental' into cookie_daemon
-rwxr-xr-xexamples/data/uzbl/scripts/uzbl_tabbed.py326
1 files changed, 200 insertions, 126 deletions
diff --git a/examples/data/uzbl/scripts/uzbl_tabbed.py b/examples/data/uzbl/scripts/uzbl_tabbed.py
index 6d458f5..9ffa97d 100755
--- a/examples/data/uzbl/scripts/uzbl_tabbed.py
+++ b/examples/data/uzbl/scripts/uzbl_tabbed.py
@@ -47,7 +47,7 @@
# Optional dependencies:
# simplejson - save uzbl_tabbed.py sessions & presets in json.
#
-# Note: I haven't included version numbers with this dependency list because
+# Note: I haven't included version numbers with this dependency list because
# I've only ever tested uzbl_tabbed.py on the latest stable versions of these
# packages in Gentoo's portage. Package names may vary on different systems.
@@ -161,19 +161,27 @@ import gobject
import socket
import random
import hashlib
+import atexit
+from gobject import io_add_watch, source_remove, timeout_add, IO_IN, IO_HUP
+from signal import signal, SIGTERM, SIGINT
from optparse import OptionParser, OptionGroup
pygtk.require('2.0')
+_scriptname = os.path.basename(sys.argv[0])
def error(msg):
- sys.stderr.write("%s\n"%msg)
+ sys.stderr.write("%s: %s\n" % (_scriptname, msg))
+
+def echo(msg):
+ print "%s: %s" % (_scriptname, msg)
# ============================================================================
# ::: Default configuration section ::::::::::::::::::::::::::::::::::::::::::
# ============================================================================
+
# Location of your uzbl data directory.
if 'XDG_DATA_HOME' in os.environ.keys() and os.environ['XDG_DATA_HOME']:
data_dir = os.path.join(os.environ['XDG_DATA_HOME'], 'uzbl/')
@@ -253,7 +261,7 @@ config = {
'selected_https': 'foreground = "#fff"',
'selected_https_text': 'foreground = "gold"',
- } # End of config dict.
+} # End of config dict.
# This is the tab style policy handler. Every time the tablist is updated
# this function is called to determine how to colourise that specific tab
@@ -423,7 +431,7 @@ class UzblTabbed:
self._connected = True
if timer_call in self.timers.keys():
- gobject.source_remove(self.timers[timer_call])
+ source_remove(self.timers[timer_call])
del self.timers[timer_call]
if self._switch:
@@ -467,11 +475,13 @@ class UzblTabbed:
def __init__(self):
'''Create tablist, window and notebook.'''
- self._fifos = {}
+ # Store information about the applications fifo_socket.
+ self._fifo = None
+
self._timers = {}
self._buffer = ""
self._killed = False
-
+
# A list of the recently closed tabs
self._closed = []
@@ -562,23 +572,84 @@ class UzblTabbed:
self.window.show()
self.wid = self.notebook.window.xid
- # Create the uzbl_tabbed fifo
+ # Generate the fifo socket filename.
fifo_filename = 'uzbltabbed_%d' % os.getpid()
self.fifo_socket = os.path.join(config['fifo_dir'], fifo_filename)
- self._create_fifo_socket(self.fifo_socket)
- self._setup_fifo_watcher(self.fifo_socket)
-
+
+ # Now initialise the fifo socket at self.fifo_socket
+ self.init_fifo_socket()
+
# If we are using sessions then load the last one if it exists.
if config['save_session']:
self.load_session()
- def _create_fifo_socket(self, fifo_socket):
+ def run(self):
+ '''UzblTabbed main function that calls the gtk loop.'''
+
+ if not len(self.tabs):
+ self.new_tab()
+
+ # Update tablist timer
+ #timer = "update-tablist"
+ #timerid = timeout_add(500, self.update_tablist,timer)
+ #self._timers[timer] = timerid
+
+ # Probe clients every second for window titles and location
+ timer = "probe-clients"
+ timerid = timeout_add(1000, self.probe_clients, timer)
+ self._timers[timer] = timerid
+
+ # Make SIGTERM act orderly.
+ signal(SIGTERM, lambda signum, stack_frame: self.terminate(SIGTERM))
+
+ # Catch keyboard interrupts
+ signal(SIGINT, lambda signum, stack_frame: self.terminate(SIGINT))
+
+ try:
+ gtk.main()
+
+ except:
+ error("encounted error %r" % sys.exc_info()[1])
+
+ # Unlink fifo socket
+ self.unlink_fifo_socket()
+
+ # Attempt to close all uzbl instances nicely.
+ self.quitrequest()
+
+ # Allow time for all the uzbl instances to quit.
+ time.sleep(1)
+
+ raise
+
+
+ def terminate(self, termsig=None):
+ '''Handle termination signals and exit safely and cleanly.'''
+
+ # Not required but at least it lets the user know what killed his
+ # browsing session.
+ if termsig == SIGTERM:
+ error("caught SIGTERM signal")
+
+ elif termsig == SIGINT:
+ error("caught keyboard interrupt")
+
+ else:
+ error("caught unknown signal")
+
+ error("commencing infanticide!")
+
+ # Sends the exit signal to all uzbl instances.
+ self.quitrequest()
+
+
+ def init_fifo_socket(self):
'''Create interprocess communication fifo socket.'''
- if os.path.exists(fifo_socket):
- if not os.access(fifo_socket, os.F_OK | os.R_OK | os.W_OK):
- os.mkfifo(fifo_socket)
+ if os.path.exists(self.fifo_socket):
+ if not os.access(self.fifo_socket, os.F_OK | os.R_OK | os.W_OK):
+ os.mkfifo(self.fifo_socket)
else:
basedir = os.path.dirname(self.fifo_socket)
@@ -587,60 +658,93 @@ class UzblTabbed:
os.mkfifo(self.fifo_socket)
- print "Listening on %s" % self.fifo_socket
+ # Add event handlers for IO_IN & IO_HUP events.
+ self.setup_fifo_watchers()
+ echo("listening at %r" % self.fifo_socket)
- def _setup_fifo_watcher(self, fifo_socket):
- '''Open fifo socket fd and setup gobject IO_IN & IO_HUP watchers.
- Also log the creation of a fd and store the the internal
- self._watchers dictionary along with the filename of the fd.'''
+ # Add atexit register to destroy the socket on program termination.
+ atexit.register(self.unlink_fifo_socket)
- if fifo_socket in self._fifos.keys():
- fd, watchers = self._fifos[fifo_socket]
- os.close(fd)
- for (watcherid, gid) in watchers.items():
- gobject.source_remove(gid)
- del watchers[watcherid]
- del self._fifos[fifo_socket]
+ def unlink_fifo_socket(self):
+ '''Unlink the fifo socket. Note: This function is called automatically
+ on exit by an atexit register.'''
- # Re-open fifo and add listeners.
- fd = os.open(fifo_socket, os.O_RDONLY | os.O_NONBLOCK)
- watchers = {}
- self._fifos[fifo_socket] = (fd, watchers)
- watcher = lambda key, id: watchers.__setitem__(key, id)
+ # Make sure the fifo_socket fd is closed.
+ self.close_fifo()
- # Watch for incoming data.
- gid = gobject.io_add_watch(fd, gobject.IO_IN, self.main_fifo_read)
- watcher('main-fifo-read', gid)
+ # And unlink if the real fifo_socket exists.
+ if os.path.exists(self.fifo_socket):
+ os.unlink(self.fifo_socket)
+ echo("unlinked %r" % self.fifo_socket)
- # Watch for fifo hangups.
- gid = gobject.io_add_watch(fd, gobject.IO_HUP, self.main_fifo_hangup)
- watcher('main-fifo-hangup', gid)
+ def close_fifo(self):
+ '''Remove all event handlers watching the fifo and close the fd.'''
- def run(self):
- '''UzblTabbed main function that calls the gtk loop.'''
-
- if not len(self.tabs):
- self.new_tab()
-
- # Update tablist timer
- #timer = "update-tablist"
- #timerid = gobject.timeout_add(500, self.update_tablist,timer)
- #self._timers[timer] = timerid
+ # Already closed
+ if self._fifo is None: return
- # Probe clients every second for window titles and location
- timer = "probe-clients"
- timerid = gobject.timeout_add(1000, self.probe_clients, timer)
- self._timers[timer] = timerid
+ (fd, watchers) = self._fifo
+ os.close(fd)
+
+ # Stop all gobject io watchers watching the fifo.
+ for gid in watchers:
+ source_remove(gid)
+
+ self._fifo = None
+
+
+ def setup_fifo_watchers(self):
+ '''Open fifo socket fd and setup gobject IO_IN & IO_HUP event
+ handlers.'''
+
+ # Close currently open fifo_socket fd and kill all watchers
+ self.close_fifo()
+
+ fd = os.open(self.fifo_socket, os.O_RDONLY | os.O_NONBLOCK)
+
+ # Add gobject io event handlers to the fifo socket.
+ watchers = [io_add_watch(fd, IO_IN, self.main_fifo_read),\
+ io_add_watch(fd, IO_HUP, self.main_fifo_hangup)]
+
+ self._fifo = (fd, watchers)
+
+
+ def main_fifo_hangup(self, fd, cb_condition):
+ '''Handle main fifo socket hangups.'''
+
+ # Close old fd, open new fifo socket and add io event handlers.
+ self.setup_fifo_watchers()
+
+ # Kill the gobject event handler calling this handler function.
+ return False
+
+
+ def main_fifo_read(self, fd, cb_condition):
+ '''Read from main fifo socket.'''
+
+ self._buffer = os.read(fd, 1024)
+ temp = self._buffer.split("\n")
+ self._buffer = temp.pop()
+ cmds = [s.strip().split() for s in temp if len(s.strip())]
+
+ for cmd in cmds:
+ try:
+ #print cmd
+ self.parse_command(cmd)
- gtk.main()
+ except:
+ error("parse_command: invalid command %s" % ' '.join(cmd))
+ raise
+
+ return True
def probe_clients(self, timer_call):
'''Probe all uzbl clients for up-to-date window titles and uri's.'''
-
+
save_session = config['save_session']
sockd = {}
@@ -652,7 +756,7 @@ class UzblTabbed:
uzbl = self.tabs[tab]
uzbl.probe()
if uzbl._socket:
- sockd[uzbl._socket] = uzbl
+ sockd[uzbl._socket] = uzbl
sockets = sockd.keys()
(reading, _, errors) = select.select(sockets, [], sockets, 0)
@@ -675,36 +779,6 @@ class UzblTabbed:
return True
- def main_fifo_hangup(self, fd, cb_condition):
- '''Handle main fifo socket hangups.'''
-
- # Close fd, re-open fifo_socket and watch.
- self._setup_fifo_watcher(self.fifo_socket)
-
- # And to kill any gobject event handlers calling this function:
- return False
-
-
- def main_fifo_read(self, fd, cb_condition):
- '''Read from main fifo socket.'''
-
- self._buffer = os.read(fd, 1024)
- temp = self._buffer.split("\n")
- self._buffer = temp.pop()
- cmds = [s.strip().split() for s in temp if len(s.strip())]
-
- for cmd in cmds:
- try:
- #print cmd
- self.parse_command(cmd)
-
- except:
- error("parse_command: invalid command %s" % ' '.join(cmd))
- raise
-
- return True
-
-
def parse_command(self, cmd):
'''Parse instructions from uzbl child processes.'''
@@ -778,14 +852,15 @@ class UzblTabbed:
new = ' '.join(cmd[2:])
setattr(uzbl, cmd[0], new)
if old != new:
- self.update_tablist()
+ self.update_tablist()
+
else:
error("parse_command: no uzbl with pid %r" % int(cmd[1]))
elif cmd[0] == "preset":
if len(cmd) < 3:
error("parse_command: invalid preset command")
-
+
elif cmd[1] == "save":
path = os.path.join(config['saved_sessions_dir'], cmd[2])
self.save_session(path)
@@ -801,7 +876,7 @@ class UzblTabbed:
else:
error("parse_command: preset %r does not exist." % path)
-
+
elif cmd[1] == "list":
uzbl = self.get_tab_by_pid(int(cmd[2]))
if uzbl:
@@ -824,7 +899,7 @@ class UzblTabbed:
elif cmd[0] == "clean":
self.clean_slate()
-
+
else:
error("parse_command: unknown command %r" % ' '.join(cmd))
@@ -859,7 +934,7 @@ class UzblTabbed:
if switch is None:
switch = config['switch_to_new_tabs']
-
+
if not title:
title = config['new_tab_title']
@@ -875,7 +950,7 @@ class UzblTabbed:
# Add gobject timer to make sure the config is pushed when fifo socket
# has been created.
- timerid = gobject.timeout_add(100, uzbl.flush, "flush-initial-config")
+ timerid = timeout_add(100, uzbl.flush, "flush-initial-config")
uzbl.timers['flush-initial-config'] = timerid
self.update_tablist()
@@ -890,7 +965,7 @@ class UzblTabbed:
if tab not in tabs: continue
uzbl = self.tabs[tab]
uzbl.send("exit")
-
+
def config_uzbl(self, uzbl):
'''Send bind commands for tab new/close/next/prev to a uzbl
@@ -918,8 +993,6 @@ class UzblTabbed:
bind(config['bind_goto_first'], 'goto 0')
bind(config['bind_goto_last'], 'goto -1')
bind(config['bind_clean_slate'], 'clean')
-
- # session preset binds
bind(config['bind_save_preset'], 'preset save %s')
bind(config['bind_load_preset'], 'preset load %s')
bind(config['bind_del_preset'], 'preset del %s')
@@ -929,7 +1002,7 @@ class UzblTabbed:
# set(key, command back to fifo)
if config['capture_new_windows']:
set("new_window", r'new $8')
-
+
# Send config to uzbl instance via its socket file.
uzbl.send("\n".join(binds+sets))
@@ -1019,7 +1092,7 @@ class UzblTabbed:
uzbl = self.tabs[tab]
for (timer, gid) in uzbl.timers.items():
error("tab_closed: removing timer %r" % timer)
- gobject.source_remove(gid)
+ source_remove(gid)
del uzbl.timers[timer]
if uzbl._socket:
@@ -1125,7 +1198,7 @@ class UzblTabbed:
if session_file is None:
session_file = config['session_file']
-
+
if session is None:
tabs = self.tabs.keys()
state = []
@@ -1147,7 +1220,7 @@ class UzblTabbed:
lines += ["%s\t%s" % (strip(uri), strip(title)),]
raw = "\n".join(lines)
-
+
if not os.path.isfile(session_file):
dirname = os.path.dirname(session_file)
if not os.path.isdir(dirname):
@@ -1156,11 +1229,11 @@ class UzblTabbed:
h = open(session_file, 'w')
h.write(raw)
h.close()
-
+
def load_session(self, session_file=None):
'''Load a saved session from file.'''
-
+
default_path = False
strip = str.strip
json_session = config['json_session']
@@ -1181,8 +1254,8 @@ class UzblTabbed:
"Trying to load it as a non-json session file."\
% session_file)
json_session = False
-
- if json_session:
+
+ if json_session:
try:
session = json.loads(raw)
curtab, tabs = session['curtab'], session['tabs']
@@ -1201,7 +1274,7 @@ class UzblTabbed:
error("Warning: The non-json session file %r looks invalid."\
% session_file)
return None
-
+
try:
for line in lines:
if line.startswith("curtab"):
@@ -1216,22 +1289,19 @@ class UzblTabbed:
return None
session = {'curtab': curtab, 'tabs': tabs}
-
+
# Now populate notebook with the loaded session.
for (index, (uri, title)) in enumerate(tabs):
self.new_tab(uri=uri, title=title, switch=(curtab==index))
- # There may be other state information in the session dict of use to
- # other functions. Of course however the non-json session object is
+ # There may be other state information in the session dict of use to
+ # other functions. Of course however the non-json session object is
# just a dummy object of no use to no one.
return session
def quitrequest(self, *args):
'''Called by delete-event signal to kill all uzbl instances.'''
-
- #TODO: Even though I send the kill request to all uzbl instances
- # i should add a gobject timeout to check they all die.
self._killed = True
@@ -1241,33 +1311,37 @@ class UzblTabbed:
else:
# Notebook has no pages so delete session file if it exists.
- if os.path.isfile(session_file):
- os.remove(session_file)
-
+ if os.path.isfile(config['session_file']):
+ os.remove(config['session_file'])
+
for (tab, uzbl) in self.tabs.items():
uzbl.send("exit")
-
-
+
+ # Add a gobject timer to make sure the application force-quits after a
+ # reasonable period. Calling quit when all the tabs haven't had time to
+ # close should be a last resort.
+ timer = "force-quit"
+ timerid = timeout_add(5000, self.quit, timer)
+ self._timers[timer] = timerid
+
+
def quit(self, *args):
- '''Cleanup the application and quit. Called by delete-event signal.'''
-
- for (fifo_socket, (fd, watchers)) in self._fifos.items():
- os.close(fd)
- for (watcherid, gid) in watchers.items():
- gobject.source_remove(gid)
- del watchers[watcherid]
+ '''Cleanup and quit. Called by delete-event signal.'''
- del self._fifos[fifo_socket]
+ # Close the fifo socket, remove any gobject io event handlers and
+ # delete socket.
+ self.unlink_fifo_socket()
+ # Remove all gobject timers that are still ticking.
for (timerid, gid) in self._timers.items():
- gobject.source_remove(gid)
+ source_remove(gid)
del self._timers[timerid]
- if os.path.exists(self.fifo_socket):
- os.unlink(self.fifo_socket)
- print "Unlinked %s" % self.fifo_socket
+ try:
+ gtk.main_quit()
- gtk.main_quit()
+ except:
+ pass
if __name__ == "__main__":