480 lines
17 KiB
Python
Executable File
480 lines
17 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
## Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Red Hat, Inc.
|
|
## Author: Tim Waugh <twaugh@redhat.com>
|
|
|
|
## This program is free software; you can redistribute it and/or modify
|
|
## it under the terms of the GNU General Public License as published by
|
|
## the Free Software Foundation; either version 2 of the License, or
|
|
## (at your option) any later version.
|
|
|
|
## This program is distributed in the hope that it will be useful,
|
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
## GNU General Public License for more details.
|
|
|
|
## You should have received a copy of the GNU General Public License
|
|
## along with this program; if not, write to the Free Software
|
|
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
import cups
|
|
from functools import reduce
|
|
cups.require ("1.9.42")
|
|
import sys
|
|
import socket
|
|
import getpass
|
|
from debug import *
|
|
|
|
import dbus
|
|
|
|
# set up global default main loop
|
|
from dbus.mainloop.glib import DBusGMainLoop
|
|
DBusGMainLoop(set_as_default=True)
|
|
|
|
import dbus.service
|
|
import gi
|
|
from gi.repository import GObject
|
|
from gi.repository import GLib
|
|
import time
|
|
import locale
|
|
import cupshelpers.installdriver
|
|
|
|
DOMAIN="system-config-printer"
|
|
import gettext
|
|
gettext.install(domain=DOMAIN)
|
|
try:
|
|
locale.setlocale (locale.LC_ALL, "")
|
|
except locale.Error as e:
|
|
import os
|
|
os.environ['LC_ALL'] = 'C'
|
|
locale.setlocale (locale.LC_ALL, "")
|
|
|
|
gi.require_version('Notify', '0.7')
|
|
from gi.repository import Notify
|
|
|
|
APPDIR="/usr/share/system-config-printer"
|
|
ICON="printer"
|
|
|
|
# We need to call Notify.init before we can check the server for caps
|
|
Notify.init('System Config Printer Notification')
|
|
|
|
# D-Bus APIs of other objects we'll use.
|
|
PRINTING_BUS="org.fedoraproject.Config.Printing"
|
|
PRINTING_PATH="/org/fedoraproject/Config/Printing"
|
|
PRINTING_IFACE="org.fedoraproject.Config.Printing"
|
|
NEWPRINTERDIALOG_IFACE=PRINTING_IFACE + ".NewPrinterDialog"
|
|
PRINTERPROPERTIESDIALOG_IFACE=PRINTING_IFACE + ".PrinterPropertiesDialog"
|
|
|
|
####
|
|
#### NewPrinterNotification DBus server (the 'new' way).
|
|
####
|
|
PDS_PATH="/com/redhat/NewPrinterNotification"
|
|
PDS_IFACE="com.redhat.NewPrinterNotification"
|
|
PDS_OBJ="com.redhat.NewPrinterNotification"
|
|
class NewPrinterNotification(dbus.service.Object):
|
|
STATUS_SUCCESS = 0
|
|
STATUS_MODEL_MISMATCH = 1
|
|
STATUS_GENERIC_DRIVER = 2
|
|
STATUS_NO_DRIVER = 3
|
|
|
|
def __init__ (self, system_bus, session_bus):
|
|
self.system_bus = system_bus
|
|
self.session_bus = session_bus
|
|
self.getting_ready = 0
|
|
bus_name = dbus.service.BusName (PDS_OBJ, bus=system_bus)
|
|
dbus.service.Object.__init__ (self, bus_name, PDS_PATH)
|
|
self.notification = None
|
|
|
|
@dbus.service.method(PDS_IFACE, in_signature='', out_signature='')
|
|
def GetReady (self):
|
|
TIMEOUT=1200000
|
|
if self.getting_ready == 0:
|
|
n = Notify.Notification.new (_("Configuring new printer"),
|
|
_("Please wait..."),
|
|
'printer')
|
|
n.set_timeout (TIMEOUT + 5000)
|
|
n.closed = False
|
|
n.connect ('closed', self.on_notification_closed)
|
|
n.show ()
|
|
self.notification = n
|
|
|
|
self.getting_ready += 1
|
|
GLib.timeout_add_seconds (TIMEOUT, self.timeout_ready)
|
|
|
|
def on_notification_closed (self, notification):
|
|
notification.closed = True
|
|
|
|
def timeout_ready (self):
|
|
if self.getting_ready > 0:
|
|
self.getting_ready -= 1
|
|
if (self.getting_ready == 0 and
|
|
self.notification and
|
|
not getattr (self.notification, 'closed', None)):
|
|
self.notification.close ()
|
|
|
|
return False
|
|
|
|
@dbus.service.method(PDS_IFACE, in_signature='isssss', out_signature='')
|
|
def NewPrinter (self, status, name, mfg, mdl, des, cmd):
|
|
if name.find("/") >= 0:
|
|
# name is a URI, no queue was generated, because no suitable
|
|
# driver was found
|
|
title = _("Missing printer driver")
|
|
devid = "MFG:%s;MDL:%s;DES:%s;CMD:%s;" % (mfg, mdl, des, cmd)
|
|
if (mfg and mdl) or des:
|
|
if (mfg and mdl):
|
|
device = "%s %s" % (mfg, mdl)
|
|
else:
|
|
device = des
|
|
text = _("No printer driver for %s.") % device
|
|
else:
|
|
text = _("No driver for this printer.")
|
|
n = Notify.Notification.new (title, text, 'printer')
|
|
if "actions" in Notify.get_server_caps():
|
|
n.set_urgency (Notify.Urgency.CRITICAL)
|
|
n.set_timeout (Notify.EXPIRES_NEVER)
|
|
n.add_action ("setup-printer", _("Search"),
|
|
lambda x, y:
|
|
self.setup_printer (x, y, name, devid))
|
|
else:
|
|
self.setup_printer (None, None, name, devid)
|
|
|
|
else:
|
|
# name is the name of the queue which hal_lpadmin has set up
|
|
# automatically.
|
|
c = cups.Connection ()
|
|
try:
|
|
printer = c.getPrinters ()[name]
|
|
except KeyError:
|
|
return
|
|
|
|
try:
|
|
filename = c.getPPD (name)
|
|
except cups.IPPError:
|
|
return
|
|
|
|
del c
|
|
|
|
# Check for missing packages
|
|
cups.ppdSetConformance (cups.PPD_CONFORM_RELAXED)
|
|
ppd = cups.PPD (filename)
|
|
import os
|
|
os.unlink (filename)
|
|
import sys
|
|
sys.path.append (APPDIR)
|
|
import cupshelpers
|
|
(missing_pkgs,
|
|
missing_exes) = cupshelpers.missingPackagesAndExecutables (ppd)
|
|
|
|
from cupshelpers.ppds import ppdMakeModelSplit
|
|
(make, model) = ppdMakeModelSplit (printer['printer-make-and-model'])
|
|
driver = make + " " + model
|
|
if status < self.STATUS_GENERIC_DRIVER:
|
|
title = _("Printer added")
|
|
else:
|
|
title = _("Missing printer driver")
|
|
|
|
if len (missing_pkgs) > 0:
|
|
pkgs = reduce (lambda x,y: x + ", " + y, missing_pkgs)
|
|
title = _("Install printer driver")
|
|
text = (_("`%s' requires driver installation: %s.") %
|
|
(name, pkgs))
|
|
n = Notify.Notification.new (title, text, 'printer')
|
|
import installpackage
|
|
if "actions" in Notify.get_server_caps():
|
|
try:
|
|
self.packagekit = installpackage.PackageKit ()
|
|
n.set_timeout (Notify.EXPIRES_NEVER)
|
|
n.add_action ("install-driver", _("Install"),
|
|
lambda x, y:
|
|
self.install_driver (x, y,
|
|
missing_pkgs))
|
|
except:
|
|
pass
|
|
else:
|
|
try:
|
|
self.packagekit = installpackage.PackageKit ()
|
|
self.packagekit.InstallPackageName (0, 0,
|
|
missing_pkgs[0])
|
|
except:
|
|
pass
|
|
|
|
elif status == self.STATUS_SUCCESS:
|
|
devid = "MFG:%s;MDL:%s;DES:%s;CMD:%s;" % (mfg, mdl, des, cmd)
|
|
text = _("`%s' is ready for printing.") % name
|
|
n = Notify.Notification.new (title, text, 'printer')
|
|
if "actions" in Notify.get_server_caps():
|
|
n.set_urgency (Notify.Urgency.NORMAL)
|
|
n.add_action ("test-page", _("Print test page"),
|
|
lambda x, y:
|
|
self.print_test_page (x, y, name))
|
|
n.add_action ("configure", _("Configure"),
|
|
lambda x, y: self.configure (x, y, name))
|
|
else: # Model mismatch
|
|
devid = "MFG:%s;MDL:%s;DES:%s;CMD:%s;" % (mfg, mdl, des, cmd)
|
|
text = (_("`%s' has been added, using the `%s' driver.") %
|
|
(name, driver))
|
|
n = Notify.Notification.new (title, text, 'printer')
|
|
if "actions" in Notify.get_server_caps():
|
|
n.set_urgency (Notify.Urgency.CRITICAL)
|
|
n.add_action ("test-page", _("Print test page"),
|
|
lambda x, y:
|
|
self.print_test_page (x, y, name, devid))
|
|
n.add_action ("find-driver", _("Find driver"),
|
|
lambda x, y:
|
|
self.find_driver (x, y, name, devid))
|
|
n.set_timeout (Notify.EXPIRES_NEVER)
|
|
else:
|
|
self.configure (None, None, name)
|
|
|
|
self.timeout_ready ()
|
|
n.show ()
|
|
self.notification = n
|
|
|
|
def print_test_page (self, notification, action, name):
|
|
path = self.configure (None, None, name)
|
|
obj = self.session_bus.get_object (PRINTING_BUS, path)
|
|
iface = dbus.Interface (obj, PRINTERPROPERTIESDIALOG_IFACE)
|
|
iface.PrintTestPage ()
|
|
|
|
def configure (self, notification, action, name):
|
|
obj = self.session_bus.get_object (PRINTING_BUS, PRINTING_PATH)
|
|
iface = dbus.Interface (obj, PRINTING_IFACE)
|
|
return iface.PrinterPropertiesDialog (dbus.UInt32(0), name)
|
|
|
|
def get_newprinterdialog_interface (self):
|
|
obj = self.session_bus.get_object (PRINTING_BUS, PRINTING_PATH)
|
|
iface = dbus.Interface (obj, PRINTING_IFACE)
|
|
path = iface.NewPrinterDialog ()
|
|
obj = self.session_bus.get_object (PRINTING_BUS, path)
|
|
iface = dbus.Interface (obj, NEWPRINTERDIALOG_IFACE)
|
|
return iface
|
|
|
|
def ignore_dbus_replies (self, *args):
|
|
pass
|
|
|
|
def find_driver (self, notification, action, name, devid = ""):
|
|
try:
|
|
iface = self.get_newprinterdialog_interface ()
|
|
iface.ChangePPD (dbus.UInt32(0), name, devid,
|
|
reply_handler=self.ignore_dbus_replies,
|
|
error_handler=self.ignore_dbus_replies)
|
|
except dbus.DBusException:
|
|
pass
|
|
|
|
def setup_printer (self, notification, action, uri, devid = ""):
|
|
try:
|
|
iface = self.get_newprinterdialog_interface ()
|
|
iface.NewPrinterFromDevice (dbus.UInt32(0), uri, devid,
|
|
reply_handler=self.ignore_dbus_replies,
|
|
error_handler=self.ignore_dbus_replies)
|
|
except dbus.DBusException:
|
|
pass
|
|
|
|
def install_driver (self, notification, action, missing_pkgs):
|
|
try:
|
|
self.packagekit.InstallPackageName (0, 0, missing_pkgs[0])
|
|
except:
|
|
pass
|
|
|
|
def collect_exit_code (self, pid):
|
|
# We do this with timers instead of signals because we already
|
|
# have GLib imported, but don't (yet) import signal;
|
|
# let's try not to inflate the process size.
|
|
import os
|
|
try:
|
|
print("Waiting for child %d" % pid)
|
|
(pid, status) = os.waitpid (pid, os.WNOHANG)
|
|
if pid == 0:
|
|
# Run this timer again.
|
|
return True
|
|
except OSError:
|
|
pass
|
|
|
|
return False
|
|
|
|
PROGRAM_NAME="system-config-printer-applet"
|
|
def show_help ():
|
|
print("usage: %s [--help|--version|--debug]" % PROGRAM_NAME)
|
|
|
|
def show_version ():
|
|
import config
|
|
print("%s %s" % (PROGRAM_NAME, config.VERSION))
|
|
|
|
####
|
|
#### Main program entry
|
|
####
|
|
|
|
def monitor_session (*args):
|
|
pass
|
|
|
|
def any_jobs ():
|
|
try:
|
|
c = cups.Connection ()
|
|
jobs = c.getJobs (my_jobs=True, limit=1)
|
|
if len (jobs):
|
|
return True
|
|
except:
|
|
pass
|
|
|
|
return False
|
|
|
|
class RunLoop:
|
|
DBUS_PATH="/com/redhat/PrinterSpooler"
|
|
DBUS_IFACE="com.redhat.PrinterSpooler"
|
|
|
|
def __init__ (self, session_bus, system_bus, loop):
|
|
self.system_bus = system_bus
|
|
self.session_bus = session_bus
|
|
self.loop = loop
|
|
self.timer = None
|
|
try:
|
|
system_bus.add_signal_receiver (self.handle_dbus_signal,
|
|
path=self.DBUS_PATH,
|
|
dbus_interface=self.DBUS_IFACE)
|
|
except dbus.DBusException as e:
|
|
try:
|
|
print ("%s: failed to add D-Bus signal receiver: %s" \
|
|
% (PROGRAM_NAME, e), file=sys.stderr)
|
|
finally:
|
|
sys.exit (1)
|
|
|
|
self.check_for_jobs ()
|
|
|
|
def remove_signal_receiver (self):
|
|
try:
|
|
self.system_bus.remove_signal_receiver (self.handle_dbus_signal,
|
|
path=self.DBUS_PATH,
|
|
dbus_interface=self.DBUS_IFACE)
|
|
except dbus.DBusException as e:
|
|
try:
|
|
print ("%s: failed to remove D-Bus signal receiver: %s" \
|
|
% (PROGRAM_NAME, e), file=sys.stderr)
|
|
except:
|
|
pass
|
|
|
|
def run (self):
|
|
self.loop.run ()
|
|
|
|
def __del__ (self):
|
|
self.remove_signal_receiver ()
|
|
if self.timer:
|
|
GLib.source_remove (self.timer)
|
|
|
|
def handle_dbus_signal (self, *args):
|
|
if self.timer:
|
|
GLib.source_remove (self.timer)
|
|
self.timer = GLib.timeout_add (200, self.check_for_jobs)
|
|
|
|
def check_for_jobs (self, *args):
|
|
debugprint ("checking for jobs")
|
|
if any_jobs ():
|
|
if self.timer is not None:
|
|
GLib.source_remove (self.timer)
|
|
|
|
self.remove_signal_receiver ()
|
|
|
|
# Start the job applet.
|
|
debugprint ("Starting job applet")
|
|
try:
|
|
obj = self.session_bus.get_object (PRINTING_BUS, PRINTING_PATH)
|
|
iface = dbus.Interface (obj, PRINTING_IFACE)
|
|
path = iface.JobApplet ()
|
|
debugprint ("Job applet is %s" % path)
|
|
except dbus.DBusException as e:
|
|
try:
|
|
print(e)
|
|
except:
|
|
pass
|
|
|
|
# Don't run this timer again.
|
|
return False
|
|
|
|
if __name__ == '__main__':
|
|
import sys, getopt
|
|
try:
|
|
opts, args = getopt.gnu_getopt (sys.argv[1:], '',
|
|
['debug',
|
|
'help',
|
|
'version'])
|
|
except getopt.GetoptError:
|
|
show_help ()
|
|
sys.exit (1)
|
|
|
|
for opt, optarg in opts:
|
|
if opt == "--help":
|
|
show_help ()
|
|
sys.exit (0)
|
|
if opt == "--version":
|
|
show_version ()
|
|
sys.exit (0)
|
|
elif opt == "--debug":
|
|
set_debugging (True)
|
|
|
|
# Must be done before connecting to D-Bus (for some reason).
|
|
if not Notify.init (PROGRAM_NAME):
|
|
try:
|
|
print(("%s: unable to initialize pynotify" %
|
|
PROGRAM_NAME), file=sys.stderr)
|
|
except:
|
|
pass
|
|
|
|
system_bus = session_bus = None
|
|
try:
|
|
system_bus = dbus.SystemBus()
|
|
except:
|
|
try:
|
|
print(("%s: failed to connect to system D-Bus" %
|
|
PROGRAM_NAME), file=sys.stderr)
|
|
finally:
|
|
sys.exit (1)
|
|
|
|
try:
|
|
session_bus = dbus.SessionBus()
|
|
# Stop running when the session ends.
|
|
session_bus.add_signal_receiver (monitor_session)
|
|
except:
|
|
try:
|
|
print(("%s: failed to connect to "
|
|
"session D-Bus" % PROGRAM_NAME), file=sys.stderr)
|
|
finally:
|
|
sys.exit (1)
|
|
|
|
try:
|
|
NewPrinterNotification(system_bus, session_bus)
|
|
except:
|
|
try:
|
|
print(("%s: failed to start "
|
|
"NewPrinterNotification service" %
|
|
PROGRAM_NAME), file=sys.stderr)
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
cupshelpers.installdriver.set_debugprint_fn (debugprint)
|
|
cupshelpers.installdriver.PrinterDriversInstaller(system_bus)
|
|
except Exception as e:
|
|
try:
|
|
print(("%s: failed to start "
|
|
"PrinterDriversInstaller service: "
|
|
"%s" % (PROGRAM_NAME, e)), file=sys.stderr)
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
# Create an abstract socket, by prefixing it with null.
|
|
sock.bind("\0printer-applet-lock-user-" + getpass.getuser())
|
|
except socket.error as err:
|
|
error_code = err.args[0]
|
|
error_string = err.args[1]
|
|
print("Process already running ({0}:{1}). Exiting.".format(error_code, error_string))
|
|
sys.exit(0)
|
|
|
|
loop = GLib.MainLoop ()
|
|
runloop = RunLoop (session_bus, system_bus, loop)
|
|
try:
|
|
runloop.run ()
|
|
except KeyboardInterrupt:
|
|
pass
|