From: Mason Larobina Date: Thu, 30 Jul 2009 13:00:06 +0000 (+0800) Subject: Merge branch 'experimental' of git://github.com/Dieterbe/uzbl into experimental X-Git-Url: https://vcs.maemo.org/git/?a=commitdiff_plain;h=b67187957c0759946df602c3ca3123975b86f157;hp=a3adc8f8c27fb8246bc8aa584aab4a589625a70c;p=uzbl-mobile Merge branch 'experimental' of git://github.com/Dieterbe/uzbl into experimental Conflicts: uzbl.c --- diff --git a/examples/data/uzbl/scripts/uzbl_tabbed.py b/examples/data/uzbl/scripts/uzbl_tabbed.py index 9de6fba..6d458f5 100755 --- a/examples/data/uzbl/scripts/uzbl_tabbed.py +++ b/examples/data/uzbl/scripts/uzbl_tabbed.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # Uzbl tabbing wrapper using a fifo socket interface # Copyright (c) 2009, Tom Adams @@ -23,7 +23,7 @@ # Tom Adams # Wrote the original uzbl_tabbed.py as a proof of concept. # -# Chris van Dijk (quigybo) +# Chris van Dijk (quigybo) # Made signifigant headway on the old uzbl_tabbing.py script on the # uzbl wiki # @@ -39,6 +39,19 @@ # Fix for session restoration code. +# Dependencies: +# pygtk - python bindings for gtk. +# pango - python bindings needed for text rendering & layout in gtk widgets. +# pygobject - GLib's GObject bindings for python. +# +# Optional dependencies: +# simplejson - save uzbl_tabbed.py sessions & presets in json. +# +# 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. + + # Configuration: # Because this version of uzbl_tabbed is able to inherit options from your main # uzbl configuration file you may wish to configure uzbl tabbed from there. @@ -51,6 +64,7 @@ # tablist_top = 1 # gtk_tab_pos = (top|left|bottom|right) # switch_to_new_tabs = 1 +# capture_new_windows = 1 # # Tab title options: # tab_titles = 1 @@ -58,15 +72,18 @@ # max_title_len = 50 # show_ellipsis = 1 # -# Core options: +# Session options: # save_session = 1 +# json_session = 0 +# session_file = $HOME/.local/share/uzbl/session +# +# Inherited uzbl options: # fifo_dir = /tmp # socket_dir = /tmp # icon_path = $HOME/.local/share/uzbl/uzbl.png -# session_file = $HOME/.local/share/uzbl/session +# status_background = #303030 # # Window options: -# status_background = #303030 # window_size = 800,800 # # And the key bindings: @@ -79,6 +96,13 @@ # bind_goto_tab = gi_ # bind_goto_first = g< # bind_goto_last = g> +# bind_clean_slate = gQ +# +# Session preset key bindings: +# 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. @@ -121,7 +145,6 @@ # - check spelling. # - pass a uzbl socketid to uzbl_tabbed.py and have it assimilated into # the collective. Resistance is futile! -# - on demand store the session to file (need binding & command for that) import pygtk @@ -139,6 +162,8 @@ import socket import random import hashlib +from optparse import OptionParser, OptionGroup + pygtk.require('2.0') def error(msg): @@ -173,6 +198,7 @@ config = { 'tablist_top': True, # Display tab-list at top of window 'gtk_tab_pos': 'top', # Gtk tab position (top|left|bottom|right) 'switch_to_new_tabs': True, # Upon opening a new tab switch to it + 'capture_new_windows': True, # Use uzbl_tabbed to catch new windows # Tab title options 'tab_titles': True, # Display tab titles (else only tab-nums) @@ -180,27 +206,38 @@ config = { 'max_title_len': 50, # Truncate title at n characters 'show_ellipsis': True, # Show ellipsis when truncating titles - # Core options + # Session options 'save_session': True, # Save session in file when quit - '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'), + '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'), + # 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'), + 'status_background': "#303030", # Default background for all panels. + # Window options - 'status_background': "#303030", # Default background for all panels - 'window_size': "800,800", # width,height in pixels + 'window_size': "800,800", # width,height in pixels. - # Key bindings. + # Key bindings 'bind_new_tab': 'gn', # Open new tab. 'bind_tab_from_clip': 'gY', # Open tab from clipboard. 'bind_tab_from_uri': 'go _', # Open new tab and goto entered uri. 'bind_close_tab': 'gC', # Close tab. 'bind_next_tab': 'gt', # Next tab. 'bind_prev_tab': 'gT', # Prev tab. - 'bind_goto_tab': 'gi_', # Goto tab by tab-number (in title) - 'bind_goto_first': 'g<', # Goto first tab - 'bind_goto_last': 'g>', # Goto last tab + 'bind_goto_tab': 'gi_', # Goto tab by tab-number (in title). + '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. + + # Session preset key bindings + 'bind_save_preset': 'gsave _', # Save session to file %s. + 'bind_load_preset': 'gload _', # Load preset session from file %s. + 'bind_del_preset': 'gdel _', # Delete preset session %s. + 'bind_list_presets': 'glist', # List all session presets. # Add custom tab style definitions to be used by the tab colour policy # handler here. Because these are added to the config dictionary like @@ -271,9 +308,10 @@ def readconfig(uzbl_config, config): rawconfig = h.read() h.close() + configkeys, strip = config.keys(), str.strip for (key, value) in findsets(rawconfig): - key, value = key.strip(), value.strip() - if key not in config.keys(): continue + key, value = strip(key), strip(value) + if key not in configkeys: continue if isint(value): value = int(value) config[key] = value @@ -313,14 +351,14 @@ class UzblTabbed: '''Uzbl instance meta-data/meta-action object.''' def __init__(self, parent, tab, fifo_socket, socket_file, pid,\ - uri, switch): + uri, title, switch): self.parent = parent self.tab = tab self.fifo_socket = fifo_socket self.socket_file = socket_file self.pid = pid - self.title = config['new_tab_title'] + self.title = title self.uri = uri self.timers = {} self._lastprobe = 0 @@ -432,12 +470,10 @@ class UzblTabbed: self._fifos = {} self._timers = {} self._buffer = "" - - # Once a second is updated with the latest tabs' uris so that when the - # window is killed the session is saved. - self._tabsuris = [] - # And index of current page in self._tabsuris - self._curpage = 0 + self._killed = False + + # A list of the recently closed tabs + self._closed = [] # Holds metadata on the uzbl childen open. self.tabs = {} @@ -468,7 +504,7 @@ class UzblTabbed: self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path)) # Attach main window event handlers - self.window.connect("delete-event", self.quit) + self.window.connect("delete-event", self.quitrequest) # Create tab list if config['show_tablist']: @@ -531,6 +567,10 @@ class UzblTabbed: 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) + + # 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): @@ -558,8 +598,8 @@ class UzblTabbed: if fifo_socket in self._fifos.keys(): fd, watchers = self._fifos[fifo_socket] os.close(fd) - for watcherid in watchers.keys(): - gobject.source_remove(watchers[watcherid]) + for (watcherid, gid) in watchers.items(): + gobject.source_remove(gid) del watchers[watcherid] del self._fifos[fifo_socket] @@ -581,7 +621,10 @@ class UzblTabbed: 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) @@ -597,22 +640,19 @@ class UzblTabbed: 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 = {} - uriinventory = [] tabskeys = self.tabs.keys() notebooklist = list(self.notebook) for tab in notebooklist: if tab not in tabskeys: continue uzbl = self.tabs[tab] - uriinventory.append(uzbl.uri) uzbl.probe() if uzbl._socket: - sockd[uzbl._socket] = uzbl - - self._tabsuris = uriinventory - self._curpage = self.notebook.get_current_page() + sockd[uzbl._socket] = uzbl sockets = sockd.keys() (reading, _, errors) = select.select(sockets, [], sockets, 0) @@ -741,6 +781,50 @@ class UzblTabbed: 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) + + elif cmd[1] == "load": + path = os.path.join(config['saved_sessions_dir'], cmd[2]) + self.load_session(path) + + elif cmd[1] == "del": + path = os.path.join(config['saved_sessions_dir'], cmd[2]) + if os.path.isfile(path): + os.remove(path) + + 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: + if not os.path.isdir(config['saved_sessions_dir']): + js = "js alert('No saved presets.');" + uzbl.send(js) + + else: + listdir = os.listdir(config['saved_sessions_dir']) + listdir = "\\n".join(listdir) + js = "js alert('Session presets:\\n\\n%s');" % listdir + uzbl.send(js) + + else: + error("parse_command: unknown tab pid.") + + else: + error("parse_command: unknown parse command %r"\ + % ' '.join(cmd)) + + elif cmd[0] == "clean": + self.clean_slate() + else: error("parse_command: unknown command %r" % ' '.join(cmd)) @@ -748,14 +832,14 @@ class UzblTabbed: def get_tab_by_pid(self, pid): '''Return uzbl instance by pid.''' - for tab in self.tabs.keys(): - if self.tabs[tab].pid == pid: - return self.tabs[tab] + for (tab, uzbl) in self.tabs.items(): + if uzbl.pid == pid: + return uzbl return False - def new_tab(self, uri='', switch=None): + def new_tab(self, uri='', title='', switch=None): '''Add a new tab to the notebook and start a new instance of uzbl. Use the switch option to negate config['switch_to_new_tabs'] option when you need to load multiple tabs at a time (I.e. like when @@ -766,6 +850,7 @@ class UzblTabbed: tab.show() self.notebook.append_page(tab) sid = tab.get_id() + uri = uri.strip() fifo_filename = 'uzbl_fifo_%s_%0.2d' % (self.wid, pid) fifo_socket = os.path.join(config['fifo_dir'], fifo_filename) @@ -774,14 +859,16 @@ class UzblTabbed: if switch is None: switch = config['switch_to_new_tabs'] + + if not title: + title = config['new_tab_title'] + uzbl = self.UzblInstance(self, tab, fifo_socket, socket_file, pid,\ + uri, title, switch) - # Create meta-instance and spawn child - if len(uri.strip()): - uri = '--uri %s' % uri + if len(uri): + uri = "--uri %r" % uri - uzbl = self.UzblInstance(self, tab, fifo_socket, socket_file, pid,\ - uri, switch) self.tabs[tab] = uzbl cmd = 'uzbl -s %s -n %s_%0.2d %s &' % (sid, self.wid, pid, uri) subprocess.Popen([cmd], shell=True) # TODO: do i need close_fds=True ? @@ -794,17 +881,33 @@ class UzblTabbed: self.update_tablist() + def clean_slate(self): + '''Close all open tabs and open a fresh brand new one.''' + + self.new_tab() + tabs = self.tabs.keys() + for tab in list(self.notebook)[:-1]: + 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 instance.''' binds = [] - bind_format = 'bind %s = sh "echo \\\"%s\\\" > \\\"%s\\\""' - bind = lambda key, action: binds.append(bind_format % (key, action, \ + bind_format = r'bind %s = sh "echo \"%s\" > \"%s\""' + bind = lambda key, action: binds.append(bind_format % (key, action,\ + self.fifo_socket)) + + sets = [] + set_format = r'set %s = sh \"echo \\"%s\\" > \\"%s\\""' + set = lambda key, action: binds.append(set_format % (key, action,\ self.fifo_socket)) - # Keys are defined in the config section - # bind ( key , command back to fifo ) + # Bind definitions here + # bind(key, command back to fifo) bind(config['bind_new_tab'], 'new') bind(config['bind_tab_from_clip'], 'newfromclip') bind(config['bind_tab_from_uri'], 'new %s') @@ -814,9 +917,21 @@ class UzblTabbed: bind(config['bind_goto_tab'], 'goto %s') 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') + bind(config['bind_list_presets'], 'preset list %d' % uzbl.pid) - # uzbl.send via socket or uzbl.write via fifo, I'll try send. - uzbl.send("\n".join(binds)) + # Set definitions here + # 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)) def goto_tab(self, index): @@ -902,9 +1017,10 @@ class UzblTabbed: if tab in self.tabs.keys(): uzbl = self.tabs[tab] - for timer in uzbl.timers.keys(): + for (timer, gid) in uzbl.timers.items(): error("tab_closed: removing timer %r" % timer) - gobject.source_remove(uzbl.timers[timer]) + gobject.source_remove(gid) + del uzbl.timers[timer] if uzbl._socket: uzbl._socket.close() @@ -913,9 +1029,16 @@ class UzblTabbed: uzbl._fifoout = [] uzbl._socketout = [] uzbl._kill = True + self._closed.append((uzbl.uri, uzbl.title)) + self._closed = self._closed[-10:] del self.tabs[tab] 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) + self.quit() self.update_tablist() @@ -995,43 +1118,154 @@ class UzblTabbed: return True - def quit(self, *args): - '''Cleanup the application and quit. Called by delete-event signal.''' + def save_session(self, session_file=None, session=None): + '''Save the current session to file for restoration on next load.''' - for fifo_socket in self._fifos.keys(): - fd, watchers = self._fifos[fifo_socket] - os.close(fd) - for watcherid in watchers.keys(): - gobject.source_remove(watchers[watcherid]) - del watchers[watcherid] + strip = str.strip - del self._fifos[fifo_socket] + 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),] + + session = {'curtab': self.notebook.get_current_page(), + 'tabs': state} + + if config['json_session']: + raw = json.dumps(session) - for timerid in self._timers.keys(): - gobject.source_remove(self._timers[timerid]) - del self._timers[timerid] + else: + lines = ["curtab = %d" % session['curtab'],] + for (uri, title) in session['tabs']: + 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): + os.makedirs(dirname) + + h = open(session_file, 'w') + h.write(raw) + h.close() + - if os.path.exists(self.fifo_socket): - os.unlink(self.fifo_socket) - print "Unlinked %s" % self.fifo_socket + def load_session(self, session_file=None): + '''Load a saved session from file.''' + + default_path = False + strip = str.strip + json_session = config['json_session'] - if config['save_session']: + if session_file is None: + default_path = True session_file = config['session_file'] - if len(self._tabsuris): - if not os.path.isfile(session_file): - dirname = os.path.dirname(session_file) - if not os.path.isdir(dirname): - os.makedirs(dirname) - sessionstr = '\n'.join(self._tabsuris) - h = open(session_file, 'w') - h.write('current = %s\n%s' % (self._curpage, sessionstr)) - h.close() + if not os.path.isfile(session_file): + return False + + h = open(session_file, 'r') + raw = h.read() + h.close() + if json_session: + if sum([1 for s in raw.split("\n") if strip(s)]) != 1: + error("Warning: The session file %r does not look json. "\ + "Trying to load it as a non-json session file."\ + % session_file) + json_session = False + + if json_session: + try: + session = json.loads(raw) + curtab, tabs = session['curtab'], session['tabs'] + + except: + error("Failed to load jsonifed session from %r"\ + % session_file) + return None + + else: + tabs = [] + strip = str.strip + curtab, tabs = 0, [] + lines = [s for s in raw.split("\n") if strip(s)] + if len(lines) < 2: + error("Warning: The non-json session file %r looks invalid."\ + % session_file) + return None + + try: + for line in lines: + if line.startswith("curtab"): + curtab = int(line.split()[-1]) + + else: + uri, title = line.split("\t",1) + tabs += [(strip(uri), strip(title)),] + + except: + error("Warning: failed to load session file %r" % session_file) + 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 + # 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 + + if config['save_session']: + if len(list(self.notebook)): + self.save_session() else: # Notebook has no pages so delete session file if it exists. if os.path.isfile(session_file): os.remove(session_file) + + for (tab, uzbl) in self.tabs.items(): + uzbl.send("exit") + + + 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] + + del self._fifos[fifo_socket] + + for (timerid, gid) in self._timers.items(): + gobject.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 gtk.main_quit() @@ -1041,32 +1275,36 @@ if __name__ == "__main__": # Read from the uzbl config into the global config dictionary. readconfig(uzbl_config, config) - uzbl = UzblTabbed() + # Build command line parser + parser = OptionParser() + 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) - if os.path.isfile(os.path.expandvars(config['session_file'])): - h = open(os.path.expandvars(config['session_file']),'r') - lines = [line.strip() for line in h.readlines()] - h.close() - current = 0 - urls = [] - for line in lines: - if line.startswith("current"): - current = int(line.split()[-1]) + # Parse command line options + (options, uris) = parser.parse_args() - else: - urls.append(line.strip()) + if options.nosession: + config['save_session'] = False - for (index, url) in enumerate(urls): - if current == index: - uzbl.new_tab(line, True) + if config['json_session']: + try: + import simplejson as json - else: - uzbl.new_tab(line, False) + except: + error("Warning: json_session set but cannot import the python "\ + "module simplejson. Fix: \"set json_session = 0\" or "\ + "install the simplejson python module to remove this warning.") + config['json_session'] = False - if not len(urls): - uzbl.new_tab() + uzbl = UzblTabbed() - else: - uzbl.new_tab() + # All extra arguments given to uzbl_tabbed.py are interpreted as + # web-locations to opened in new tabs. + lasturi = len(uris)-1 + for (index,uri) in enumerate(uris): + uzbl.new_tab(uri, switch=(index==lasturi)) uzbl.run() diff --git a/uzbl.c b/uzbl.c index 8c3674b..e2148fd 100644 --- a/uzbl.c +++ b/uzbl.c @@ -2929,10 +2929,13 @@ main (int argc, char* argv[]) { uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h); gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v); - if(uzbl.gui.geometry) - cmd_set_geometry(); - else - retrieve_geometry(); + /* Check uzbl is in window mode before getting/setting geometry */ + if (uzbl.gui.main_window) { + if(uzbl.gui.geometry) + cmd_set_geometry(); + else + retrieve_geometry(); + } gchar *uri_override = (uzbl.state.uri ? g_strdup(uzbl.state.uri) : NULL); if (argc > 1 && !uzbl.state.uri)