diff options
author | Mason Larobina <mason.larobina@gmail.com> | 2009-08-02 00:00:07 +0800 |
---|---|---|
committer | Mason Larobina <mason.larobina@gmail.com> | 2009-08-02 00:00:07 +0800 |
commit | 67d29a8f08730e7e633315ab36ce82130ec5b026 (patch) | |
tree | bfb8e79165a813d558843c2939e58bf60b6a4002 | |
parent | 2d37b7e5851a1217bb5f77503b8e7ff1284cf2ad (diff) | |
parent | 17a60c4e168c56973bbd5b5c6e9a212f4e3ec901 (diff) |
Merge branch 'experimental' into cookie_daemon
-rwxr-xr-x | examples/data/uzbl/scripts/uzbl_tabbed.py | 326 |
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__": |