#!/usr/bin/python ######################################################################## # # ffremote.py # # Open a web page in your existing Firefox web browser. If you are # using multiple Firefox instances at the same time (each with a # different profile), use the instance matching the desired profile. # # This program is in the public domain - do whatever you want with it. # # David Simmons # September 10, 2009 # ######################################################################## # # NOTES: # # This script requires the python-xlib library, available in Ubuntu # with "sudo apt-get install python-xlib" or from this web site: # http://python-xlib.sourceforge.net/ # # CAVEATS: # # This script uses a kludgy method for obtaining a lock during # communication with the browser. The correct way would be to invoke # XGrabServer while the lock is being established, to avoid a race # condition. However, XGrabServer makes me nervous. # # When searching for Firefox windows, this code only scans the direct # children of the root window. It may be better to recurse into the # window tree in case some fancy window manager has reparented # toplevel windows. The current scheme seems to work fine in my # GNOME/metacity environment, though. # # I've only tested this with Firefox 3.5.3. # ######################################################################## ######################################################################## # configuration ######################################################################## # set this variable to the name of your preferred firefox profile TARGET_PROFILE = "default" # set this to the full path to firefox, so the script can invoke # firefox directly when it can't find a suitable existing window PATH_TO_FIREFOX = "/usr/bin/firefox" # if another application is controlling firefox, retry every # LOCK_RETRY_INTERVAL seconds, until LOCK_OVERRIDE_TIME seconds # have passed. you probably don't need to change this. LOCK_RETRY_INTERVAL = 0.5 LOCK_OVERRIDE_TIME = 5 ######################################################################## import sys import os import time from Xlib import X, display, Xatom from socket import gethostname # # class Firefox # # This class represents a single Firefox instance. It knows how to # retrieve information about the instance, and issue commands. # class Firefox: class State: def __init__(self, properties): self.properties = properties self.reset() def reset(self): for i in self.properties: self.__dict__[i] = None def __init__(self, properties, atoms, xdisplay, window): self.properties = properties self.atoms = atoms self.xdisplay = xdisplay self.window = window self.state = Firefox.State(properties) self.update_state() def update_state(self): self.state.reset() for i in self.properties: if self.atoms[i]: property = self.window.get_property( self.atoms[i], Xatom.STRING, 0, 1024 ) if property: self.state.__dict__[i] = property.value def show(self): self.update_state() self.show_current_state() def show_current_state(self): print '0x%-10x %-20s %-15s %-10s %-10s %-10s %-20s %s' % ( self.window.id, self.state.program+" "+self.state.version, self.state.profile, self.state.user, self.state.command, self.state.response, self.state.lock, self.state.commandline ) def lock(self): lock_value = "pid%d@%s" % (os.getpid(), gethostname()) # check to see if another application has locked this instance lock_property = self.window.get_property( self.atoms["lock"], Xatom.STRING, 0, 128 ) lock_retry_elapsed = 0 while lock_property: if lock_retry_elapsed == 0: print "firefox locked by %s -- waiting..." % ( lock_property.value ) time.sleep(LOCK_RETRY_INTERVAL) lock_retry_elapsed += LOCK_RETRY_INTERVAL if lock_retry_elapsed >= LOCK_OVERRIDE_TIME: print "overriding lock after %d seconds." % LOCK_OVERRIDE_TIME break lock_property = self.window.get_property( self.atoms["lock"], Xatom.STRING, 0, 128 ) # mark our territory self.window.change_property( self.atoms["lock"], Xatom.STRING, 8, lock_value ) self.xdisplay.sync() def unlock(self): # clear the lock property property = self.window.get_property( self.atoms["lock"], Xatom.STRING, 0, 128, True # delete after getting ); self.xdisplay.sync() def send_command(self, command): self.lock() self.window.change_property( self.atoms["command"], Xatom.STRING, 8, command ) self.unlock() # # class FirefoxScanner # # This class scans the toplevel windows of the X11 display, # looking for Firefox instances. # class FirefoxScanner: properties = { "version": "_MOZILLA_VERSION", "lock": "_MOZILLA_LOCK", "command": "_MOZILLA_COMMAND", "response": "_MOZILLA_RESPONSE", "user": "_MOZILLA_USER", "profile": "_MOZILLA_PROFILE", "program": "_MOZILLA_PROGRAM", "commandline": "_MOZILLA_COMMANDLINE", } def __init__(self, xdisplay): self.xdisplay = xdisplay self.root = xdisplay.screen().root self.initialize_atoms() def initialize_atoms(self): self.atoms = {}; for i in self.properties: atom = self.xdisplay.get_atom(self.properties[i], 1) if atom: self.atoms[i] = atom def is_firefox(self, window): # query the _MOZILLA_PROGRAM property on this window p = window.get_property(self.atoms["program"], Xatom.STRING, 0, 1000) # Other Mozilla programs (like Thunderbird) will also have these # properties, so we make sure to only regard "firefox". if p and (p.value == "firefox"): return 1 else: return 0 def scan(self): self.instances = [] for window in self.root.query_tree().children: if self.is_firefox(window): self.instances.append( Firefox(self.properties,self.atoms,xdisplay,window) ) def show(self): self.xdisplay.sync() print '%-12s %-20s %-15s %-10s %-10s %-10s %-20s %s' % ( 'window id', 'program/ver', 'profile', 'user', 'command', 'response', 'lock', 'commandline' ); print '%-12s %-20s %-15s %-10s %-10s %-10s %-20s %s' % ( '------------', '--------------------', '---------------', '----------', '----------', '----------', '--------------------', '---------------', ); for i in self.instances: i.show_current_state() def get_firefox(self, profile): for i in self.instances: if i.state.profile == profile: return i return None # # code that isn't cool enough to be part of a class. # # if this script is not executed with one and only one # argument after the program name, or if the one argument # starts with a dash, then we run the command-line with # firefox as-is. if (len(sys.argv) != 2) or (sys.argv[1].startswith("-")): sys.argv[0] = PATH_TO_FIREFOX os.execv(PATH_TO_FIREFOX, sys.argv) os._exit(0) url = sys.argv[1] # scan for the desired firefox instance xdisplay = display.Display() scanner = FirefoxScanner(xdisplay); scanner.scan() #scanner.show() firefox = scanner.get_firefox(TARGET_PROFILE); # if no desired firefox instance was found, run a new # instance of firefox, using the desired profile if not firefox: args = [ PATH_TO_FIREFOX, '-no-remote', '-P', TARGET_PROFILE, url ] os.execv(PATH_TO_FIREFOX, args) os._exit(0) # send the URL to the desired firefox instance firefox.send_command("openurl("+url+")")