Back to index

system-config-printer  1.3.9+20120706
scp-dbus-service.py
Go to the documentation of this file.
00001 #!/usr/bin/python
00002 
00003 ## system-config-printer
00004 
00005 ## Copyright (C) 2010, 2011 Red Hat, Inc.
00006 ## Authors:
00007 ##  Tim Waugh <twaugh@redhat.com>
00008 
00009 ## This program is free software; you can redistribute it and/or modify
00010 ## it under the terms of the GNU General Public License as published by
00011 ## the Free Software Foundation; either version 2 of the License, or
00012 ## (at your option) any later version.
00013 
00014 ## This program is distributed in the hope that it will be useful,
00015 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00017 ## GNU General Public License for more details.
00018 
00019 ## You should have received a copy of the GNU General Public License
00020 ## along with this program; if not, write to the Free Software
00021 ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00022 
00023 import dbus.service
00024 import gobject
00025 import sys
00026 
00027 from debug import *
00028 import asyncconn
00029 import config
00030 import cups
00031 import cupshelpers
00032 import dnssdresolve
00033 import jobviewer
00034 import newprinter
00035 import PhysicalDevice
00036 import ppdcache
00037 import printerproperties
00038 
00039 cups.require ("1.9.52")
00040 
00041 CONFIG_BUS='org.fedoraproject.Config.Printing'
00042 CONFIG_PATH='/org/fedoraproject/Config/Printing'
00043 CONFIG_IFACE='org.fedoraproject.Config.Printing'
00044 CONFIG_NEWPRINTERDIALOG_IFACE=CONFIG_IFACE + ".NewPrinterDialog"
00045 CONFIG_PRINTERPROPERTIESDIALOG_IFACE=CONFIG_IFACE + ".PrinterPropertiesDialog"
00046 CONFIG_JOBVIEWER_IFACE=CONFIG_IFACE + ".JobViewer"
00047 
00048 g_ppds = None
00049 g_killtimer = None
00050 
00051 class KillTimer:
00052     def __init__ (self, timeout=30, killfunc=None):
00053         self._timeout = timeout
00054         self._killfunc = killfunc
00055         self._holds = 0
00056         self._add_timeout ()
00057 
00058     def _add_timeout (self):
00059         self._timer = gobject.timeout_add_seconds (self._timeout, self._kill)
00060 
00061     def _kill (self):
00062         debugprint ("Timeout (%ds), exiting" % self._timeout)
00063         if self._killfunc:
00064             self._killfunc ()
00065         else:
00066             sys.exit (0)
00067 
00068     def add_hold (self):
00069         if self._holds == 0:
00070             debugprint ("Kill timer stopped")
00071             gobject.source_remove (self._timer)
00072 
00073         self._holds += 1
00074 
00075     def remove_hold (self):
00076         if self._holds > 0:
00077             self._holds -= 1
00078             if self._holds == 0:
00079                 debugprint ("Kill timer started")
00080                 self._add_timeout ()
00081 
00082     def alive (self):
00083         if self._holds == 0:
00084             gobject.source_remove (self._timer)
00085             self._add_timeout ()
00086 
00087 class FetchedPPDs(gobject.GObject):
00088     __gsignals__ = {
00089         'ready': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
00090         'error': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
00091                   [gobject.TYPE_PYOBJECT])
00092         }
00093 
00094     def __init__ (self, cupsconn, language):
00095         gobject.GObject.__init__ (self)
00096         self._cupsconn = cupsconn
00097         self._language = language
00098         self._ppds = None
00099 
00100     def is_ready (self):
00101         return self._ppds != None
00102 
00103     def get_ppds (self):
00104         return self._ppds
00105 
00106     def run (self):
00107         debugprint ("FetchPPDs: running")
00108         self._cupsconn.getPPDs2 (reply_handler=self._cups_getppds_reply,
00109                                  error_handler=self._cups_error)
00110 
00111     def _cups_error (self, conn, exc):
00112         debugprint ("FetchPPDs: error: %s" % repr (exc))
00113         self.emit ('error', exc)
00114 
00115     def _cups_getppds_reply (self, conn, result):
00116         debugprint ("FetchPPDs: success")
00117         self._ppds = cupshelpers.ppds.PPDs (result, language=self._language)
00118         self.emit ('ready')
00119 
00120 gobject.type_register (FetchedPPDs)
00121 
00122 class GetBestDriversRequest:
00123     def __init__ (self, device_id, device_make_and_model, device_uri,
00124                   cupsconn, language, reply_handler, error_handler):
00125         self.device_id = device_id
00126         self.device_make_and_model = device_make_and_model
00127         self.device_uri = device_uri
00128         self.reply_handler = reply_handler
00129         self.error_handler = error_handler
00130         self._signals = []
00131         debugprint ("+%s" % self)
00132 
00133         g_killtimer.add_hold ()
00134         global g_ppds
00135         if g_ppds == None:
00136             debugprint ("GetBestDrivers request: need to fetch PPDs")
00137             g_ppds = FetchedPPDs (cupsconn, language)
00138             self._signals.append (g_ppds.connect ('ready', self._ppds_ready))
00139             self._signals.append (g_ppds.connect ('error', self._ppds_error))
00140             g_ppds.run ()
00141         else:
00142             if g_ppds.is_ready ():
00143                 debugprint ("GetBestDrivers request: PPDs already fetched")
00144                 self._ppds_ready (g_ppds)
00145             else:
00146                 debugprint ("GetBestDrivers request: waiting for PPDs")
00147                 self._signals.append (g_ppds.connect ('ready',
00148                                                       self._ppds_ready))
00149                 self._signals.append (g_ppds.connect ('error',
00150                                                       self._ppds_error))
00151 
00152     def __del__ (self):
00153         debugprint ("-%s" % self)
00154 
00155     def _disconnect_signals (self):
00156         for s in self._signals:
00157             g_ppds.disconnect (s)
00158 
00159     def _ppds_error (self, fetchedppds, exc):
00160         self._disconnect_signals ()
00161         self.error_handler (exc)
00162 
00163     def _ppds_ready (self, fetchedppds):
00164         self._disconnect_signals ()
00165         ppds = fetchedppds.get_ppds ()
00166 
00167         try:
00168             if self.device_id:
00169                 id_dict = cupshelpers.parseDeviceID (self.device_id)
00170             else:
00171                 id_dict = {}
00172                 (mfg,
00173                  mdl) = cupshelpers.ppds.ppdMakeModelSplit (self.device_make_and_model)
00174                 id_dict["MFG"] = mfg
00175                 id_dict["MDL"] = mdl
00176                 id_dict["DES"] = ""
00177                 id_dict["CMD"] = []
00178 
00179             fit = ppds.getPPDNamesFromDeviceID (id_dict["MFG"],
00180                                                 id_dict["MDL"],
00181                                                 id_dict["DES"],
00182                                                 id_dict["CMD"],
00183                                                 self.device_uri,
00184                                                 self.device_make_and_model)
00185 
00186             ppdnamelist = ppds.orderPPDNamesByPreference (fit.keys ())
00187 
00188             g_killtimer.remove_hold ()
00189             self.reply_handler (map (lambda x: (x, fit[x]), ppdnamelist))
00190         except Exception, e:
00191             try:
00192                 g_killtimer.remove_hold ()
00193             except:
00194                 pass
00195 
00196             self.error_handler (e)
00197 
00198 class GroupPhysicalDevicesRequest:
00199     def __init__ (self, devices, reply_handler, error_handler):
00200         self.devices = devices
00201         self.reply_handler = reply_handler
00202         self.error_handler = error_handler
00203         debugprint ("+%s" % self)
00204 
00205         try:
00206             g_killtimer.add_hold ()
00207             need_resolving = {}
00208             self.deviceobjs = {}
00209             for device_uri, device_dict in self.devices.iteritems ():
00210                 deviceobj = cupshelpers.Device (device_uri, **device_dict)
00211                 self.deviceobjs[device_uri] = deviceobj
00212                 if device_uri.startswith ("dnssd://"):
00213                     need_resolving[device_uri] = deviceobj
00214 
00215             if len (need_resolving) > 0:
00216                 resolver = dnssdresolve.DNSSDHostNamesResolver (need_resolving)
00217                 resolver.resolve (reply_handler=self._group)
00218             else:
00219                 self._group ()
00220         except Exception, e:
00221             g_killtimer.remove_hold ()
00222             self.error_handler (e)
00223 
00224     def __del__ (self):
00225         debugprint ("-%s" % self)
00226 
00227     def _group (self, resolved_devices=None):
00228         # We can ignore resolved_devices because the actual objects
00229         # (in self.devices) have been modified.
00230         try:
00231             self.physdevs = []
00232             for device_uri, deviceobj in self.deviceobjs.iteritems ():
00233                 newphysicaldevice = PhysicalDevice.PhysicalDevice (deviceobj)
00234                 matched = False
00235                 try:
00236                     i = self.physdevs.index (newphysicaldevice)
00237                     self.physdevs[i].add_device (deviceobj)
00238                 except ValueError:
00239                     self.physdevs.append (newphysicaldevice)
00240 
00241             uris_by_phys = []
00242             for physdev in self.physdevs:
00243                 uris_by_phys.append (map (lambda x: x.uri,
00244                                           physdev.get_devices ()))
00245 
00246             g_killtimer.remove_hold ()
00247             self.reply_handler (uris_by_phys)
00248         except Exception, e:
00249             g_killtimer.remove_hold ()
00250             self.error_handler (e)
00251 
00252 class ConfigPrintingNewPrinterDialog(dbus.service.Object):
00253     def __init__ (self, bus, path, cupsconn):
00254         bus_name = dbus.service.BusName (CONFIG_BUS, bus=bus)
00255         dbus.service.Object.__init__ (self, bus_name, path)
00256         self.dialog = newprinter.NewPrinterGUI()
00257         self.dialog.NewPrinterWindow.set_modal (False)
00258         self.handles = [self.dialog.connect ('dialog-canceled',
00259                                              self.on_dialog_canceled),
00260                         self.dialog.connect ('printer-added',
00261                                              self.on_printer_added),
00262                         self.dialog.connect ('printer-modified',
00263                                              self.on_printer_modified)]
00264         self._ppdcache = ppdcache.PPDCache ()
00265         self._cupsconn = cupsconn
00266         debugprint ("+%s" % self)
00267 
00268     def __del__ (self):
00269         debugprint ("-%s" % self)
00270 
00271     @dbus.service.method(dbus_interface=CONFIG_NEWPRINTERDIALOG_IFACE,
00272                          in_signature='uss', out_signature='')
00273     def NewPrinterFromDevice(self, xid, device_uri, device_id):
00274         g_killtimer.add_hold ()
00275         self.dialog.init ('printer_with_uri', device_uri=device_uri,
00276                           devid=device_id, xid=xid)
00277 
00278     @dbus.service.method(dbus_interface=CONFIG_NEWPRINTERDIALOG_IFACE,
00279                          in_signature='uss', out_signature='')
00280     def ChangePPD(self, xid, name, device_id):
00281         g_killtimer.add_hold ()
00282         self.xid = xid
00283         self.name = name
00284         self.device_id = device_id
00285         self._ppdcache.fetch_ppd (name, self._change_ppd_got_ppd)
00286 
00287     def _change_ppd_got_ppd(self, name, ppd, exc):
00288         # Got PPD; now find device URI.
00289         self.ppd = ppd
00290         self._cupsconn.getPrinters (reply_handler=self._change_ppd_with_dev,
00291                                     error_handler=self._do_change_ppd)
00292 
00293     def _change_ppd_with_dev (self, conn, result):
00294         self.device_uri = result.get (self.name, {}).get ('device-uri', None)
00295         self._do_change_ppd (conn)
00296 
00297     def _do_change_ppd(self, conn, exc=None):
00298         self.dialog.init ('ppd', device_uri=self.device_uri, name=self.name,
00299                           ppd=self.ppd, devid=self.device_id, xid=self.xid)
00300 
00301     @dbus.service.signal(dbus_interface=CONFIG_NEWPRINTERDIALOG_IFACE,
00302                          signature='')
00303     def DialogCanceled(self):
00304         pass
00305 
00306     @dbus.service.signal(dbus_interface=CONFIG_NEWPRINTERDIALOG_IFACE,
00307                          signature='s')
00308     def PrinterAdded(self, name):
00309         pass
00310 
00311     @dbus.service.signal(dbus_interface=CONFIG_NEWPRINTERDIALOG_IFACE,
00312                          signature='sb')
00313     def PrinterModified(self, name, ppd_has_changed):
00314         pass
00315 
00316     def on_dialog_canceled(self, obj):
00317         g_killtimer.remove_hold ()
00318         self.DialogCanceled ()
00319         self.remove_handles ()
00320         self.remove_from_connection ()
00321 
00322     def on_printer_added(self, obj, name):
00323         g_killtimer.remove_hold ()
00324         self.PrinterAdded (name)
00325         self.remove_handles ()
00326         self.remove_from_connection ()
00327 
00328     def on_printer_modified(self, obj, name, ppd_has_changed):
00329         g_killtimer.remove_hold ()
00330         self.PrinterModifed (name, ppd_has_changed)
00331         self.remove_handles ()
00332         self.remove_from_connection ()
00333 
00334     def remove_handles (self):
00335         for handle in self.handles:
00336             self.dialog.disconnect (handle)
00337 
00338 class ConfigPrintingPrinterPropertiesDialog(dbus.service.Object):
00339     def __init__ (self, bus, path, xid, name):
00340         bus_name = dbus.service.BusName (CONFIG_BUS, bus=bus)
00341         dbus.service.Object.__init__ (self, bus_name=bus_name, object_path=path)
00342         self.dialog = printerproperties.PrinterPropertiesDialog ()
00343         self.dialog.dialog.set_modal (False)
00344         handle = self.dialog.connect ('dialog-closed', self.on_dialog_closed)
00345         self.closed_handle = handle
00346         self.dialog.show (name)
00347         self.dialog.dialog.set_modal (False)
00348         g_killtimer.add_hold ()
00349 
00350     @dbus.service.method(dbus_interface=CONFIG_PRINTERPROPERTIESDIALOG_IFACE,
00351                          in_signature='', out_signature='')
00352     def PrintTestPage (self):
00353         debugprint ("Printing test page")
00354         return self.dialog.printTestPage ()
00355 
00356     @dbus.service.signal(dbus_interface=CONFIG_PRINTERPROPERTIESDIALOG_IFACE,
00357                          signature='')
00358     def Finished (self):
00359         pass
00360 
00361     def on_dialog_closed (self, dialog):
00362         dialog.destroy ()
00363         g_killtimer.remove_hold ()
00364         self.Finished ()
00365         self.dialog.disconnect (self.closed_handle)
00366         self.remove_from_connection ()
00367 
00368 class ConfigPrintingJobApplet(dbus.service.Object):
00369     def __init__ (self, bus, path):
00370         bus_name = dbus.service.BusName (CONFIG_BUS, bus=bus)
00371         dbus.service.Object.__init__ (self, bus_name=bus_name, object_path=path)
00372         self.jobapplet = jobviewer.JobViewer(bus=dbus.SystemBus (),
00373                                              applet=True, my_jobs=True)
00374         handle = self.jobapplet.connect ('finished', self.on_jobapplet_finished)
00375         self.finished_handle = handle
00376         self.has_finished = False
00377         g_killtimer.add_hold ()
00378         debugprint ("+%s" % self)
00379 
00380     def __del__ (self):
00381         debugprint ("-%s" % self)
00382 
00383     @dbus.service.method(dbus_interface=CONFIG_JOBVIEWER_IFACE,
00384                          in_signature='', out_signature='')
00385     def Quit(self):
00386         if not self.has_finished:
00387             self.jobapplet.cleanup ()
00388 
00389     @dbus.service.signal(dbus_interface=CONFIG_JOBVIEWER_IFACE, signature='')
00390     def Finished(self):
00391         pass
00392 
00393     def on_jobapplet_finished (self, jobapplet):
00394         self.Finished ()
00395         g_killtimer.remove_hold ()
00396         self.has_finished = True
00397         self.jobapplet.disconnect (self.finished_handle)
00398         self.remove_from_connection ()
00399 
00400 class ConfigPrinting(dbus.service.Object):
00401     def __init__ (self):
00402         self.bus = dbus.SessionBus ()
00403         bus_name = dbus.service.BusName (CONFIG_BUS, bus=self.bus)
00404         dbus.service.Object.__init__ (self, bus_name, CONFIG_PATH)
00405         self._cupsconn = asyncconn.Connection ()
00406         self._pathn = 0
00407         self._jobapplet = None
00408         self._jobappletpath = None
00409         self._ppds = None
00410         self._language = locale.getlocale (locale.LC_MESSAGES)[0]
00411 
00412     def destroy (self):
00413         self._cupsconn.destroy ()
00414 
00415     @dbus.service.method(dbus_interface=CONFIG_IFACE,
00416                          in_signature='', out_signature='s')
00417     def NewPrinterDialog(self):
00418         self._pathn += 1
00419         path = "%s/NewPrinterDialog/%s" % (CONFIG_PATH, self._pathn)
00420         ConfigPrintingNewPrinterDialog (self.bus, path,
00421                                         self._cupsconn)
00422         g_killtimer.alive ()
00423         return path
00424 
00425     @dbus.service.method(dbus_interface=CONFIG_IFACE,
00426                          in_signature='us', out_signature='s')
00427     def PrinterPropertiesDialog(self, xid, name):
00428         self._pathn += 1
00429         path = "%s/PrinterPropertiesDialog/%s" % (CONFIG_PATH, self._pathn)
00430         ConfigPrintingPrinterPropertiesDialog (self.bus, path, xid, name)
00431         g_killtimer.alive ()
00432         return path
00433 
00434     @dbus.service.method(dbus_interface=CONFIG_IFACE,
00435                          in_signature='', out_signature='s')
00436     def JobApplet(self):
00437        if self._jobapplet == None or self._jobapplet.has_finished:
00438             self._pathn += 1
00439             path = "%s/JobApplet/%s" % (CONFIG_PATH, self._pathn)
00440             self._jobapplet = ConfigPrintingJobApplet (self.bus, path)
00441             self._jobappletpath = path
00442 
00443        return self._jobappletpath
00444 
00445     @dbus.service.method(dbus_interface=CONFIG_IFACE,
00446                          in_signature='sss', out_signature='a(ss)',
00447                          async_callbacks=('reply_handler', 'error_handler'))
00448     def GetBestDrivers(self, device_id, device_make_and_model, device_uri,
00449                    reply_handler, error_handler):
00450         GetBestDriversRequest (device_id, device_make_and_model, device_uri,
00451                                self._cupsconn, self._language[0],
00452                                reply_handler, error_handler)
00453 
00454     @dbus.service.method(dbus_interface=CONFIG_IFACE,
00455                          in_signature='s', out_signature='as')
00456     def MissingExecutables(self, ppd_filename):
00457         ppd = cups.PPD (ppd_filename)
00458         return cupshelpers.missingExecutables (ppd)
00459 
00460     @dbus.service.method(dbus_interface=CONFIG_IFACE,
00461                          in_signature='a{sa{ss}}', out_signature='aas',
00462                          async_callbacks=('reply_handler', 'error_handler'))
00463     def GroupPhysicalDevices(self, devices, reply_handler, error_handler):
00464         GroupPhysicalDevicesRequest (devices, reply_handler, error_handler)
00465 
00466 def _client_demo ():
00467     # Client demo
00468     if len (sys.argv) > 2:
00469         device_uri = sys.argv[2]
00470         device_id = ''
00471         if (len (sys.argv) > 4 and
00472             sys.argv[3] == '--devid'):
00473             device_id = sys.argv[4]
00474     else:
00475         print "Device URI required"
00476         return
00477 
00478     import gtk
00479     bus = dbus.SessionBus ()
00480     obj = bus.get_object (CONFIG_BUS, CONFIG_PATH)
00481     iface = dbus.Interface (obj, CONFIG_IFACE)
00482     path = iface.NewPrinterDialog ()
00483     debugprint (path)
00484 
00485     obj = bus.get_object (CONFIG_BUS, path)
00486     iface = dbus.Interface (obj, CONFIG_NEWPRINTERDIALOG_IFACE)
00487     loop = gobject.MainLoop ()
00488     def on_canceled(path=None):
00489         print "%s: Dialog canceled" % path
00490         loop.quit ()
00491 
00492     def on_added(name, path=None):
00493         print "%s: Printer '%s' added" % (path, name)
00494         loop.quit ()
00495 
00496     w = gtk.Window ()
00497     w.show_now ()
00498     iface.connect_to_signal ("DialogCanceled", on_canceled,
00499                              path_keyword="path")
00500     iface.connect_to_signal ("PrinterAdded", on_added,
00501                              path_keyword="path")
00502 
00503     iface.NewPrinterFromDevice (w.window.xid, device_uri, device_id)
00504     loop.run ()
00505 
00506 if __name__ == '__main__':
00507     import ppdippstr
00508     import gettext
00509     import locale
00510     try:
00511         locale.setlocale (locale.LC_ALL, "")
00512     except:
00513         pass
00514 
00515     gettext.textdomain (config.PACKAGE)
00516     gettext.bindtextdomain (config.PACKAGE, config.localedir)
00517     ppdippstr.init ()
00518     gobject.threads_init ()
00519     from dbus.glib import DBusGMainLoop
00520     DBusGMainLoop (set_as_default=True)
00521 
00522     client_demo = False
00523     if len (sys.argv) > 1:
00524         for opt in sys.argv[1:]:
00525             if opt == "--debug":
00526                 set_debugging (True)
00527             elif opt == "--client":
00528                 client_demo = True
00529 
00530     if client_demo:
00531         _client_demo ()
00532         sys.exit (0)
00533 
00534     debugprint ("Service running...")
00535     loop = gobject.MainLoop ()
00536     g_killtimer = KillTimer (killfunc=loop.quit)
00537     cp = ConfigPrinting ()
00538     loop.run ()
00539     cp.destroy ()