#!/usr/bin/python import dbus, sys, os, time import traceback sys.path.append("/usr/share/system-config-printer") import cups, cupshelpers, ppds from syslog import * class HalPrinter: def __init__(self): self.get_properties() self.uris = None self.hp_fax_uris = None try: self.cups_connection = cups.Connection() except RuntimeError, e: syslog (LOG_ERR, "Unable to connect to CUPS: '%s'. Is CUPS running?" % e) os.exit (1) def get_properties(self): self.properties = {} for key, value in os.environ.iteritems(): if key.startswith("HAL_PROP_"): name = key[9:].lower().replace("_", '.') self.properties[name] = value self.uid = os.getenv("UDI", "") self.read() def read(self): p = self.properties self.make = (p.get("printer.vendor", "") or p.get("usb.vendor", "Unknown")) self.model = (p.get("printer.product", "") or p.get("usb.product", "Unknown")) self.description = p.get("printer.description", "") self.name = self.get_name() self.faxname = self.name + "_fax" self.commandsets = p.get('printer.commandset', '').split('\t') def get_name(self): # XXX check for unallowed chars if self.properties.has_key("usb.port_number"): name = "%s-%s" % (self.model, self.properties["usb.port_number"]) else: name = self.model name = name.replace(" ", "_") name = name.replace("/", "_") return name.replace("#", "_") def get_cups_uris(self, removed=False): if self.uris != None: return self.uris uris=["hal://%s" % self.uid] if self.properties.has_key("printer.vendor"): vendor = self.properties["printer.vendor"].lower () if (self.properties.get("linux.subsystem","") == "usb" and self.properties.has_key("printer.product")): # Use a 'usb:...' URI. Use the same method the CUPS # usb backend uses to construct it. make = self.properties["printer.vendor"] model = self.properties["printer.product"] serial = self.properties.get ("printer.serial", None) if vendor == "hewlett-packard": make = "HP" elif vendor == "lexmark international": make = "Lexmark" if model.startswith (make): model = model[len (make):] model = model.lstrip () model = model.rstrip () uri = "usb://%s/%s" % (make, model) uri = uri.replace (" ", "%20") if serial: uri += "?serial=%s" % serial uris.insert (0, uri) if (not removed and (vendor == "hewlett-packard" or vendor == "hp")): # Perhaps HPLIP can drive this device. If so, we # should use an 'hp:...' URI for CUPS. try: time.sleep (1) # Give HPLIP a chance to reconnect cmd = "LC_ALL=C hp-probe -b usb 2> /dev/null" # Try to match serial number (support for having more # than one printer of the same model on one machine) matchfound = 0 if self.properties.has_key("printer.serial"): for line in os.popen (cmd, 'r').readlines (): if not line.strip ().startswith ("hp:"): continue uri = line.split ()[0] s = uri.find ("?serial=") if s == -1: continue s += 8 e = uri[s:].find ("?") if e == -1: e = len (uri) serial = uri[s:s+e] if serial == self.properties["printer.serial"]: uris.insert (0, uri) matchfound = 1 break # Fall back to printer model if matchfound == 0: for line in os.popen (cmd, 'r').readlines (): if not line.strip ().startswith ("hp:"): continue uri = line.split ()[0] s = uri.find ("/usb/") if s == -1: s = uri.find ("/par/") if s == -1: s = uri.find ("/net/") if s == -1: continue s += 5 e = uri[s:].find ("?") if e == -1: e = len (uri) prod = uri[s:s+e].lower () if prod.startswith ("hp_"): prod = prod[3:] halprod = self.properties["printer.product"].lower() halprod = halprod.replace (" ", "_") if halprod.startswith ("hp_"): halprod = halprod[3:] if prod == halprod: matchfound = 1 uris.insert (0, uri) break except: pass self.uris = uris return uris def get_cups_uri(self): return self.get_cups_uris()[0] def get_cups_hp_fax_uris(self, removed=False): if self.hp_fax_uris != None: return self.hp_fax_uris faxurisfound = 0 if self.properties.has_key("printer.vendor"): vendor = self.properties["printer.vendor"].lower () if (not removed and (vendor == "hewlett-packard" or vendor == "hp")): # We only can have a fax URI if we have an HP printer # supported by HPLIP try: time.sleep (1) # Give HPLIP a chance to reconnect cmd = "LC_ALL=C hp-probe -b usb 2> /dev/null" # Try to find a matching HPLIP fax URI for the HPLIP # print URI for this device uris = self.get_cups_uris () faxuris = [] for uri in uris: if not uri.startswith ("hp:"): continue faxuri = uri.replace("hp:/", "hpfax:/") for line in os.popen (cmd, 'r').readlines (): if not line.strip ().startswith ("hp:"): continue u = line.split ()[0] if uri == u: faxurisfound = 1 faxuris.append (faxuri) except: pass if faxurisfound == 1: self.hp_fax_uris = faxuris return faxuris else: return None def get_cups_hp_fax_uri(self): faxuris = self.get_cups_hp_fax_uris() if faxuris: return faxuris[0] else: return None def add(self): syslog (LOG_DEBUG, "add") printers = cupshelpers.getPrinters(self.cups_connection) printers_extra_info = None uris = self.get_cups_uris () syslog (LOG_DEBUG, "URIs: %s" % uris) faxuris = self.get_cups_hp_fax_uris () syslog (LOG_DEBUG, "HPLIP Fax URIs: %s" % faxuris) printer_exists = 0 fax_exists = 0 p = None for name, printer in printers.iteritems(): if printer.is_class: continue if (name == self.name or printer.device_uri in uris): syslog (LOG_DEBUG, "Not adding printer: %s already exists" % name) printer_exists = 1 if not printer.enabled: if printers_extra_info == None: printers_extra_info = self.cups_connection.getPrinters() statemsg = printers_extra_info[name]["printer-state-message"] if statemsg.lower ().startswith ("unplugged"): syslog (LOG_INFO, "Re-enabling printer %s" % name) self.cups_connection.enablePrinter(name) else: syslog (LOG_INFO, "Printer %s exists but is disabled, reason: %s; " "use 'cupsenable %s' to enable it" % (name, statemsg, name)) if (faxuris and (name == self.faxname or printer.device_uri in faxuris)): syslog (LOG_DEBUG, "Not adding fax printer: %s already exists" % name) fax_exists = 1 if not printer.enabled: if printers_extra_info == None: printers_extra_info = self.cups_connection.getPrinters() statemsg = printers_extra_info[name]["printer-state-message"] if statemsg.lower ().startswith ("unplugged"): syslog (LOG_INFO, "Re-enabling fax printer %s" % name) self.cups_connection.enablePrinter(name) else: syslog (LOG_INFO, "Fax printer %s exists but is disabled, reason: %s; " "use 'cupsenable %s' to enable it" % (name, statemsg, name)) if printer_exists == 0: # really new printer - try autodetection bus = dbus.SystemBus() try: syslog (LOG_DEBUG, "Calling GetReady") obj = bus.get_object("com.redhat.NewPrinterNotification", "/com/redhat/NewPrinterNotification") notification = dbus.Interface(obj, "com.redhat.NewPrinterNotification") notification.GetReady () except dbus.DBusException, e: syslog (LOG_DEBUG, "D-Bus method call failed: %s" % e) notification = None if p == None: cupsppds = self.cups_connection.getPPDs () p = ppds.PPDs (cupsppds) (status, ppdname) = p.getPPDNameFromDeviceID (self.make, self.model, self.description, self.commandsets, self.get_cups_uri()) info = "%s %s" % (self.make, self.model) self.cups_connection.addPrinter(self.name, device=self.get_cups_uri(), ppdname=ppdname, info=info) self.cups_connection.enablePrinter(self.name) self.cups_connection.acceptJobs(self.name) syslog (LOG_INFO, "Added printer %s" % self.name) if notification: try: notification.NewPrinter (status, self.name, self.make, self.model, self.description, reduce(lambda x, y: x + ',' + y, self.commandsets)) except dbus.DBusException: pass if fax_exists == 0: # really new fax printer faxuri = self.get_cups_hp_fax_uri() if faxuri: bus = dbus.SystemBus() try: syslog (LOG_DEBUG, "Calling GetReady") obj = bus.get_object("com.redhat.NewPrinterNotification", "/com/redhat/NewPrinterNotification") notification = dbus.Interface(obj, "com.redhat.NewPrinterNotification") notification.GetReady () except dbus.DBusException, e: syslog (LOG_DEBUG, "D-Bus method call failed: %s" % e) notification = None faxname = self.faxname if p == None: cupsppds = self.cups_connection.getPPDs () p = ppds.PPDs (cupsppds) (status, faxppd) = p.getPPDNameFromDeviceID ("HP", "Fax", "HP Fax", [], faxuri) info = "Fax queue for %s %s" % (self.make, self.model) self.cups_connection.addPrinter(faxname, device=faxuri, ppdname=faxppd, info=info) self.cups_connection.enablePrinter(faxname) self.cups_connection.acceptJobs(faxname) syslog (LOG_INFO, "Added fax printer %s" % faxname) if notification: try: notification.NewPrinter (status, faxname, self.make, self.model + " (Fax)", self.description + " (Fax)", "") except dbus.DBusException: pass def remove(self): syslog (LOG_DEBUG, "remove") # Disable all print queues which print to the device which # we detected as having been removed. This prevents from # jobs being retried every 30 seconds. The jobs wait in the # queue until the device is reconnected and turned on. # # We cannot ask CUPS for the HPLIP URIs after having unplugged or # turned off the printer. So we take model name and serial number # provided by HAL and search the print queues whose URIs contain # this model name and derial number. These are then the queues # which we will disable. printers = cupshelpers.getPrinters(self.cups_connection) printers_extra_info = None make = self.properties["printer.vendor"] makel = make.lower () model = self.properties["printer.product"] serial = self.properties.get ("printer.serial", None) if makel == "hewlett-packard": make = "HP" elif makel == "lexmark international": make = "Lexmark" if model.startswith (make): model = model[len (make):] model = model.lstrip () for name, printer in printers.iteritems(): if printer.is_class: continue syslog (LOG_INFO, "|%s|%s|%s|%s|%s|%s|" % (printer.device_uri, make, model, serial, model.replace (" ", "%20"), model.replace (" ", "_"))) if ((printer.device_uri.find (model.replace (" ", "%20")) != -1 or printer.device_uri.find (model.replace (" ", "_")) != -1) and (not serial or (serial and printer.device_uri.find ("serial=" + serial)))): syslog (LOG_DEBUG, "Found configured printer: %s" % name) if printer.enabled: self.cups_connection.disablePrinter(name, "Unplugged or turned off") syslog (LOG_INFO, "Disabled printer %s, as the corresponding device was unplugged or turned off" % (name)) def configure(self): syslog (LOG_DEBUG, "configure") make, model = sys.stdin.readlines() if make[-1]=="\n": make = make[:-1] if model[-1]=="\n": model = model[:-1] cupsppds = self.cups_connection.getPPDs () p = ppds.PPDs (cupsppds) (status, ppdname) = p.getPPDNameFromDeviceID (make, model, "", "") if not ppdname: syslog (LOG_ERR, "User-selected make/model \"%s\" \"%s\" not found" % (make, model)) return # add printer self.cups_connection.addPrinter( self.name, device=self.get_cups_uri(), ppdname=ppdname, info="Added by HAL") self.cups_connection.enablePrinter(self.name) self.cups_connection.acceptJobs(self.name) syslog (LOG_INFO, "Added printer %s with user-selected make/model" % self.name) class HalLpAdmin: def __init__(self): if len(sys.argv)!=2: return self.usage() if sys.argv[1]=="--add": self.addPrinter() elif sys.argv[1]=="--remove": self.removePrinter() elif sys.argv[1]=="--configure": self.configurePrinter() else: return self.usage() def usage(self): print "Usage: hal_lpadmin (--add|--remove|--configure)" def addPrinter(self): printer = HalPrinter() printer.add() def removePrinter(self): printer = HalPrinter() printer.remove() def configurePrinter(self): printer = HalPrinter() printer.configure() def main(): openlog ("hal_lpadmin", 0, LOG_DAEMON) try: h = HalLpAdmin() except: (type, value, tb) = sys.exc_info () tblast = traceback.extract_tb (tb, limit=None) if len (tblast): tblast = tblast[:len (tblast) - 1] for line in traceback.format_tb (tb): syslog (LOG_ERR, line.strip ()) extxt = traceback.format_exception_only (type, value) syslog (LOG_ERR, extxt[0].strip ()) main()