aboutsummaryrefslogtreecommitdiffhomepage
path: root/examples/data/uzbl/scripts
diff options
context:
space:
mode:
authorGravatar Dieter Plaetinck <dieter@plaetinck.be>2009-09-12 12:37:58 +0200
committerGravatar Dieter Plaetinck <dieter@plaetinck.be>2009-09-12 12:37:58 +0200
commit8d4a4fee7b5ea9fa39509c9dda1dc66834ce11a1 (patch)
tree1e3820ad1a016fb2d89a847bad9a71260b71ea4a /examples/data/uzbl/scripts
parent6e9d1499e263b488336a877fd7b1acbed2551d92 (diff)
parent1431c68c54b95a3da923e491eb4b38cc00b0a89e (diff)
Merge branch 'experimental' into event-messages
Diffstat (limited to 'examples/data/uzbl/scripts')
-rwxr-xr-xexamples/data/uzbl/scripts/cookie_daemon.py260
-rwxr-xr-xexamples/data/uzbl/scripts/uzbl_tabbed.py228
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('&#10;'.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