From 6a2b62a798bbaca10ded2d95d739b877b735b10e Mon Sep 17 00:00:00 2001 From: Simon Lipp Date: Mon, 28 Dec 2009 23:49:35 +0100 Subject: uzbl-tabbed changes : * Remove bind_* options: does not work with current uzbl revisions, and a better implementation will come soon. * Remove reading of uzbl config file (same reason) * Better IPC handling (to sumarize: less periodic polling): * Better separation between network layers (communication is in SocketClient, protocol in UzblInstance, user interface in UzblTabbed) * use io_add_watch instead of select for reading uzbl events * does not use a generated hash to separate events, but the LF character * get rid of all the flush()ing logic * does not probe periodically for the title anymore; use uzbl events * create a /tmp/uzbltabbed_socket instead of polling the /tmp/uzbl_socket_* socket --- examples/data/uzbl/scripts/uzbl-tabbed | 442 ++++++++++++++------------------- 1 file changed, 183 insertions(+), 259 deletions(-) (limited to 'examples') diff --git a/examples/data/uzbl/scripts/uzbl-tabbed b/examples/data/uzbl/scripts/uzbl-tabbed index 6bab849..9d8e1e6 100755 --- a/examples/data/uzbl/scripts/uzbl-tabbed +++ b/examples/data/uzbl/scripts/uzbl-tabbed @@ -96,25 +96,6 @@ # window_size = 800,800 # verbose = 0 # -# And the key bindings: -# bind_new_tab = gn -# bind_tab_from_clip = gY -# bind_tab_from_uri = go _ -# bind_close_tab = gC -# bind_next_tab = gt -# bind_prev_tab = gT -# bind_goto_tab = gi_ -# 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 -# # And uzbl_tabbed.py takes care of the actual binding of the commands via each # instances fifo socket. # @@ -209,12 +190,6 @@ 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 = { # Tab options @@ -249,25 +224,6 @@ config = { 'window_size': "800,800", # width,height in pixels. 'verbose': False, # Print verbose output. - # 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_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. - '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 # any other uzbl_tabbed configuration option remember that they can @@ -371,108 +327,132 @@ def escape(s): return s -def gen_endmarker(): - '''Generates a random md5 for socket message-termination endmarkers.''' +class SocketClient: + '''Represents a Uzbl instance, which is not necessarly linked with a UzblInstance''' + + # List of UzblInstance objects not already linked with a SocketClient + instances_queue = {} + + def __init__(self, socket): + self._buffer = "" + self._socket = socket + self._watchers = [io_add_watch(socket, IO_IN, self._socket_recv),\ + io_add_watch(socket, IO_HUP, self._socket_closed)] + self.uzbl = None + + + def _socket_recv(self, fd, condition): + '''Data available on socket, process it''' + + self._feed(self._socket.recv(1024)) #TODO: is io_add_watch edge or level-triggered ? + return True + + + def _socket_closed(self, fd, condition): + '''Remote client exited''' + self.uzbl.close() + return False - return hashlib.md5(str(random.random()*time.time())).hexdigest() + + def _feed(self, data): + '''An Uzbl instance sent some data, parse it''' + + self._buffer += data + if self.uzbl: + if "\n" in self._buffer: + cmds = self._buffer.split("\n") + + if cmds[-1]: # Last command has been received incomplete, don't process it + self._buffer, cmds = cmds[-1], cmds[:-1] + else: + self._buffer = "" + + for cmd in cmds: + if cmd: + self.uzbl.parse_command(cmd) + else: + name = re.findall('^EVENT \[(\d+-\d+)\] INSTANCE_START \d+$', self._buffer, re.M) + uzbl = self.instances_queue.get(name[0]) + if uzbl: + del self.instances_queue[name[0]] + self.uzbl = uzbl + self.uzbl.got_socket(self) + self._feed("") + + def send(self, data): + '''Child socket send function.''' + + self._socket.send(data + "\n") + + def close(self): + '''Close the connection''' + + if self._socket: + self._socket.close() + self._socket = None + map(source_remove, self._watchers) + self._watchers = [] class UzblInstance: '''Uzbl instance meta-data/meta-action object.''' - def __init__(self, parent, tab, socket_file, pid, uri, title, switch): + def __init__(self, parent, tab, name, uri, title, switch): self.parent = parent self.tab = tab - self.socket_file = socket_file - self.pid = pid + self.name = name self.title = title self.uri = uri - self.timers = {} - self._lastprobe = 0 - self._socketout = [] - self._socket = None - self._buffer = "" - # Switch to tab after loading - self._switch = switch - # socket files exists and socket connected. - self._connected = False - # The kill switch - self._kill = False - - # Message termination endmarker. - self._marker = gen_endmarker() - - # Gen probe commands string - probes = [] - probe = probes.append - probe('print uri %d @uri %s' % (self.pid, self._marker)) - probe('print title %d @@ %s' % (self.pid,\ - self._marker)) - self._probecmds = '\n'.join(probes) - - # Enqueue keybinding config for child uzbl instance - self.parent.config_uzbl(self) + self._client = None + self._switch = switch # Switch to tab after loading ? - def flush(self, timer_call=False): - '''Flush messages from the socket-out queue.''' + def got_socket(self, client): + '''Uzbl instance is now connected''' + self._client = client - if self._kill: - if self._socket: - self._socket.close() - self._socket = None + self.parent.tabs[self.tab] = self + self.parent.update_tablist() - error("Flush called on dead tab.") - return False - - if len(self._socketout): - if not self._socket and os.path.exists(self.socket_file): - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect(self.socket_file) - self._socket = sock + self.parent.config_uzbl(self) - if self._socket: - while len(self._socketout): - msg = self._socketout.pop(0) - self._socket.send("%s\n"%msg) + if self._switch: + tabs = list(self.parent.notebook) + tabid = tabs.index(self.tab) + self.parent.goto_tab(tabid) - if not self._connected and timer_call: - if not self._socketout: - self._connected = True - if timer_call in self.timers.keys(): - source_remove(self.timers[timer_call]) - del self.timers[timer_call] + def set(self, key, val): + ''' Send the SET command to Uzbl ''' - if self._switch: - self.grabfocus() + self._client.send('set %s = %s') #TODO: escape chars ? - return len(self._socketout) + def exit(sedf): + ''' Ask the Uzbl instance to close ''' - def grabfocus(self): - '''Steal parent focus and switch the notebook to my own tab.''' + self._client.send('exit') #TODO: escape chars ? - tabs = list(self.parent.notebook) - tabid = tabs.index(self.tab) - self.parent.goto_tab(tabid) + def parse_command(self, cmd): + ''' Parse event givent by the Uzbl instance ''' - def probe(self): - '''Probes the client for information about its self.''' + type, _, args = cmd.split(" ", 2) + if type == "EVENT": + type, args = args.split(" ", 1) + if type == "TITLE_CHANGED": + self.title = args + self.parent.update_tablist() - if self._connected: - self.send(self._probecmds) - self._lastprobe = time.time() + def close(self): + '''The remote instance exited''' - def send(self, msg): - '''Child socket send function.''' + if self._client: + self._client.close() + self._client = None - self._socketout.append(msg) - # Flush messages from queue if able. - return self.flush() class UzblTabbed: '''A tabbed version of uzbl using gtk.Notebook''' @@ -480,9 +460,6 @@ class UzblTabbed: def __init__(self): '''Create tablist, window and notebook.''' - # Store information about the applications fifo_socket. - self._fifo = None - self._timers = {} self._buffer = "" self._killed = False @@ -493,6 +470,9 @@ class UzblTabbed: # Holds metadata on the uzbl childen open. self.tabs = {} + # Uzbl sockets (socket => SocketClient) + self.clients = {} + # Generates a unique id for uzbl socket filenames. self.next_pid = counter().next @@ -577,12 +557,18 @@ 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) - # Now initialise the fifo socket at self.fifo_socket - self.init_fifo_socket() + # Store information about the applications fifo and socket. + fifo_filename = 'uzbltabbed_%d.fifo' % os.getpid() + socket_filename = 'uzbltabbed_%d.socket' % os.getpid() + self._fifo = None + self._socket = None + self.fifo_path = os.path.join(config['fifo_dir'], fifo_filename) + self.socket_path = os.path.join(config['socket_dir'], socket_filename) + + # Now initialise the fifo and the socket + self.init_fifo() + self.init_socket() # If we are using sessions then load the last one if it exists. if config['save_session']: @@ -592,7 +578,7 @@ class UzblTabbed: def run(self): '''UzblTabbed main function that calls the gtk loop.''' - if not len(self.tabs): + if not self.clients and not SocketClient.instances_queue and not self.tabs: self.new_tab() gtk_refresh = int(config['gtk_refresh']) @@ -603,10 +589,6 @@ class UzblTabbed: timerid = timeout_add(gtk_refresh, self.update_tablist) self._timers["update-tablist"] = timerid - # Probe clients every second for window titles and location - 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)) @@ -620,7 +602,7 @@ class UzblTabbed: error("encounted error %r" % sys.exc_info()[1]) # Unlink fifo socket - self.unlink_fifo_socket() + self.unlink_fifo() # Attempt to close all uzbl instances nicely. self.quitrequest() @@ -651,40 +633,72 @@ class UzblTabbed: self.quitrequest() - def init_fifo_socket(self): - '''Create interprocess communication fifo socket.''' + def init_socket(self): + '''Create interprocess communication socket.''' + + def accept(sock, condition): + '''A new uzbl instance was created''' + + client, _ = sock.accept() + self.clients[client] = SocketClient(client) + + return True + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.bind(self.socket_path) + sock.listen(1) + + # Add event handler for IO_IN event. + self._socket = (sock, io_add_watch(sock, IO_IN, accept)) + + echo("[socket] listening at %r" % self.socket_path) + + # Add atexit register to destroy the socket on program termination. + atexit.register(self.close_socket) + + + def close_socket(self): + '''Close the socket when closing the application''' + + (fd, watcher) = self._socket + source_remove(watcher) + fd.close() + + + def init_fifo(self): + '''Create interprocess communication fifo.''' - 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) + if os.path.exists(self.fifo_path): + if not os.access(self.fifo_path, os.F_OK | os.R_OK | os.W_OK): + os.mkfifo(self.fifo_path) else: - basedir = os.path.dirname(self.fifo_socket) + basedir = os.path.dirname(self.fifo_path) if not os.path.exists(basedir): os.makedirs(basedir) - os.mkfifo(self.fifo_socket) + os.mkfifo(self.fifo_path) # Add event handlers for IO_IN & IO_HUP events. self.setup_fifo_watchers() - echo("listening at %r" % self.fifo_socket) + echo("[fifo] listening at %r" % self.fifo_path) - # Add atexit register to destroy the socket on program termination. - atexit.register(self.unlink_fifo_socket) + # Add atexit register to destroy the fifo on program termination. + atexit.register(self.unlink_fifo) - def unlink_fifo_socket(self): + def unlink_fifo(self): '''Unlink the fifo socket. Note: This function is called automatically on exit by an atexit register.''' - # Make sure the fifo_socket fd is closed. + # Make sure the fifo fd is closed. self.close_fifo() - # 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) + # And unlink if the real fifo exists. + if os.path.exists(self.fifo_path): + os.unlink(self.fifo_path) + echo("unlinked %r" % self.fifo_path) def close_fifo(self): @@ -707,10 +721,10 @@ class UzblTabbed: '''Open fifo socket fd and setup gobject IO_IN & IO_HUP event handlers.''' - # Close currently open fifo_socket fd and kill all watchers + # Close currently open fifo fd and kill all watchers self.close_fifo() - fd = os.open(self.fifo_socket, os.O_RDONLY | os.O_NONBLOCK) + fd = os.open(self.fifo_path, 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),\ @@ -749,43 +763,6 @@ class UzblTabbed: return True - def probe_clients(self): - '''Probe all uzbl clients for up-to-date window titles and uri's.''' - - save_session = config['save_session'] - - sockd = {} - tabskeys = self.tabs.keys() - notebooklist = list(self.notebook) - - for tab in notebooklist: - if tab not in tabskeys: continue - uzbl = self.tabs[tab] - uzbl.probe() - if uzbl._socket: - sockd[uzbl._socket] = uzbl - - sockets = sockd.keys() - (reading, _, errors) = select.select(sockets, [], sockets, 0) - - for sock in reading: - uzbl = sockd[sock] - uzbl._buffer = sock.recv(1024).replace('\n',' ') - temp = uzbl._buffer.split(uzbl._marker) - 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.''' @@ -858,7 +835,7 @@ class UzblTabbed: elif cmd[0] in ["title", "uri"]: if len(cmd) > 2: - uzbl = self.get_tab_by_pid(int(cmd[1])) + uzbl = self.get_tab_by_name(int(cmd[1])) if uzbl: old = getattr(uzbl, cmd[0]) new = ' '.join(cmd[2:]) @@ -867,7 +844,7 @@ class UzblTabbed: self.update_tablist() else: - error("parse_command: no uzbl with pid %r" % int(cmd[1])) + error("parse_command: no uzbl with name %r" % int(cmd[1])) elif cmd[0] == "preset": if len(cmd) < 3: @@ -890,20 +867,20 @@ class UzblTabbed: error("parse_command: preset %r does not exist." % path) elif cmd[1] == "list": - uzbl = self.get_tab_by_pid(int(cmd[2])) + uzbl = self.get_tab_by_name(int(cmd[2])) if uzbl: if not os.path.isdir(config['saved_sessions_dir']): js = "js alert('No saved presets.');" - uzbl.send(js) + uzbl._client.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) + uzbl._client.send(js) else: - error("parse_command: unknown tab pid.") + error("parse_command: unknown tab name.") else: error("parse_command: unknown parse command %r"\ @@ -922,11 +899,11 @@ class UzblTabbed: error("parse_command: unknown command %r" % ' '.join(cmd)) - def get_tab_by_pid(self, pid): - '''Return uzbl instance by pid.''' + def get_tab_by_name(self, name): + '''Return uzbl instance by name.''' for (tab, uzbl) in self.tabs.items(): - if uzbl.pid == pid: + if uzbl.name == name: return uzbl return False @@ -938,15 +915,12 @@ class UzblTabbed: when you need to load multiple tabs at a time (I.e. like when restoring a session from a file).''' - pid = self.next_pid() tab = gtk.Socket() tab.show() self.notebook.append_page(tab) sid = tab.get_id() uri = uri.strip() - - socket_filename = 'uzbl_socket_%s_%0.2d' % (self.wid, pid) - socket_file = os.path.join(config['socket_dir'], socket_filename) + name = "%d-%d" % (os.getpid(), self.next_pid()) if switch is None: switch = config['switch_to_new_tabs'] @@ -954,22 +928,12 @@ class UzblTabbed: if not title: title = config['new_tab_title'] - uzbl = self.UzblInstance(self, tab, socket_file, pid,\ - uri, title, switch) - - if len(uri): - uri = "--uri %r" % uri - - self.tabs[tab] = uzbl - cmd = 'uzbl-browser -s %s -n %s_%0.2d %s &' % (sid, self.wid, pid, uri) - subprocess.Popen([cmd], shell=True) # TODO: do i need close_fds=True ? - - # Add gobject timer to make sure the config is pushed when socket - # has been created. - timerid = timeout_add(100, uzbl.flush, "flush-initial-config") - uzbl.timers['flush-initial-config'] = timerid + cmd = ['uzbl-browser', '-n', name, '-s', str(sid), + '--connect-socket', self.socket_path, '--uri', uri] + subprocess.Popen(cmd) # TODO: do i need close_fds=True ? - self.update_tablist() + uzbl = UzblInstance(self, tab, name, uri, title, switch) + SocketClient.instances_queue[name] = uzbl def clean_slate(self): @@ -980,48 +944,17 @@ class UzblTabbed: for tab in list(self.notebook)[:-1]: if tab not in tabs: continue uzbl = self.tabs[tab] - uzbl.send("exit") + uzbl.exit() def config_uzbl(self, uzbl): '''Send bind commands for tab new/close/next/prev to a uzbl instance.''' - binds = [] - 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)) - - # 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') - bind(config['bind_close_tab'], 'close') - bind(config['bind_next_tab'], 'next') - bind(config['bind_prev_tab'], 'prev') - 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') - 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) - bind(config['bind_exit'], 'exit') - # 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)) + uzbl.set("new_window", r'new $8') def goto_tab(self, index): @@ -1107,17 +1040,8 @@ class UzblTabbed: if tab in self.tabs.keys(): uzbl = self.tabs[tab] - for (timer, gid) in uzbl.timers.items(): - error("tab_closed: removing timer %r" % timer) - source_remove(gid) - del uzbl.timers[timer] - - if uzbl._socket: - uzbl._socket.close() - uzbl._socket = None + uzbl.close() - uzbl._socketout = [] - uzbl._kill = True self._closed.append((uzbl.uri, uzbl.title)) self._closed = self._closed[-10:] del self.tabs[tab] @@ -1375,7 +1299,7 @@ class UzblTabbed: os.remove(config['session_file']) for (tab, uzbl) in self.tabs.items(): - uzbl.send("exit") + uzbl.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 @@ -1390,7 +1314,7 @@ class UzblTabbed: # Close the fifo socket, remove any gobject io event handlers and # delete socket. - self.unlink_fifo_socket() + self.unlink_fifo() # Remove all gobject timers that are still ticking. for (timerid, gid) in self._timers.items(): -- cgit v1.2.3