diff options
author | Mason Larobina <mason.larobina@gmail.com> | 2009-09-12 23:21:34 +0800 |
---|---|---|
committer | Mason Larobina <mason.larobina@gmail.com> | 2009-09-12 23:21:34 +0800 |
commit | 3cd29b2fa132a7aa67b79aa2c6523cde627589d8 (patch) | |
tree | 0605895577a1a91fd1c4e223df0247af2e2e50d2 /examples/data/uzbl/scripts | |
parent | bdbd00854e36785c9d660e66180211b2b4d6fd73 (diff) | |
parent | 8d4a4fee7b5ea9fa39509c9dda1dc66834ce11a1 (diff) |
Merge branch 'experimental' of git://github.com/Dieterbe/uzbl into event-messages
Diffstat (limited to 'examples/data/uzbl/scripts')
-rwxr-xr-x | examples/data/uzbl/scripts/cookie_daemon.py | 260 | ||||
-rwxr-xr-x | examples/data/uzbl/scripts/uzbl_tabbed.py | 228 |
2 files changed, 349 insertions, 139 deletions
diff --git a/examples/data/uzbl/scripts/cookie_daemon.py b/examples/data/uzbl/scripts/cookie_daemon.py index af53e73..87a2e87 100755 --- a/examples/data/uzbl/scripts/cookie_daemon.py +++ b/examples/data/uzbl/scripts/cookie_daemon.py @@ -1,9 +1,10 @@ #!/usr/bin/env python -# Uzbl tabbing wrapper using a fifo socket interface +# The Python Cookie Daemon for Uzbl. # Copyright (c) 2009, Tom Adams <tom@holizz.com> -# Copyright (c) 2009, Dieter Plaetinck <dieter AT plaetinck.be> +# Copyright (c) 2009, Dieter Plaetinck <dieter@plaetinck.be> # Copyright (c) 2009, Mason Larobina <mason.larobina@gmail.com> +# Copyright (c) 2009, Michael Fiano <axionix@gmail.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -40,21 +41,10 @@ find the latest version of the cookie_daemon.py Command line options ==================== -Usage: cookie_daemon.py [options] - -Options: - -h, --help show this help message and exit - -n, --no-daemon don't daemonise the process. - -v, --verbose print verbose output. - -t SECONDS, --daemon-timeout=SECONDS - shutdown the daemon after x seconds inactivity. - WARNING: Do not use this when launching the cookie - daemon manually. - -s SOCKET, --cookie-socket=SOCKET - manually specify the socket location. - -j FILE, --cookie-jar=FILE - manually specify the cookie jar location. - -m, --memory store cookies in memory only - do not write to disk +Use the following command to get a full list of the cookie_daemon.py command +line options: + + ./cookie_daemon.py --help Talking with uzbl ================= @@ -68,18 +58,10 @@ Or if you prefer using the $HOME variable: set cookie_handler = talk_to_socket $HOME/.cache/uzbl/cookie_daemon_socket -Issues -====== - - - There is no easy way of stopping a running daemon. - Todo list ========= - - Use a pid file to make stopping a running daemon easy. - - add {start|stop|restart} command line arguments to make the cookie_daemon - functionally similar to the daemons found in /etc/init.d/ (in gentoo) - or /etc/rc.d/ (in arch). + - Use a pid file to make force killing a running daemon possible. Reporting bugs / getting help ============================= @@ -100,6 +82,7 @@ import atexit from traceback import print_exc from signal import signal, SIGTERM from optparse import OptionParser +from os.path import join try: import cStringIO as StringIO @@ -112,27 +95,36 @@ except ImportError: # ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: # ============================================================================ +def xdghome(key, default): + '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise + use $HOME and the default path.''' -# Location of the uzbl cache directory. -if 'XDG_CACHE_HOME' in os.environ.keys() and os.environ['XDG_CACHE_HOME']: - CACHE_DIR = os.path.join(os.environ['XDG_CACHE_HOME'], 'uzbl/') + xdgkey = "XDG_%s_HOME" % key + if xdgkey in os.environ.keys() and os.environ[xdgkey]: + return os.environ[xdgkey] -else: - CACHE_DIR = os.path.join(os.environ['HOME'], '.cache/uzbl/') + return join(os.environ['HOME'], default) -# Location of the 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/') +# Setup xdg paths. +CACHE_DIR = join(xdghome('CACHE', '.cache/'), 'uzbl/') +DATA_DIR = join(xdghome('DATA', '.local/share/'), 'uzbl/') +CONFIG_DIR = join(xdghome('CONFIG', '.config/'), 'uzbl/') -else: - DATA_DIR = os.path.join(os.environ['HOME'], '.local/share/uzbl/') +# Ensure data paths exist. +for path in [CACHE_DIR, DATA_DIR, CONFIG_DIR]: + if not os.path.exists(path): + os.makedirs(path) # Default config config = { - # Default cookie jar and daemon socket locations. - 'cookie_socket': os.path.join(CACHE_DIR, 'cookie_daemon_socket'), - 'cookie_jar': os.path.join(DATA_DIR, 'cookies.txt'), + # Default cookie jar, whitelist, and daemon socket locations. + 'cookie_jar': join(DATA_DIR, 'cookies.txt'), + 'cookie_whitelist': join(CONFIG_DIR, 'cookie_whitelist'), + 'cookie_socket': join(CACHE_DIR, 'cookie_daemon_socket'), + + # Don't use a cookie whitelist policy by default. + 'use_whitelist': False, # Time out after x seconds of inactivity (set to 0 for never time out). # WARNING: Do not use this option if you are manually launching the daemon. @@ -177,7 +169,7 @@ def mkbasedir(filepath): os.makedirs(dirname) -def check_socket_health(cookie_socket): +def daemon_running(cookie_socket): '''Check if another process (hopefully a cookie_daemon.py) is listening on the cookie daemon socket. If another process is found to be listening on the socket exit the daemon immediately and leave the @@ -185,27 +177,71 @@ def check_socket_health(cookie_socket): and delete it (to be re-created in the create socket function).''' if not os.path.exists(cookie_socket): - # What once was is now no more. - return None + return False if os.path.isfile(cookie_socket): - error("regular file at %r is not a socket" % cookie_socket) + raise Exception("regular file at %r is not a socket" % cookie_socket) + if os.path.isdir(cookie_socket): - error("directory at %r is not a socket" % cookie_socket) + raise Exception("directory at %r is not a socket" % cookie_socket) try: sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) sock.connect(cookie_socket) sock.close() - error("detected another process listening on %r" % cookie_socket) + echo("detected daemon listening on %r" % cookie_socket) + return True except socket.error: # Failed to connect to cookie_socket so assume it has been # abandoned by another cookie daemon process. if os.path.exists(cookie_socket): - echo("deleting abandoned socket %r" % cookie_socket) + echo("deleting abandoned socket at %r" % cookie_socket) + os.remove(cookie_socket) + + return False + + +def send_command(cookie_socket, cmd): + '''Send a command to a running cookie daemon.''' + + if not daemon_running(cookie_socket): + return False + + try: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) + sock.connect(cookie_socket) + sock.send(cmd) + sock.close() + echo("sent command %r to %r" % (cmd, cookie_socket)) + return True + + except socket.error: + print_exc() + error("failed to send message %r to %r" % (cmd, cookie_socket)) + return False + + +def kill_daemon(cookie_socket): + '''Send the "EXIT" command to running cookie_daemon.''' + + if send_command(cookie_socket, "EXIT"): + # Now ensure the cookie_socket is cleaned up. + start = time.time() + while os.path.exists(cookie_socket): + time.sleep(0.1) + if (time.time() - start) > 5: + error("force deleting socket %r" % cookie_socket) + os.remove(cookie_socket) + return + + echo("stopped daemon listening on %r"% cookie_socket) + + else: + if os.path.exists(cookie_socket): os.remove(cookie_socket) + echo("removed abandoned/broken socket %r" % cookie_socket) def daemonize(): @@ -265,7 +301,8 @@ class CookieMonster: # listening on the cookie socket and remove the abandoned socket if # there isnt. if os.path.exists(config['cookie_socket']): - check_socket_health(config['cookie_socket']) + if daemon_running(config['cookie_socket']): + sys.exit(1) # Daemonize process. if config['daemon_mode']: @@ -310,16 +347,47 @@ class CookieMonster: self.del_socket() + def load_whitelist(self): + '''Load the cookie jar whitelist policy.''' + + cookie_whitelist = config['cookie_whitelist'] + + if cookie_whitelist: + mkbasedir(cookie_whitelist) + + # Create cookie whitelist file if it does not exist. + if not os.path.exists(cookie_whitelist): + open(cookie_whitelist, 'w').close() + + # Read cookie whitelist file into list. + file = open(cookie_whitelist,'r') + domain_list = [line.rstrip('\n') for line in file] + file.close() + + # Define policy of allowed domains + policy = cookielib.DefaultCookiePolicy(allowed_domains=domain_list) + self.jar.set_policy(policy) + + # Save the last modified time of the whitelist. + self._whitelistmtime = os.stat(cookie_whitelist).st_mtime + + def open_cookie_jar(self): '''Open the cookie jar.''' cookie_jar = config['cookie_jar'] + cookie_whitelist = config['cookie_whitelist'] + if cookie_jar: mkbasedir(cookie_jar) # Create cookie jar object from file. self.jar = cookielib.MozillaCookieJar(cookie_jar) + # Load cookie whitelist policy. + if config['use_whitelist']: + self.load_whitelist() + if cookie_jar: try: # Attempt to load cookies from the cookie jar. @@ -334,6 +402,15 @@ class CookieMonster: pass + def reload_whitelist(self): + '''Reload the cookie whitelist.''' + + cookie_whitelist = config['cookie_whitelist'] + if os.path.exists(cookie_whitelist): + echo("reloading whitelist %r" % cookie_whitelist) + self.open_cookie_jar() + + def create_socket(self): '''Create AF_UNIX socket for communication with uzbl instances.''' @@ -343,10 +420,6 @@ class CookieMonster: self.server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) - if os.path.exists(cookie_socket): - # Accounting for super-rare super-fast racetrack condition. - check_socket_health(cookie_socket) - self.server_socket.bind(cookie_socket) # Set restrictive permissions on the cookie socket to prevent other @@ -357,6 +430,7 @@ class CookieMonster: def listen(self): '''Listen for incoming cookie PUT and GET requests.''' + daemon_timeout = config['daemon_timeout'] echo("listening on %r" % config['cookie_socket']) while self._running: @@ -370,11 +444,12 @@ class CookieMonster: self.handle_request(client_socket) self.last_request = time.time() client_socket.close() + continue - if config['daemon_timeout']: + if daemon_timeout: # Checks if the daemon has been idling for too long. idle = time.time() - self.last_request - if idle > config['daemon_timeout']: + if idle > daemon_timeout: self._running = False @@ -388,19 +463,28 @@ class CookieMonster: # Cookie argument list in packet is null separated. argv = data.split("\0") + action = argv[0].upper().strip() # Catch the EXIT command sent to kill running daemons. - if len(argv) == 1 and argv[0].strip() == "EXIT": + if action == "EXIT": self._running = False return + # Catch whitelist RELOAD command. + elif action == "RELOAD": + self.reload_whitelist() + return + + # Return if command unknown. + elif action not in ['GET', 'PUT']: + error("unknown command %r." % argv) + return + # Determine whether or not to print cookie data to terminal. print_cookie = (config['verbose'] and not config['daemon_mode']) if print_cookie: print ' '.join(argv[:4]) - action = argv[0] - uri = urllib2.urlparse.ParseResult( scheme=argv[1], netloc=argv[2], @@ -475,7 +559,8 @@ def main(): '''Main function.''' # Define command line parameters. - parser = OptionParser() + usage = "usage: %prog [options] {start|stop|restart|reload}" + parser = OptionParser(usage=usage) parser.add_option('-n', '--no-daemon', dest='no_daemon', action='store_true', help="don't daemonise the process.") @@ -496,46 +581,81 @@ def main(): parser.add_option('-m', '--memory', dest='memory', action='store_true', help="store cookies in memory only - do not write to disk") + parser.add_option('-u', '--use-whitelist', dest='usewhitelist', + action='store_true', help="use cookie whitelist policy") + + parser.add_option('-w', '--cookie-whitelist', dest='whitelist', + action='store', help="manually specify whitelist location", + metavar='FILE') + # Parse the command line arguments. (options, args) = parser.parse_args() + expand = lambda p: os.path.realpath(os.path.expandvars(p)) + + initcommands = ['start', 'stop', 'restart', 'reload'] + for arg in args: + if arg not in initcommands: + error("unknown argument %r" % args[0]) + sys.exit(1) + + if len(args) > 1: + error("the daemon only accepts one {%s} action at a time." + % '|'.join(initcommands)) + sys.exit(1) + if len(args): - error("unknown argument %r" % args[0]) + action = args[0] - if options.verbose: - config['verbose'] = True - echo("verbose mode on") + else: + action = "start" if options.no_daemon: - echo("daemon mode off") config['daemon_mode'] = False if options.cookie_socket: - echo("using cookie_socket %r" % options.cookie_socket) - config['cookie_socket'] = options.cookie_socket + config['cookie_socket'] = expand(options.cookie_socket) if options.cookie_jar: - echo("using cookie_jar %r" % options.cookie_jar) - config['cookie_jar'] = options.cookie_jar + config['cookie_jar'] = expand(options.cookie_jar) if options.memory: - echo("using memory %r" % options.memory) config['cookie_jar'] = None + if options.whitelist: + config['cookie_whitelist'] = expand(options.whitelist) + + if options.whitelist or options.usewhitelist: + config['use_whitelist'] = True + if options.daemon_timeout: try: config['daemon_timeout'] = int(options.daemon_timeout) - echo("set timeout to %d seconds" % config['daemon_timeout']) except ValueError: error("expected int argument for -t, --daemon-timeout") # Expand $VAR's in config keys that relate to paths. - for key in ['cookie_socket', 'cookie_jar']: + for key in ['cookie_socket', 'cookie_jar', 'cookie_whitelist']: if config[key]: config[key] = os.path.expandvars(config[key]) - CookieMonster().run() + if options.verbose: + config['verbose'] = True + import pprint + sys.stderr.write("%s\n" % pprint.pformat(config)) + + # It would be better if we didn't need to start this python process just + # to send a command to the socket, but unfortunately socat doesn't seem + # to support SEQPACKET. + if action == "reload": + send_command(config['cookie_socket'], "RELOAD") + + if action in ['stop', 'restart']: + kill_daemon(config['cookie_socket']) + + if action in ['start', 'restart']: + CookieMonster().run() if __name__ == "__main__": diff --git a/examples/data/uzbl/scripts/uzbl_tabbed.py b/examples/data/uzbl/scripts/uzbl_tabbed.py index feae9b9..bb9b9a2 100755 --- a/examples/data/uzbl/scripts/uzbl_tabbed.py +++ b/examples/data/uzbl/scripts/uzbl_tabbed.py @@ -37,6 +37,13 @@ # # Romain Bignon <romain@peerfuse.org> # Fix for session restoration code. +# +# Jake Probst <jake.probst@gmail.com> +# Wrote a patch that overflows tabs in the tablist on to new lines when +# running of room. +# +# Devon Jones <devon.jones@gmail.com> +# Fifo command bring_to_front which brings the gtk window to focus. # Dependencies: @@ -63,8 +70,10 @@ # show_gtk_tabs = 0 # tablist_top = 1 # gtk_tab_pos = (top|left|bottom|right) +# gtk_refresh = 1000 # switch_to_new_tabs = 1 # capture_new_windows = 1 +# multiline_tabs = 1 # # Tab title options: # tab_titles = 1 @@ -83,8 +92,9 @@ # icon_path = $HOME/.local/share/uzbl/uzbl.png # status_background = #303030 # -# Window options: +# Misc options: # window_size = 800,800 +# verbose = 0 # # And the key bindings: # bind_new_tab = gn @@ -97,12 +107,13 @@ # bind_goto_first = g< # bind_goto_last = g> # bind_clean_slate = gQ +# bind_exit = gZ # # Session preset key bindings: -# bind_save_preset = gsave _ -# bind_load_preset = gload _ -# bind_del_preset = gdel _ -# bind_list_presets = glist +# bind_save_preset = gsave _ +# bind_load_preset = gload _ +# bind_del_preset = gdel _ +# bind_list_presets = glist # # And uzbl_tabbed.py takes care of the actual binding of the commands via each # instances fifo socket. @@ -162,41 +173,47 @@ import socket import random import hashlib import atexit +import types 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]) +_SCRIPTNAME = os.path.basename(sys.argv[0]) def error(msg): - sys.stderr.write("%s: %s\n" % (_scriptname, msg)) - -def echo(msg): - print "%s: %s" % (_scriptname, msg) - + sys.stderr.write("%s: error: %s\n" % (_SCRIPTNAME, msg)) # ============================================================================ # ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: # ============================================================================ +def xdghome(key, default): + '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise + use $HOME and the default path.''' -# 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/') -else: - data_dir = os.path.join(os.environ['HOME'], '.local/share/uzbl/') -if not os.path.exists(data_dir): - error("Warning: uzbl data_dir does not exist: %r" % data_dir) + xdgkey = "XDG_%s_HOME" % key + if xdgkey in os.environ.keys() and os.environ[xdgkey]: + return os.environ[xdgkey] -# Location of your uzbl configuration file. -if 'XDG_CONFIG_HOME' in os.environ.keys() and os.environ['XDG_CONFIG_HOME']: - uzbl_config = os.path.join(os.environ['XDG_CONFIG_HOME'], 'uzbl/config') -else: - uzbl_config = os.path.join(os.environ['HOME'],'.config/uzbl/config') -if not os.path.exists(uzbl_config): - error("Warning: Cannot locate your uzbl_config file %r" % uzbl_config) + return os.path.join(os.environ['HOME'], default) + +# Setup xdg paths. +DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') +CONFIG_DIR = os.path.join(xdghome('CONFIG', '.config/'), 'uzbl/') + +# Ensure uzbl xdg paths exist +for path in [DATA_DIR, CONFIG_DIR]: + if not os.path.exists(path): + os.makedirs(path) + +# Path to uzbl config +UZBL_CONFIG = os.path.join(CONFIG_DIR, 'config') +if not os.path.exists(UZBL_CONFIG): + error("cannot find uzbl config file at %r" % UZBL_CONFIG) + sys.exit(1) # All of these settings can be inherited from your uzbl config file. config = { @@ -205,8 +222,10 @@ config = { 'show_gtk_tabs': False, # Show gtk notebook tabs 'tablist_top': True, # Display tab-list at top of window 'gtk_tab_pos': 'top', # Gtk tab position (top|left|bottom|right) + 'gtk_refresh': 1000, # Tablist refresh millisecond interval 'switch_to_new_tabs': True, # Upon opening a new tab switch to it 'capture_new_windows': True, # Use uzbl_tabbed to catch new windows + 'multiline_tabs': True, # Tabs overflow onto new tablist lines. # Tab title options 'tab_titles': True, # Display tab titles (else only tab-nums) @@ -217,17 +236,18 @@ config = { # Session options 'save_session': True, # Save session in file when quit 'json_session': False, # Use json to save session. - 'saved_sessions_dir': os.path.join(data_dir, 'sessions/'), - 'session_file': os.path.join(data_dir, 'session'), + 'saved_sessions_dir': os.path.join(DATA_DIR, 'sessions/'), + 'session_file': os.path.join(DATA_DIR, 'session'), # Inherited uzbl options 'fifo_dir': '/tmp', # Path to look for uzbl fifo. 'socket_dir': '/tmp', # Path to look for uzbl socket. - 'icon_path': os.path.join(data_dir, 'uzbl.png'), + 'icon_path': os.path.join(DATA_DIR, 'uzbl.png'), 'status_background': "#303030", # Default background for all panels. - # Window options + # Misc options 'window_size': "800,800", # width,height in pixels. + 'verbose': False, # Print verbose output. # Key bindings 'bind_new_tab': 'gn', # Open new tab. @@ -240,6 +260,7 @@ config = { 'bind_goto_first': 'g<', # Goto first tab. 'bind_goto_last': 'g>', # Goto last tab. 'bind_clean_slate': 'gQ', # Close all tabs and open new tab. + 'bind_exit': 'gZ', # Exit nicely. # Session preset key bindings 'bind_save_preset': 'gsave _', # Save session to file %s. @@ -293,11 +314,14 @@ def colour_selector(tabindex, currentpage, uzbl): # Default tab style. return (config['tab_colours'], config['tab_text_colours']) - # ============================================================================ # ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: # ============================================================================ +def echo(msg): + if config['verbose']: + sys.stderr.write("%s: %s\n" % (_SCRIPTNAME, msg)) + def readconfig(uzbl_config, config): '''Loads relevant config from the users uzbl config file into the global @@ -324,8 +348,9 @@ def readconfig(uzbl_config, config): config[key] = value # Ensure that config keys that relate to paths are expanded. - expand = ['fifo_dir', 'socket_dir', 'session_file', 'icon_path'] - for key in expand: + pathkeys = ['fifo_dir', 'socket_dir', 'session_file', 'icon_path', + 'saved_sessions_dir'] + for key in pathkeys: config[key] = os.path.expandvars(config[key]) @@ -522,6 +547,7 @@ class UzblTabbed: self.window.add(vbox) ebox = gtk.EventBox() self.tablist = gtk.Label() + self.tablist.set_use_markup(True) self.tablist.set_justify(gtk.JUSTIFY_LEFT) self.tablist.set_line_wrap(False) @@ -571,7 +597,6 @@ class UzblTabbed: self.window.show() self.wid = self.notebook.window.xid - # Generate the fifo socket filename. fifo_filename = 'uzbltabbed_%d' % os.getpid() self.fifo_socket = os.path.join(config['fifo_dir'], fifo_filename) @@ -590,15 +615,17 @@ class UzblTabbed: if not len(self.tabs): self.new_tab() + gtk_refresh = int(config['gtk_refresh']) + if gtk_refresh < 100: + gtk_refresh = 100 + # Update tablist timer - #timer = "update-tablist" - #timerid = timeout_add(500, self.update_tablist,timer) - #self._timers[timer] = timerid + timerid = timeout_add(gtk_refresh, self.update_tablist) + self._timers["update-tablist"] = 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 + timerid = timeout_add(gtk_refresh, self.probe_clients) + self._timers["probe-clients"] = timerid # Make SIGTERM act orderly. signal(SIGTERM, lambda signum, stack_frame: self.terminate(SIGTERM)) @@ -742,7 +769,7 @@ class UzblTabbed: return True - def probe_clients(self, timer_call): + def probe_clients(self): '''Probe all uzbl clients for up-to-date window titles and uri's.''' save_session = config['save_session'] @@ -800,6 +827,11 @@ class UzblTabbed: # title {pid} {document-title} # updates tablist title. # uri {pid} {document-location} + # updates tablist uri + # bring_to_front + # brings the gtk window to focus. + # exit + # exits uzbl_tabbed.py if cmd[0] == "new": if len(cmd) == 2: @@ -897,9 +929,15 @@ class UzblTabbed: error("parse_command: unknown parse command %r"\ % ' '.join(cmd)) + elif cmd[0] == "bring_to_front": + self.window.present() + elif cmd[0] == "clean": self.clean_slate() + elif cmd[0] == "exit": + self.quitrequest() + else: error("parse_command: unknown command %r" % ' '.join(cmd)) @@ -997,6 +1035,7 @@ class UzblTabbed: bind(config['bind_load_preset'], 'preset load %s') bind(config['bind_del_preset'], 'preset del %s') bind(config['bind_list_presets'], 'preset list %d' % uzbl.pid) + bind(config['bind_exit'], 'exit') # Set definitions here # set(key, command back to fifo) @@ -1108,9 +1147,8 @@ class UzblTabbed: if self.notebook.get_n_pages() == 0: if not self._killed and config['save_session']: - if len(self._closed): - d = {'curtab': 0, 'tabs': [self._closed[-1],]} - self.save_session(session=d) + if os.path.exists(config['session_file']): + os.remove(config['session_file']) self.quit() @@ -1135,6 +1173,11 @@ class UzblTabbed: show_gtk_tabs = config['show_gtk_tabs'] tab_titles = config['tab_titles'] show_ellipsis = config['show_ellipsis'] + multiline_tabs = config['multiline_tabs'] + + if multiline_tabs: + multiline = [] + if not show_tablist and not show_gtk_tabs: return True @@ -1149,8 +1192,10 @@ class UzblTabbed: pango = "" normal = (config['tab_colours'], config['tab_text_colours']) selected = (config['selected_tab'], config['selected_tab_text']) + if tab_titles: tab_format = "<span %s> [ %d <span %s> %s</span> ] </span>" + else: tab_format = "<span %s> [ <span %s>%d</span> ] </span>" @@ -1164,14 +1209,22 @@ class UzblTabbed: if index == curpage: self.window.set_title(title_format % uzbl.title) - tabtitle = uzbl.title[:max_title_len] + # Unicode heavy strings do not like being truncated/sliced so by + # re-encoding the string sliced of limbs are removed. + tabtitle = uzbl.title[:max_title_len + int(show_ellipsis)] + if type(tabtitle) != types.UnicodeType: + tabtitle = unicode(tabtitle, 'utf-8', 'ignore') + + tabtitle = tabtitle.encode('utf-8', 'ignore').strip() + if show_ellipsis and len(tabtitle) != len(uzbl.title): - tabtitle = "%s\xe2\x80\xa6" % tabtitle[:-1] # Show Ellipsis + tabtitle += "\xe2\x80\xa6" if show_gtk_tabs: if tab_titles: - self.notebook.set_tab_label_text(tab,\ + self.notebook.set_tab_label_text(tab, gtk_tab_format % (index, tabtitle)) + else: self.notebook.set_tab_label_text(tab, str(index)) @@ -1179,19 +1232,44 @@ class UzblTabbed: style = colour_selector(index, curpage, uzbl) (tabc, textc) = style - if tab_titles: - pango += tab_format % (tabc, index, textc,\ + if multiline_tabs: + opango = pango + + if tab_titles: + pango += tab_format % (tabc, index, textc, + escape(tabtitle)) + + else: + pango += tab_format % (tabc, textc, index) + + self.tablist.set_markup(pango) + listwidth = self.tablist.get_layout().get_pixel_size()[0] + winwidth = self.window.get_size()[0] + + if listwidth > (winwidth - 20): + multiline.append(opango) + pango = tab_format % (tabc, index, textc, + escape(tabtitle)) + + elif tab_titles: + pango += tab_format % (tabc, index, textc, escape(tabtitle)) + else: pango += tab_format % (tabc, textc, index) if show_tablist: - self.tablist.set_markup(pango) + if multiline_tabs: + multiline.append(pango) + self.tablist.set_markup(' '.join(multiline)) + + else: + self.tablist.set_markup(pango) return True - def save_session(self, session_file=None, session=None): + def save_session(self, session_file=None): '''Save the current session to file for restoration on next load.''' strip = str.strip @@ -1199,17 +1277,16 @@ class UzblTabbed: if session_file is None: session_file = config['session_file'] - if session is None: - tabs = self.tabs.keys() - state = [] - for tab in list(self.notebook): - if tab not in tabs: continue - uzbl = self.tabs[tab] - if not uzbl.uri: continue - state += [(uzbl.uri, uzbl.title),] + tabs = self.tabs.keys() + state = [] + for tab in list(self.notebook): + if tab not in tabs: continue + uzbl = self.tabs[tab] + if not uzbl.uri: continue + state += [(uzbl.uri, uzbl.title),] - session = {'curtab': self.notebook.get_current_page(), - 'tabs': state} + session = {'curtab': self.notebook.get_current_page(), + 'tabs': state} if config['json_session']: raw = json.dumps(session) @@ -1237,9 +1314,11 @@ class UzblTabbed: default_path = False strip = str.strip json_session = config['json_session'] + delete_loaded = False if session_file is None: default_path = True + delete_loaded = True session_file = config['session_file'] if not os.path.isfile(session_file): @@ -1294,6 +1373,10 @@ class UzblTabbed: for (index, (uri, title)) in enumerate(tabs): self.new_tab(uri=uri, title=title, switch=(curtab==index)) + # A saved session has been loaded now delete it. + if delete_loaded and os.path.exists(session_file): + os.remove(session_file) + # 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. @@ -1301,16 +1384,16 @@ class UzblTabbed: def quitrequest(self, *args): - '''Called by delete-event signal to kill all uzbl instances.''' + '''Attempt to close all uzbl instances nicely and exit.''' self._killed = True if config['save_session']: - if len(list(self.notebook)): + if len(list(self.notebook)) > 1: self.save_session() else: - # Notebook has no pages so delete session file if it exists. + # Notebook has one page open so delete the session file. if os.path.isfile(config['session_file']): os.remove(config['session_file']) @@ -1347,15 +1430,15 @@ class UzblTabbed: if __name__ == "__main__": # Read from the uzbl config into the global config dictionary. - readconfig(uzbl_config, config) + readconfig(UZBL_CONFIG, config) # Build command line parser - parser = OptionParser() - parser.add_option('-n', '--no-session', dest='nosession',\ + usage = "usage: %prog [OPTIONS] {URIS}..." + parser = OptionParser(usage=usage) + parser.add_option('-n', '--no-session', dest='nosession', action='store_true', help="ignore session saving a loading.") - group = OptionGroup(parser, "Note", "All other command line arguments are "\ - "interpreted as uris and loaded in new tabs.") - parser.add_option_group(group) + parser.add_option('-v', '--verbose', dest='verbose', + action='store_true', help='print verbose output.') # Parse command line options (options, uris) = parser.parse_args() @@ -1363,6 +1446,9 @@ if __name__ == "__main__": if options.nosession: config['save_session'] = False + if options.verbose: + config['verbose'] = True + if config['json_session']: try: import simplejson as json @@ -1373,6 +1459,10 @@ if __name__ == "__main__": "install the simplejson python module to remove this warning.") config['json_session'] = False + if config['verbose']: + import pprint + sys.stderr.write("%s\n" % pprint.pformat(config)) + uzbl = UzblTabbed() # All extra arguments given to uzbl_tabbed.py are interpreted as |