#!/usr/local/bin/python3.10

## system-config-printer

## Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Red Hat, Inc.
## Authors:
##  Tim Waugh <twaugh@redhat.com>
##  Florian Festi <ffesti@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.

# config is generated from config.py.in by configure
import config

import authconn
import cupshelpers
from OpenPrintingRequest import OpenPrintingRequest

import errno
import sys, os, tempfile, time, re, http.client
import locale
import string
import subprocess
from timedops import *
import dbus
from gi.repository import Gdk
from gi.repository import Gtk
import functools

import cups

try:
    import pysmb
    PYSMB_AVAILABLE=True
except:
    PYSMB_AVAILABLE=False

import options
from gi.repository import GObject
from gi.repository import GLib
from gui import GtkGUI
from optionwidgets import OptionWidget
from debug import *
import probe_printer
import urllib.request, urllib.parse
from smburi import SMBURI
from errordialogs import *
from PhysicalDevice import PhysicalDevice
import firewallsettings
import asyncconn
import ppdsloader
import dnssdresolve
import installpackage

import gettext
gettext.install(domain=config.PACKAGE, localedir=config.localedir)

HTTPS_TIMEOUT = 15.0

TEXT_adjust_firewall = _("The firewall may need adjusting in order to "
                         "detect network printers.  Adjust the "
                         "firewall now?")

def validDeviceURI (uri):
    """Returns True is the provided URI is valid."""
    (scheme, rest) = urllib.parse.splittype (uri)
    if scheme is None or scheme == '':
        return False
    return True

# Both the printer properties window and the new printer window
# need to be able to drive 'class members' selections.
def moveClassMembers(treeview_from, treeview_to):
    selection = treeview_from.get_selection()
    model_from, rows = selection.get_selected_rows()
    rows = [Gtk.TreeRowReference.new(model_from, row) for row in rows]

    model_to = treeview_to.get_model()

    for row in rows:
        path = row.get_path()
        iter = model_from.get_iter(path)
        row_data = model_from.get(iter, 0)
        model_to.append(row_data)
        model_from.remove(iter)

def getCurrentClassMembers(treeview):
    model = treeview.get_model()
    iter = model.get_iter_first()
    result = []
    while iter:
        result.append(model.get(iter, 0)[0])
        iter = model.iter_next(iter)
    result.sort()
    return result

def checkNPName(printers, name):
    if not name: return False
    name = name.lower()
    for printer in printers.values():
        if not printer.discovered and printer.name.lower()==name:
            return False
    return True

def ready (win, cursor=None):
    try:
        gdkwin = win.get_window()
        if gdkwin:
            gdkwin.set_cursor (cursor)
            while Gtk.events_pending ():
                Gtk.main_iteration ()
    except:
        nonfatalException ()

def busy (win):
    ready (win, Gdk.Cursor.new(Gdk.CursorType.WATCH))

def on_delete_just_hide (widget, event):
    widget.hide ()
    return True # stop other handlers

def _singleton (x):
    """If we don't know whether getPPDs() or getPPDs2() was used, this
    function can unwrap an item from a list in either case."""
    if isinstance (x, list):
        return x[0]
    return x

def download_gpg_fingerprint(url):
    """Get GPG fingerprint from URL.

    Check that the URL is HTTPS with a valid and trusted server
    certificate, read it, extract the GPG fingerprint from it, and return
    it. Return None if the URL is invalid, not trusted, or the fingerprint
    can't be found.
    """
    if not url.startswith('https://'):
        debugprint('Not a https fingerprint URL: %s, ignoring driver' % url)
        return None

    content = None
    try:
        with urllib.request.urlopen(url, timeout=HTTPS_TIMEOUT) as resp:
            if resp.status == 200:
                content = resp.read().decode('utf-8')
    except Exception as e:
        debugprint('Cannot retrieve %s: %s' % (url, e))

    if content is None:
        debugprint('Cannot retrieve %s' % url)
        return None

    keyid_re = re.compile(r' ((?:(?:[0-9A-F]{4})(?:\s+|$)){10})$', re.M)

    m = keyid_re.search(content)
    if m:
        return m.group(1).strip().replace(' ','')

    return None

class NewPrinterGUI(GtkGUI):

    __gsignals__ = {
        'destroy':          (GObject.SignalFlags.RUN_LAST, None, ()),
        'printer-added' :   (GObject.SignalFlags.RUN_LAST, None, (str,)),
        'printer-modified': (GObject.SignalFlags.RUN_LAST, None,
                             (str,    # printer name
                              bool,)), # PPD modified?
        'driver-download-checked': (GObject.SignalFlags.RUN_LAST, None, (str,)),
        'dialog-canceled':  (GObject.SignalFlags.RUN_LAST, None, ()),
        }

    # Page numbers used to name the different stages
    PAGE_DESCRIBE_PRINTER = 0
    PAGE_SELECT_DEVICE = 1
    PAGE_SELECT_INSTALL_METHOD = 2
    PAGE_CHOOSE_DRIVER_FROM_DB = 3
    PAGE_CHOOSE_CLASS_MEMBERS = 4
    PAGE_APPLY = 5
    PAGE_INSTALLABLE_OPTIONS = 6
    PAGE_DOWNLOAD_DRIVER = 7

    # Values returned by _handlePrinterInstallationMode() and
    # related sub-functions, to know whether to continue the
    # execution of the remaining logic from calling functions
    INSTALL_RESULT_DONE = True
    INSTALL_RESULT_OPS_PENDING = False

    new_printer_device_tabs = {
        "parallel" : 0, # empty tab
        "usb" : 0,
        "bluetooth" : 0,
        "hal" : 0,
        "beh" : 0,
        "hp" : 0,
        "hpfax" : 0,
        "dnssd" : 0,
        "socket": 2,
        "lpd" : 3,
        "scsi" : 4,
        "serial" : 5,
        "smb" : 6,
        "network": 7,
        }

    def __init__(self):
        GObject.GObject.__init__ (self)
        self.language = locale.getlocale (locale.LC_MESSAGES)

        self.options = {} # keyword -> Option object
        self.changed = set()
        self.conflicts = set()
        self.device = None
        self.ppd = None
        self.remotecupsqueue = False
        self.exactdrivermatch = False
        self.installable_options = False
        self.ppdsloader = None
        self.installed_driver_files = []
        self.searchedfordriverpackages = False
        self.founddownloadabledrivers = False
        self.founddownloadableppd = False
        self.downloadable_driver_for_printer = None
        self.downloadable_printers = []
        self.nextnptab_rerun = False
        self.printers = {} # set in init()
        self.recommended_model_selected = False
        self._searchdialog = None
        self._installdialog = None

        self.getWidgets({"NewPrinterWindow":
                             ["NewPrinterWindow",
                              "ntbkNewPrinter",
                              "btnNPBack",
                              "btnNPForward",
                              "btnNPApply",
                              "spinner",
                              "entNPName",
                              "entNPDescription",
                              "entNPLocation",
                              "tvNPDevices",
                              "ntbkNPType",
                              "lblNPDeviceDescription",
                              "expNPDeviceURIs",
                              "tvNPDeviceURIs",
                              "cmbNPTSerialBaud",
                              "cmbNPTSerialParity",
                              "cmbNPTSerialBits",
                              "cmbNPTSerialFlow",
                              "btnNPTLpdProbe",
                              "entNPTLpdHost",
                              "entNPTLpdQueue",
                              "entNPTJetDirectHostname",
                              "entNPTJetDirectPort",
                              "entSMBURI",
                              "btnSMBBrowse",
                              "tblSMBAuth",
                              "rbtnSMBAuthPrompt",
                              "rbtnSMBAuthSet",
                              "entSMBUsername",
                              "entSMBPassword",
                              "btnSMBVerify",
                              "entNPTNetworkHostname",
                              "btnNetworkFind",
                              "lblNetworkFindSearching",
                              "lblNetworkFindNotFound",
                              "entNPTDevice",
                              "tvNCMembers",
                              "tvNCNotMembers",
                              "btnNCAddMember",
                              "btnNCDelMember",
                              "ntbkPPDSource",
                              "rbtnNPPPD",
                              "tvNPMakes",
                              "rbtnNPFoomatic",
                              "filechooserPPD",
                              "rbtnNPDownloadableDriverSearch",
                              "entNPDownloadableDriverSearch",
                              "btnNPDownloadableDriverSearch",
                              "btnNPDownloadableDriverSearch_label",
                              "cmbNPDownloadableDriverFoundPrinters",
                              "tvNPModels",
                              "tvNPDrivers",
                              "rbtnChangePPDasIs",
                              "rbtnChangePPDKeepSettings",
                              "scrNPInstallableOptions",
                              "vbNPInstallOptions",
                              "tvNPDownloadableDrivers",
                              "ntbkNPDownloadableDriverProperties",
                              "lblNPDownloadableDriverSupplier",
                              "cbNPDownloadableDriverSupplierVendor",
                              "lblNPDownloadableDriverLicense",
                              "cbNPDownloadableDriverLicensePatents",
                              "cbNPDownloadableDriverLicenseFree",
                              "lblNPDownloadableDriverDescription",
                              "lblNPDownloadableDriverSupportContacts",
                              "hsDownloadableDriverPerfText",
                              "hsDownloadableDriverPerfLineArt",
                              "hsDownloadableDriverPerfGraphics",
                              "hsDownloadableDriverPerfPhoto",
                              "lblDownloadableDriverPerfTextUnknown",
                              "lblDownloadableDriverPerfLineArtUnknown",
                              "lblDownloadableDriverPerfGraphicsUnknown",
                              "lblDownloadableDriverPerfPhotoUnknown",
                              "frmNPDownloadableDriverLicenseTerms",
                              "tvNPDownloadableDriverLicense",
                              "rbtnNPDownloadLicenseYes",
                              "rbtnNPDownloadLicenseNo"],
                         "WaitWindow":
                             ["WaitWindow",
                              "lblWait"],
                         "SMBBrowseDialog":
                             ["SMBBrowseDialog",
                              "tvSMBBrowser",
                              "btnSMBBrowseOk"]},

                        domain=config.PACKAGE)

        # Fill in liststores for combo-box widgets
        for (widget,
             opts) in [(self.cmbNPTSerialBaud,
                        [[_("Default"), ""],
                         ["1200", "1200"],
                         ["2400", "2400"],
                         ["4800", "4800"],
                         ["9600", "9600"],
                         ["19200", "19200"],
                         ["38400", "38400"],
                         ["57600", "57600"],
                         ["115200", "115200"]]),

                       (self.cmbNPTSerialParity,
                        [[_("Default"), ""],
                         [_("None"), "none"],
                         [_("Odd"), "odd"],
                         [_("Even"), "even"]]),

                       (self.cmbNPTSerialBits,
                        [[_("Default"), ""],
                         ["8", "8"],
                         ["7", "7"]]),

                       (self.cmbNPTSerialFlow,
                        [[_("Default"), ""],
                         [_("None"), "none"],
                         [_("XON/XOFF (Software)"), "soft"],
                         [_("RTS/CTS (Hardware)"), "hard"],
                         [_("DTR/DSR (Hardware)"), "hard"]]),

                       ]:
            store = Gtk.ListStore (str, str)
            for row in opts:
                store.append (row)

            widget.set_model (store)
            cell = Gtk.CellRendererText ()
            widget.clear ()
            widget.pack_start (cell, True)
            widget.add_attribute (cell, 'text', 0)

        # Set up some lists
        m = Gtk.SelectionMode.MULTIPLE
        s = Gtk.SelectionMode.SINGLE
        b = Gtk.SelectionMode.BROWSE
        for name, model, treeview, selection_mode in (
            (_("Members of this class"), Gtk.ListStore(str),
             self.tvNCMembers, m),
            (_("Others"), Gtk.ListStore(str), self.tvNCNotMembers, m),
            (_("Devices"), Gtk.ListStore(str), self.tvNPDevices, s),
            (_("Connections"), Gtk.ListStore(str), self.tvNPDeviceURIs, s),
            (_("Makes"), Gtk.ListStore(str, str), self.tvNPMakes,s),
            (_("Models"), Gtk.ListStore(str, str), self.tvNPModels,s),
            (_("Drivers"), Gtk.ListStore(str), self.tvNPDrivers,s),
            (_("Downloadable Drivers"), Gtk.ListStore(str),
             self.tvNPDownloadableDrivers, b),
            ):

            cell = Gtk.CellRendererText()
            column = Gtk.TreeViewColumn(name, cell, text=0)
            treeview.set_model(model)
            treeview.append_column(column)
            treeview.get_selection().set_mode(selection_mode)

        # Since some dialogs are reused we can't let the delete-event's
        # default handler destroy them
        self.SMBBrowseDialog.connect ("delete-event", on_delete_just_hide)
        self.WaitWindow_handler = self.WaitWindow.connect ("delete-event",
                                                           on_delete_just_hide)

        self.ntbkNewPrinter.set_show_tabs(False)
        self.ntbkPPDSource.set_show_tabs(False)
        self.ntbkNPType.set_show_tabs(False)
        self.ntbkNPDownloadableDriverProperties.set_show_tabs(False)

        self.spinner_count = 0

        # Set up OpenPrinting widgets.
        self.opreq = None
        self.opreq_handlers = None
        combobox = self.cmbNPDownloadableDriverFoundPrinters
        cell = Gtk.CellRendererText()
        combobox.pack_start (cell, True)
        combobox.add_attribute(cell, 'text', 0)
        if config.DOWNLOADABLE_ONLYFREE:
            for widget in [self.cbNPDownloadableDriverLicenseFree,
                           self.cbNPDownloadableDriverLicensePatents]:
                widget.hide ()
        if os.path.exists('/etc/apt/sources.list') or os.path.exists(
            '/etc/apt/sources.list.d'):
            config.packagesystem = 'deb'
            self.packageinstaller = 'apt'
        elif os.path.exists('/etc/yum.conf'):
            config.packagesystem = 'rpm'
            self.packageinstaller = 'yum'
        else:
            # No known package system, so we only load single PPDs via
            # OpenPrinting
            config.DOWNLOADABLE_ONLYPPD = True

        def protect_toggle (toggle_widget):
            active = getattr (toggle_widget, 'protect_active', None)
            if active is not None:
                toggle_widget.set_active (active)

        for widget in [self.cbNPDownloadableDriverSupplierVendor,
                       self.cbNPDownloadableDriverLicenseFree,
                       self.cbNPDownloadableDriverLicensePatents]:
            widget.connect ('clicked', protect_toggle)

        for widget in [self.hsDownloadableDriverPerfText,
                       self.hsDownloadableDriverPerfLineArt,
                       self.hsDownloadableDriverPerfGraphics,
                       self.hsDownloadableDriverPerfPhoto]:
            widget.connect ('change-value',
                            lambda x, y, z: True)

        # Device list
        slct = self.tvNPDevices.get_selection ()
        slct.set_select_function (self.device_select_function, None)
        self.tvNPDevices.set_row_separator_func (self.device_row_separator_fn, None)
        self.tvNPDevices.connect ("row-activated", self.device_row_activated)
        self.tvNPDevices.connect ("row-expanded", self.device_row_expanded)

        # Devices expander
        self.expNPDeviceURIs.connect ("notify::expanded",
                                      self.on_expNPDeviceURIs_expanded)
        self.expNPDeviceURIs.set_expanded(1)

        # SMB browser
        self.smb_store = Gtk.TreeStore (GObject.TYPE_PYOBJECT)
        self.btnSMBBrowse.set_sensitive (True)
        if not PYSMB_AVAILABLE:
            self.btnSMBBrowse.set_tooltip_text (_("Browsing requires pysmbc "
                                                  "module"))
        self.tvSMBBrowser.set_model (self.smb_store)

        # SMB list columns
        col = Gtk.TreeViewColumn (_("Share"))
        cell = Gtk.CellRendererText ()
        col.pack_start (cell, False)
        col.set_cell_data_func (cell, self.smbbrowser_cell_share, None)
        self.tvSMBBrowser.append_column (col)

        col = Gtk.TreeViewColumn (_("Comment"))
        cell = Gtk.CellRendererText ()
        col.pack_start (cell, False)
        col.set_cell_data_func (cell, self.smbbrowser_cell_comment, None)
        self.tvSMBBrowser.append_column (col)

        slct = self.tvSMBBrowser.get_selection ()
        slct.set_select_function (self.smb_select_function, None)

        self.SMBBrowseDialog.set_transient_for(self.NewPrinterWindow)

        self.tvNPDrivers.set_has_tooltip(True)
        self.tvNPDrivers.connect("query-tooltip", self.on_NPDrivers_query_tooltip)

        ppd_filter = Gtk.FileFilter()
        ppd_filter.set_name(_("PostScript Printer Description files (*.ppd, *.PPD, *.ppd.gz, *.PPD.gz, *.PPD.GZ)"))
        ppd_filter.add_pattern("*.ppd")
        ppd_filter.add_pattern("*.PPD")
        ppd_filter.add_pattern("*.ppd.gz")
        ppd_filter.add_pattern("*.PPD.gz")
        ppd_filter.add_pattern("*.PPD.GZ")
        self.filechooserPPD.add_filter(ppd_filter)

        ppd_filter = Gtk.FileFilter()
        ppd_filter.set_name(_("All files (*)"))
        ppd_filter.add_pattern("*")
        self.filechooserPPD.add_filter(ppd_filter)

        self.device_selected = -1
        self.dialog_mode = "printer"
        self.connect_signals ()
        debugprint ("+%s" % self)

    def __del__ (self):
        debugprint ("-%s" % self)

    def do_destroy (self):
        debugprint ("DESTROY: %s" % self)
        if self.SMBBrowseDialog:
            self.SMBBrowseDialog.destroy ()
            self.SMBBrowseDialog = None

        if self.NewPrinterWindow:
            self.NewPrinterWindow.destroy ()
            self.NewPrinterWindow = None

        if self.WaitWindow:
            self.WaitWindow.destroy ()
            self.WaitWindow = None

    def inc_spinner_task (self):
        if self.spinner_count == 0:
            self.spinner.show ()
            self.spinner.start ()

        self.spinner_count += 1

    def dec_spinner_task (self):
        self.spinner_count -= 1
        if self.spinner_count == 0:
            self.spinner.hide ()
            self.spinner.stop ()

    def show_IPP_Error (self, exception, message):
        debugprint ("%s: IPP error dialog (%s, %s)" % (self, repr (exception),
                                                       message))
        return show_IPP_Error (exception, message, parent=self.NewPrinterWindow)

    def option_changed(self, option):
        if option.is_changed():
            self.changed.add(option)
        else:
            self.changed.discard(option)

        if option.conflicts:
            self.conflicts.add(option)
        else:
            self.conflicts.discard(option)
        self.setDataButtonState()

        return

    def setDataButtonState(self):
        self.btnNPForward.set_sensitive(not bool(self.conflicts))

    def makeNameUnique(self, name):
        """Make a suggested queue name valid and unique."""
        name = name.replace (" ", "-")
        name = name.replace ("/", "-")
        name = name.replace ("#", "-")
        if not checkNPName (self.printers, name):
            suffix=2
            while not checkNPName (self.printers, name + "-" + str (suffix)):
                suffix += 1
                if suffix == 100:
                    break
            name += "-" + str (suffix)
        return name

    def destroy (self):
        self.emit ('destroy')

    def init(self, dialog_mode, device_uri=None, name=None, ppd=None,
             devid="", host=None, encryption=None, parent=None, xid=0):
        self.parent = parent
        if not self.parent:
            self.NewPrinterWindow.set_focus_on_map (False)

        self.dialog_mode = dialog_mode
        self._device_uri = device_uri
        self.orig_ppd = ppd
        self.devid = devid
        self._host = host
        self._encryption = encryption
        self._name = name
        if not host:
            self._host = cups.getServer ()
        if not encryption:
            self._encryption = cups.getEncryption ()

        self.options = {} # keyword -> Option object
        self.changed = set()
        self.conflicts = set()
        self.fetchDevices_conn = None
        self.ppds = None
        self.ppdsmatch_result = None
        self.printer_finder = None

        if not self._validInitParameters ():
            raise RuntimeError

        # Get a current list of printers so that we can know whether
        # the chosen name is unique.
        try:
            self.cups = authconn.Connection (parent=self.NewPrinterWindow,
                                             host=self._host,
                                             encryption=self._encryption)
        except cups.HTTPError as e:
            (s,) = e.args
            show_HTTP_Error (s, self.parent)
            return False
        except RuntimeError:
            show_HTTP_Error (-1, self.parent)
            return False
        except Exception as e:
            nonfatalException (e)
            return False

        try:
            self.printers = cupshelpers.getPrinters (self.cups)
        except cups.IPPError as e:
            (e, m) = e.args
            show_IPP_Error (e, m, parent=self.parent)
            return False

        # Initialise widgets.
        self.lblNetworkFindSearching.hide ()
        self.entNPTNetworkHostname.set_sensitive (True)
        self.entNPTNetworkHostname.set_text ('')
        self.btnNetworkFind.set_sensitive (True)
        self.lblNetworkFindNotFound.hide ()

        # Clear out any previous list of makes.
        model = self.tvNPMakes.get_model()
        model.clear()

        combobox = self.cmbNPDownloadableDriverFoundPrinters
        combobox.set_model (Gtk.ListStore (str, str))
        self.entNPDownloadableDriverSearch.set_text ('')
        label = self.btnNPDownloadableDriverSearch_label
        label.set_text (_("Search"))

        self.entNPTJetDirectPort.set_text('9100')
        self.rbtnSMBAuthPrompt.set_active(True)

        if xid != 0 and self.parent:
            self.NewPrinterWindow.show_now()
            self.NewPrinterWindow.set_transient_for (self.parent)

        if self.dialog_mode == "printer":
            self._initialisePrinterMode ()
        elif self.dialog_mode == "class":
            self._initialiseClassMode ()
        elif self.dialog_mode == "device":
            self._initialiseDeviceMode ()
        elif self.dialog_mode == "printer_with_uri":
            self._initialisePrinterWithURIMode ()
        elif self.dialog_mode == "ppd":
            self._initialisePPDMode ()
        elif self.dialog_mode == "download_driver":
            self._initialiseDownloadDriverMode ()
            return True

        if xid == 0 and self.parent:
            self.NewPrinterWindow.set_transient_for (parent)

        self.NewPrinterWindow.show()
        self.setNPButtons()
        return True

    def _validInitParameters (self):
        if self.dialog_mode in ['printer_with_uri', 'device', 'ppd']:
            return self._device_uri is not None
        elif self.dialog_mode == 'download_driver':
            return self.devid != ""
        return True

    def _initialiseClassMode (self):
        self._initialiseWidgetsForMode ("class")
        self.NewPrinterWindow.set_title (_("New Class"))

        self.fillNewClassMembers ()

        # Start on name page
        self.ntbkNewPrinter.set_current_page (self.PAGE_DESCRIBE_PRINTER)

    def _initialisePrinterMode (self):
        self._initialiseWidgetsForMode ("printer")
        self.NewPrinterWindow.set_title (_("New Printer"))

        # Start on devices page (SELECT_DEVICE, not DESCRIBE_PRINTER)
        self.ntbkNewPrinter.set_current_page (self.PAGE_SELECT_DEVICE)
        self.fillDeviceTab ()
        self.rbtnNPFoomatic.set_active (True)
        self.on_rbtnNPFoomatic_toggled (self.rbtnNPFoomatic)

    def _initialiseDeviceMode (self):
        self.NewPrinterWindow.set_title (_("Change Device URI"))
        self.ntbkNewPrinter.set_current_page (self.PAGE_SELECT_DEVICE)
        self.fillDeviceTab (self._device_uri)

    def _initialisePrinterWithURIMode (self):
        self._initialiseWidgetsForMode ("printer")
        self._initialiseDeviceFromURI ()

        self.NewPrinterWindow.set_title (_("New Printer"))

        self.ntbkNewPrinter.set_current_page (self.PAGE_SELECT_INSTALL_METHOD)
        self.rbtnNPFoomatic.set_active (True)
        self.on_rbtnNPFoomatic_toggled (self.rbtnNPFoomatic)
        self.rbtnChangePPDKeepSettings.set_active (True)

        self._initialiseAutoVariables ()
        self.nextNPTab (step = 0)

    def _initialiseDownloadDriverMode (self):
        self.NewPrinterWindow.set_title (_("Download Printer Driver"))

        self.ntbkNewPrinter.set_current_page (self.PAGE_DOWNLOAD_DRIVER)
        self.nextnptab_rerun = True
        self.nextNPTab (step = 0)

    def _initialisePPDMode (self):
        self._initialiseDeviceFromURI ()

        self.NewPrinterWindow.set_title (_("Change Driver"))

        # We'll need to know the Device ID for this device.
        if not self.devid:
            scheme = str (self._device_uri.split (":", 1)[0])
            schemes = [scheme]
            if scheme in ["socket", "lpd", "ipp"]:
                schemes.extend (["snmp", "dnssd"])
            self.fetchDevices_conn = asyncconn.Connection ()
            self.fetchDevices_conn._begin_operation (_("fetching device list"))
            self.inc_spinner_task ()
            cupshelpers.getDevices (self.fetchDevices_conn,
                                    include_schemes=schemes,
                                    reply_handler=self.change_ppd_got_devs,
                                    error_handler=self.change_ppd_got_devs)

        self.ntbkNewPrinter.set_current_page (self.PAGE_SELECT_INSTALL_METHOD)
        self.rbtnNPFoomatic.set_active (True)
        self.on_rbtnNPFoomatic_toggled (self.rbtnNPFoomatic)
        self.rbtnChangePPDKeepSettings.set_active (True)

        self._initialiseAutoVariables ()

    def _initialiseWidgetsForMode (self, mode_name):
        self.entNPName.set_text (self.makeNameUnique (mode_name))
        self.entNPName.grab_focus ()
        for widget in [self.entNPLocation,
                       self.entNPDescription,
                       self.entSMBURI, self.entSMBUsername,
                       self.entSMBPassword]:
            widget.set_text ('')

    def _initialiseDeviceFromURI (self):
        device_dict = { }
        self.device = cupshelpers.Device (self._device_uri, **device_dict)

    def _initialiseAutoVariables (self):
        self.auto_make = ""
        self.auto_model = ""
        self.auto_driver = None

    def change_ppd_got_devs (self, conn, result):
        self.fetchDevices_conn._end_operation ()
        self.fetchDevices_conn.destroy ()
        self.fetchDevices_conn = None
        self.dec_spinner_task ()
        if isinstance (result, Exception):
            current_devices = {}
        else:
            current_devices = result

        devid = None
        mm = None
        if self.devid != "":
            devid = self.devid
        else:
            device = current_devices.get (self.device.uri)
            if device:
                devid = device.id
                mm = device.make_and_model
                self.device = device

        # We'll also need the list of PPDs
        self.ntbkNewPrinter.set_current_page (self.PAGE_SELECT_INSTALL_METHOD)
        self.nextNPTab(step = 0)

    def on_ppdsloader_finished_next (self, ppdsloader):
        """
        This method is called when the PPDs loader has finished
        loading PPDs in preparation for the next screen the user will
        see, having clicked 'Forward'.  We are creating a new queue,
        and dialog_mode is either "printer" or "printer_with_uri".
        """

        self._getPPDs_reply (ppdsloader)
        if not self.ppds:
            return

        if ppdsloader._jockey_has_answered:
            self.searchedfordriverpackages = True

        debugprint ("Loaded PPDs this time; try nextNPTab again...")
        self.nextnptab_rerun = True
        if self.ntbkNewPrinter.get_current_page () == self.PAGE_SELECT_INSTALL_METHOD:
            self.nextNPTab (step = 0)
        else:
            self.nextNPTab ()

    # get PPDs

    def _getPPDs_reply (self, ppdsloader):
        exc = ppdsloader.get_error ()
        if exc:
            ppdsloader.destroy ()
            try:
                raise exc
            except cups.IPPError as e:
                (e, m) = e.args
                self.show_IPP_Error (e, m)
                return

        ppds = ppdsloader.get_ppds ()
        if ppds:
            self.ppds = ppds
            self.ppdsmatch_result = ppdsloader.get_ppdsmatch_result ()
            if ppdsloader._jockey_has_answered:
                self.installed_driver_files = ppdsloader.get_installed_files ()
        else:
            self.ppds = None
            self.ppdsmatch_result = None

        ppdsloader.destroy ()
        self.ppdsloader = None

    # Class members

    def fillNewClassMembers(self):
        model = self.tvNCMembers.get_model()
        model.clear()
        model = self.tvNCNotMembers.get_model()
        model.clear()
        try:
            self.printers = cupshelpers.getPrinters (self.cups)
        except cups.IPPError:
            self.printers = {}

        for printer in self.printers.keys():
            if not self.printers[printer].type & cups.CUPS_PRINTER_CLASS:
                model.append((printer,))

    def on_btnNCAddMember_clicked(self, button):
        moveClassMembers(self.tvNCNotMembers, self.tvNCMembers)
        self.btnNPApply.set_sensitive(
            bool(getCurrentClassMembers(self.tvNCMembers)))
        button.set_sensitive(False)

    def on_btnNCDelMember_clicked(self, button):
        moveClassMembers(self.tvNCMembers, self.tvNCNotMembers)
        self.btnNPApply.set_sensitive(
            bool(getCurrentClassMembers(self.tvNCMembers)))
        button.set_sensitive(False)

    def on_tvNCMembers_cursor_changed(self, widget):
        selection = widget.get_selection()
        if selection is None:
            return

        model_from, rows = selection.get_selected_rows()
        self.btnNCDelMember.set_sensitive(rows != [])

    def on_tvNCNotMembers_cursor_changed(self, widget):
        selection = widget.get_selection()
        if selection is None:
            return

        model_from, rows = selection.get_selected_rows()
        self.btnNCAddMember.set_sensitive(rows != [])

    # Navigation buttons

    def on_NPCancel(self, widget, event=None):
        if self.fetchDevices_conn:
            self.fetchDevices_conn.destroy ()
            self.fetchDevices_conn = None
            self.dec_spinner_task ()

        if self.ppdsloader:
            self.ppdsloader.destroy ()
            self.ppdsloader = None

        if self.printer_finder:
            self.printer_finder.cancel ()
            self.printer_finder = None
            self.dec_spinner_task ()

        self.NewPrinterWindow.hide()
        if self.opreq is not None:
            for handler in self.opreq_handlers:
                self.opreq.disconnect (handler)

            self.opreq_handlers = None
            self.opreq.cancel ()
            self.opreq = None

        self.device = None
        self.printers = {}
        self.emit ('dialog-canceled')
        return True

    def on_btnNPBack_clicked(self, widget):
        self.nextNPTab(-1)

    def on_btnNPForward_clicked(self, widget):
        self.nextNPTab()

    def installdriverpackage (self, driver):
        install_info = self._getDriverInstallationInfo (driver)
        if not install_info:
            return False

        name = install_info['name']
        repo = install_info['repo']
        keyid = install_info['keyid']

        fmt = _("Installing driver %s") % name
        self._installdialog = Gtk.MessageDialog (parent=self.NewPrinterWindow,
                                                 modal=True, destroy_with_parent=True,
                                                 type=Gtk.MessageType.INFO,
                                                 buttons=Gtk.ButtonsType.CANCEL,
                                                 text=fmt)

        self._installdialog.format_secondary_text (_("Installing ..."))
        # Add a progress bar to the message box
        dialogarea = self._installdialog.get_message_area()
        pbar = Gtk.ProgressBar()
        dialogarea.add(pbar)
        pbar.show()

        # Save a reference to the progress bar in the dialog, so that
        # we can easily reference it from do_installdriverpackage()
        self._installdialog._progress_bar = pbar

        self._installdialog.connect ("response", self._installdialog_response)
        self._installdialog.show_all ()

        # Perform the actual installation of the printer driver
        ret = self.do_installdriverpackage (name, repo, keyid)

        if self._installdialog:
            self._installdialog.hide ()
            self._installdialog.destroy ()
            self._installdialog = None

        return ret

    def do_installdriverpackage(self, name, repo, keyid):
        debugprint('Installing driver: %s; Repo: %s; Key ID: %s' %
                   (repr (name),
                    repr (repo),
                    repr (keyid)))

        # Do the installation with a command line helper script
        new_environ = os.environ.copy()
        new_environ['LC_ALL'] = "C"
        if keyid:
            args = ["install-printerdriver", name, repo, keyid]
        else:
            args = ["install-printerdriver", name, repo]
        debugprint ("Running command: " + repr(args))
        ret = True
        try:
            self.p = subprocess.Popen (args, env=new_environ, close_fds=True,
                                       stdin=subprocess.DEVNULL,
                                       stdout=subprocess.PIPE)
            # Keep the UI refreshed while we wait for
            # the drivers query to complete.
            (stdout, stderr) = (self.p.stdout, self.p.stderr)

            done = False
            pbar = self._installdialog._progress_bar
            while self.p.poll() is None:
                line = stdout.readline ().strip()
                if (len(line) > 0):
                    if line == "done":
                        done = True
                        break
                    elif line.startswith(b"P"):
                        try:
                            percentage = float(line[1:])
                            if percentage >= 0:
                                pbar.set_fraction(percentage/100)
                            else:
                                pbar.set_pulse_step(-percentage/100)
                                pbar.pulse()
                        except:
                            pass
                    else:
                        self.installed_driver_files.append(line.decode("utf-8"));
                while Gtk.events_pending ():
                    Gtk.main_iteration ()
                if not line:
                    time.sleep (0.1)
            if self.p.returncode != 0 and not done:
                ret = False
        except:
            # Problem executing command.
            ret = False

        if not ret:
            self.installed_driver_files = [];

        return ret

    def _getDriverInstallationInfo (self, driver):
        pkgs = driver.get('packages', {})
        arches = list(pkgs.keys())
        if len(arches) == 0:
            debugprint('No packages for driver')
            return None
        if len(arches) > 1:
            debugprint('Returned more than one matching architecture, please report this as a bug: %s' % repr (arches))
            return None

        pkgs = pkgs[arches[0]]

        if len(pkgs) != 1:
            debugprint('Returned more than one package, this is currently not handled')
            return None
        pkg = list(pkgs.keys())[0]

        name = ''
        if pkg.endswith('.deb'):
            name = pkg.split('_')[0]
        elif pkg.endswith('.rpm'):
            name = '-'.join(pkg.split('-')[0:-2])
        else:
            raise ValueError('Unknown package type: ' + pkg)

        # require signature for binary packages; architecture
        # independent packages are usually PPDs, which we trust enough
        keyid = None
        if 'fingerprint' not in pkgs[pkg]:
            if config.DOWNLOADABLE_PKG_ONLYSIGNED and arches[0] not in ['all', 'noarch']:
                debugprint('Not installing driver as it does not have a GPG fingerprint URL')
                return None
        else:
            keyid = download_gpg_fingerprint(pkgs[pkg]['fingerprint'])
            if config.DOWNLOADABLE_PKG_ONLYSIGNED and arches[0] not in ['all', 'noarch'] and not keyid:
                debugprint('Not installing driver as it does not have a valid GPG fingerprint')
                return None


        repo = pkgs[pkg].get('repositories', {}).get(self.packageinstaller)
        if not repo:
            debugprint('Local package system %s not found in %s' %
                       (self.packageinstaller,
                        repr (pkgs[pkg].get('repositories', {}))))
            return None

        # All good: return necessary information to install the driver
        return { 'name': name, 'repo': repo, 'keyid': keyid }

    def _installdialog_response (self, dialog, response):
        self.p.terminate ()

    def nextNPTab(self, step=1):
        page_nr = self.ntbkNewPrinter.get_current_page()
        debugprint ("Next clicked on page %d" % page_nr)

        keep_going = True
        if self.dialog_mode == "printer" or self.dialog_mode == "printer_with_uri" or \
              self.dialog_mode == "ppd" or self.dialog_mode == "download_driver":
            install_result = self._handlePrinterInstallationMode (step)
            if install_result == self.INSTALL_RESULT_OPS_PENDING:
                # Do not continue if the installation process says so
                # (e.g. waiting for a CUPS to return a list of PPDs)
                keep_going = False

        if not keep_going:
            debugprint ('Interrupting execution of nextNPTab(): Operations pending')
            return

        order = self._getPagesOrderForDialogMode ()
        next_page_nr = order[order.index(page_nr)+step]

        # fill Installable Options tab
        fetch_ppd = False
        try:
            if order.index (self.PAGE_APPLY) > -1:
                # There is a copy settings page in this set
                fetch_ppd = next_page_nr == self.PAGE_APPLY and step >= 0
        except ValueError:
            fetch_ppd = next_page_nr == self.PAGE_INSTALLABLE_OPTIONS and step >= 0

        debugprint ("Will fetch ppd? %d" % fetch_ppd)
        if fetch_ppd:
            self.ppd = self.getNPPPD()
            self.installable_options = False
            if self.ppd is None:
                return

            # Prepare Installable Options screen.
            if isinstance(self.ppd, cups.PPD):
                self.fillNPInstallableOptions()
            else:
                # Put a label there explaining why the page is empty.
                ppd = self.ppd
                self.ppd = None
                self.fillNPInstallableOptions()
                self.ppd = ppd

            if not self.installable_options:
                if next_page_nr == self.PAGE_INSTALLABLE_OPTIONS:
                    # step over if empty
                    next_page_nr = order[order.index(next_page_nr)+1]

        # Step over empty Installable Options tab when moving backwards.
        if next_page_nr == self.PAGE_INSTALLABLE_OPTIONS and \
                not self.installable_options and step < 0:
            next_page_nr = order[order.index(next_page_nr)-1]

        debugprint ("Will advance to page %d" % next_page_nr)
        if step >= 0 and next_page_nr == self.PAGE_DOWNLOAD_DRIVER: # About to show downloadable drivers
            self.fillDownloadableDrivers ()

        if step >= 0 and next_page_nr == self.PAGE_DESCRIBE_PRINTER: # About to choose a name.
            # Suggest an appropriate name.
            name = None
            descr = None

            try:
                if (self.device.id and
                    not self.device.type in ("socket", "lpd", "ipp",
                                             "http", "https", "bluetooth")):
                    name = "%s %s" % (self.device.id_dict["MFG"], 
                                      self.device.id_dict["MDL"])
            except:
                nonfatalException ()

            try:
                if name is None and isinstance (self.ppd, cups.PPD):
                    mname = self.ppd.findAttr ("modelName").value
                    make, model = cupshelpers.ppds.ppdMakeModelSplit (mname)
                    if make and model:
                        name = "%s %s" % (make, model)
                    elif make or model:
                        name = "%s%s" % (make, model)
            except:
                nonfatalException ()

            if name:
                descr = name
            else:
                name = 'printer'

            name = self.makeNameUnique (name)
            self.entNPName.set_text (name)

            if descr:
                self.entNPDescription.set_text (descr)

        self.ntbkNewPrinter.set_current_page(next_page_nr)

        self.setNPButtons()


    def _getPagesOrderForDialogMode (self):
        order = []
        if self.dialog_mode == "class":
            order = [
                self.PAGE_DESCRIBE_PRINTER,
                self.PAGE_CHOOSE_CLASS_MEMBERS,
                self.PAGE_APPLY,
            ]
        elif self.dialog_mode == "download_driver":
            order = [
                self.PAGE_DOWNLOAD_DRIVER
            ]
        elif self.dialog_mode == "printer":
            if self.remotecupsqueue:
                order = [
                    self.PAGE_SELECT_DEVICE,
                    self.PAGE_DESCRIBE_PRINTER
                ]
            elif (self.founddownloadabledrivers and
                  not self.rbtnNPDownloadableDriverSearch.get_active()):
                if self.exactdrivermatch:
                    order = [
                        self.PAGE_SELECT_DEVICE,
                        self.PAGE_DOWNLOAD_DRIVER,
                        self.PAGE_INSTALLABLE_OPTIONS,
                        self.PAGE_DESCRIBE_PRINTER
                    ]
                else:
                    order = [
                        self.PAGE_SELECT_DEVICE,
                        self.PAGE_DOWNLOAD_DRIVER,
                        self.PAGE_SELECT_INSTALL_METHOD,
                        self.PAGE_CHOOSE_DRIVER_FROM_DB,
                        self.PAGE_INSTALLABLE_OPTIONS,
                        self.PAGE_DESCRIBE_PRINTER
                    ]
            elif (self.exactdrivermatch and
                  not self.rbtnNPDownloadableDriverSearch.get_active()):
                order = [
                    self.PAGE_SELECT_DEVICE,
                    self.PAGE_INSTALLABLE_OPTIONS,
                    self.PAGE_DESCRIBE_PRINTER
                ]
            elif self.rbtnNPFoomatic.get_active():
                order = [
                    self.PAGE_SELECT_DEVICE,
                    self.PAGE_SELECT_INSTALL_METHOD,
                    self.PAGE_CHOOSE_DRIVER_FROM_DB,
                    self.PAGE_INSTALLABLE_OPTIONS,
                    self.PAGE_DESCRIBE_PRINTER
                ]
            elif self.rbtnNPPPD.get_active():
                order = [
                    self.PAGE_SELECT_DEVICE,
                    self.PAGE_SELECT_INSTALL_METHOD,
                    self.PAGE_INSTALLABLE_OPTIONS,
                    self.PAGE_DESCRIBE_PRINTER
                ]
            else:
                # Downloadable driver
                order = [
                    self.PAGE_SELECT_DEVICE,
                    self.PAGE_SELECT_INSTALL_METHOD,
                    self.PAGE_DOWNLOAD_DRIVER,
                    self.PAGE_INSTALLABLE_OPTIONS,
                    self.PAGE_DESCRIBE_PRINTER
                ]
        elif self.dialog_mode == "ppd":
            if self.rbtnNPFoomatic.get_active():
                order = [
                    self.PAGE_SELECT_INSTALL_METHOD,
                    self.PAGE_CHOOSE_DRIVER_FROM_DB,
                    self.PAGE_APPLY,
                    self.PAGE_INSTALLABLE_OPTIONS
                ]
            elif self.rbtnNPPPD.get_active():
                order = [
                    self.PAGE_SELECT_INSTALL_METHOD,
                    self.PAGE_APPLY,
                    self.PAGE_INSTALLABLE_OPTIONS
                ]
            else:
                # Downloadable driver
                order = [
                    self.PAGE_SELECT_INSTALL_METHOD,
                    self.PAGE_DOWNLOAD_DRIVER,
                    self.PAGE_APPLY,
                    self.PAGE_INSTALLABLE_OPTIONS
                ]
        elif self.dialog_mode == "device":
            order = [
                self.PAGE_SELECT_DEVICE,
            ]
        else: # dialog_mode == "printer-from-uri"
            if self.remotecupsqueue:
                order = [
                    self.PAGE_DESCRIBE_PRINTER,
                ]
            elif (self.founddownloadabledrivers and
                  not self.rbtnNPDownloadableDriverSearch.get_active()):
                if self.exactdrivermatch:
                    order = [
                        self.PAGE_DOWNLOAD_DRIVER,
                        self.PAGE_INSTALLABLE_OPTIONS,
                        self.PAGE_DESCRIBE_PRINTER
                    ]
                else:
                    order = [
                        self.PAGE_DOWNLOAD_DRIVER,
                        self.PAGE_SELECT_INSTALL_METHOD,
                        self.PAGE_CHOOSE_DRIVER_FROM_DB,
                        self.PAGE_INSTALLABLE_OPTIONS,
                        self.PAGE_DESCRIBE_PRINTER
                    ]
            elif (self.exactdrivermatch and
                  not self.rbtnNPDownloadableDriverSearch.get_active()):
                order = [
                    self.PAGE_INSTALLABLE_OPTIONS,
                    self.PAGE_DESCRIBE_PRINTER
                ]
            elif self.rbtnNPFoomatic.get_active():
                order = [
                    self.PAGE_SELECT_INSTALL_METHOD,
                    self.PAGE_CHOOSE_DRIVER_FROM_DB,
                    self.PAGE_INSTALLABLE_OPTIONS,
                    self.PAGE_DESCRIBE_PRINTER
                ]
            elif self.rbtnNPPPD.get_active():
                order = [
                    self.PAGE_SELECT_INSTALL_METHOD,
                    self.PAGE_INSTALLABLE_OPTIONS,
                    self.PAGE_DESCRIBE_PRINTER
                ]
            else:
                # Downloadable driver
                order = [
                    self.PAGE_SELECT_INSTALL_METHOD,
                    self.PAGE_DOWNLOAD_DRIVER,
                    self.PAGE_INSTALLABLE_OPTIONS,
                    self.PAGE_DESCRIBE_PRINTER
                ]
        return order

    def _handlePrinterInstallationMode (self, step):
        busy (self.NewPrinterWindow)

        page_nr = self.ntbkNewPrinter.get_current_page ()
        page_nr = self.ntbkNewPrinter.get_current_page ()

        # Let's assume everything will be completed by default
        result = self.INSTALL_RESULT_DONE

        if (((page_nr == self.PAGE_SELECT_DEVICE or
              page_nr == self.PAGE_DOWNLOAD_DRIVER) and step > 0) or
            ((page_nr == self.PAGE_SELECT_INSTALL_METHOD or
              page_nr == self.PAGE_DOWNLOAD_DRIVER) and step == 0)):
            # The function below will return INSTALL_RESULT_OPS_PENDING
            # if, for some reason, it spawns an asynchronous operation
            # and needs to wait for its results before continuing.
            result = self._handlePrinterInstallationStage (page_nr, step)

        if page_nr == self.PAGE_CHOOSE_DRIVER_FROM_DB:
            if not self.device.id:
                # Choose an appropriate name when no Device ID
                # is available, based on the model the user has
                # selected.
                try:
                    model, iter = self.tvNPModels.get_selection ().\
                                  get_selected ()
                    name = model.get(iter, 0)[0]
                    name = self.makeNameUnique (name)
                    self.entNPName.set_text (name)
                except:
                    nonfatalException ()

        ready (self.NewPrinterWindow)
        return result

    def _handlePrinterInstallationStage (self, page_nr, step):
        if self.dialog_mode != "download_driver":
            uri = self.device.uri
            if uri and uri.startswith ("smb"):
                # User has selected an smb device
                uri = SMBURI (uri=uri[6:]).sanitize_uri ()
                self._installSMBBackendIfNeeded ()

        if page_nr == self.PAGE_SELECT_DEVICE or page_nr == self.PAGE_SELECT_INSTALL_METHOD:
            self._selectDeviceForInstallation (uri)
        elif page_nr == self.PAGE_DOWNLOAD_DRIVER and self.nextnptab_rerun == False:
            self._handleDriverInstallation ()

        devid = None
        if not self.remotecupsqueue or self.dialog_mode == "ppd":
            if self.dialog_mode != "download_driver":
                devid = self.device.id # ID of selected device
            if not devid:
                devid = self.devid # ID supplied at init()
            if not devid:
                devid = None

            if self.ppds is None and self.dialog_mode != "download_driver":
                self._loadPPDsForDevice (devid, uri)
                # _loadPPDsForDevice () is an asynchronous operation, so
                # let the caller know that it can't continue for now.
                return self.INSTALL_RESULT_OPS_PENDING

        self.nextnptab_rerun = False
        if page_nr == self.PAGE_SELECT_DEVICE or page_nr == self.PAGE_SELECT_INSTALL_METHOD:
            self._installHPScannerFilesIfNeeded ()

        return self._installPrinterFromDeviceID (devid, page_nr, step)

    def _installSMBBackendIfNeeded (self):
        # Does the backend need to be installed?
        if (self.nextnptab_rerun == False and not self.searchedfordriverpackages and
               (self._host == 'localhost' or self._host[0] == '/') and
               not os.access ("/usr/local/libexec/cups/backend/smb", os.F_OK)):
            debugprint ("No smb backend so attempting install")
            try:
                pk = installpackage.PackageKit ()
                # The following call means a blocking, synchronous, D-Bus call
                pk.InstallPackageName (0, 0, "samba-client")
            except:
                nonfatalException ()

    def _selectDeviceForInstallation (self, uri):
        self._initialiseAutoVariables ()
        self.device.uri = self.getDeviceURI ()

        # Cancel the printer finder now as the user has
        # already selected their device.
        if self.fetchDevices_conn:
            self.fetchDevices_conn.destroy ()
            self.fetchDevices_conn = None
            self.dec_spinner_task ()
        if self.printer_finder:
            self.printer_finder.cancel ()
            self.printer_finder = None
            self.dec_spinner_task ()

        if (not self.device.id and
            self.device.type in ["socket", "lpd", "ipp"]):
            # This is a network printer whose model we don't yet know.
            # Try to discover it.
            self.getNetworkPrinterMakeModel ()

        # Try to access the PPD, in this case our detected IPP
        # printer is a queue on a remote CUPS server which is
        # not automatically set up on our local CUPS server
        # (for example DNS-SD broadcasted queue from Mac OS X)
        self.remotecupsqueue = None
        res = re.search (r"ipp://(\S+?)(:\d+|)/printers/(\S+)", uri)
        if res:
            resg = res.groups()
            if len (resg[1]) > 0:
                port = int (resg[1][1:])
            else:
                port = 631
            try:
                debugprint('Download ppd file from remote server')
                conn = http.client.HTTPConnection(resg[0], port)
                conn.request("GET", "/printers/%s.ppd" % resg[2])
                resp = conn.getresponse()
                if resp.status == 200:
                    self.remotecupsqueue = resg[2]

                ppdcontent = resp.read()

                with tempfile.NamedTemporaryFile () as tmpf:
                    tmpf.write(ppdcontent)
                    tmpf.flush()
                    try:
                        ppd = cups.PPD(tmpf.name)
                    except (cups.IPPError, RuntimeError):
                        raise IOError("Server's ppd file is corrupted.")
            except:
                pass

            # We also want to fetch the printer-info and
            # printer-location attributes, to pre-fill those
            # fields for this new queue.
            try:
                encryption = cups.HTTP_ENCRYPT_IF_REQUESTED
                c = cups.Connection (host=resg[0],
                                     port=port,
                                     encryption=encryption)

                r = ['printer-info', 'printer-location']
                attrs = c.getPrinterAttributes (uri=uri,
                                                requested_attributes=r)
                info = attrs.get ('printer-info', '')
                location = attrs.get ('printer-location', '')
                if len (info) > 0:
                    self.entNPDescription.set_text (info)
                if len (location) > 0:
                    self.device.location = location
            except RuntimeError:
                pass
            except:
                nonfatalException ()
        elif ((uri.startswith ("dnssd:") or uri.startswith("mdns:")) and
                uri.find ("/cups") != -1 and self.device.info):
            # Remote CUPS queue discovered by "dnssd" CUPS backend
            self.remotecupsqueue = self.device.info

    def _handleDriverInstallation (self):
        # Install package of the driver found on OpenPrinting
        treeview = self.tvNPDownloadableDrivers
        model, iter = treeview.get_selection ().get_selected ()
        driver = None
        if iter is not None:
            driver = model.get_value (iter, 1)
        if driver is None or driver == 0 or 'packages' not in driver:
            return

        # Find the package name, repository, and fingerprint
        # and install the package
        result = self.installdriverpackage (driver)
        if not result or len(self.installed_driver_files) == 0:
          return

        # We actually installed a package, delete the
        # PPD list to get it regenerated
        self.ppds = None

        if (self.dialog_mode != "download_driver" and
              (not self.device.id and
              (not self.device.make_and_model or self.device.make_and_model == "Unknown") and
              self.downloadable_driver_for_printer)):
            self.device.make_and_model = self.downloadable_driver_for_printer

    def _installHPScannerFilesIfNeeded (self):
        if (hasattr (self.device, 'hp_scannable') and self.device.hp_scannable and
                not os.access ("/etc/sane.d/dll.d/hpaio", os.R_OK) and
                not os.access ("/etc/sane.d/dll.d/hplip", os.R_OK)):
            debugprint ("No HPLIP sane backend so "
                        "attempting install")
            try:
                pk = installpackage.PackageKit ()
                # The following call means a blocking, synchronous, D-Bus call
                pk.InstallPackageName (0, 0, "libsane-hpaio")
            except:
                pass

    def _loadPPDsForDevice (self, devid, uri):
        debugprint ("nextNPTab: need PPDs loaded")
        p = ppdsloader.PPDsLoader (device_id=devid,
                                   device_uri=uri,
                                   parent=self.NewPrinterWindow,
                                   host=self._host,
                                   encryption=self._encryption)
        self.ppdsloader = p
        p.connect ('finished',self.on_ppdsloader_finished_next)
        p.run ()

    def _installPrinterFromDeviceID (self, devid, page_nr, step):
        ppdname = None
        self.id_matched_ppdnames = []
        try:
            if self.dialog_mode == "download_driver":
                ppdname = "download"
                status = "generic"
            elif self.remotecupsqueue:
                # We have a remote CUPS queue, let the client queue
                # stay raw so that the driver on the server gets used
                if self.ppd is None:
                    ppdname = 'raw'
                    self.ppd = ppdname
                name = self.remotecupsqueue
                name = self.makeNameUnique (name)
                self.entNPName.set_text (name)
                status = "exact"
            elif (self.device.id or
                  (self.device.make_and_model and
                   self.device.make_and_model != "Unknown") or
                  devid):
                if self.device.id:
                    id_dict = self.device.id_dict
                elif devid:
                    id_dict = cupshelpers.parseDeviceID (devid)
                else:
                    id_dict = {}
                    (id_dict["MFG"],
                     id_dict["MDL"]) = cupshelpers.ppds.\
                         ppdMakeModelSplit (self.device.make_and_model)
                    id_dict["DES"] = ""
                    id_dict["CMD"] = []
                    devid = "MFG:%s;MDL:%s;" % (id_dict["MFG"],
                                                id_dict["MDL"])

                fit = self.ppds.\
                    getPPDNamesFromDeviceID (id_dict["MFG"],
                                             id_dict["MDL"],
                                             id_dict["DES"],
                                             id_dict["CMD"],
                                             self.device.uri,
                                             self.device.make_and_model)
                debugprint ("Suitable PPDs found: %s" % repr(fit))
                ppdnamelist = self.ppds.\
                    orderPPDNamesByPreference (list(fit.keys ()),
                                               self.installed_driver_files,
                                               devid=id_dict, fit=fit)
                debugprint ("PPDs in priority order: %s" % repr(ppdnamelist))
                self.id_matched_ppdnames = ppdnamelist
                ppdname = ppdnamelist[0]
                status = fit[ppdname]
            elif (self.dialog_mode == "ppd" and self.orig_ppd):
                attr = self.orig_ppd.findAttr("NickName")
                if not attr:
                    attr = self.orig_ppd.findAttr("ModelName")

                if attr and attr.value:
                    value = attr.value
                    if value.endswith (" (recommended)"):
                        value = value[:-14]

                    mfgmdl = cupshelpers.ppds.ppdMakeModelSplit (value)
                    (make, model) = mfgmdl

                    # Search for ppdname with that make-and-model
                    ppds = self.ppds.getInfoFromModel (make, model)
                    for ppd, info in ppds.items ():
                        if (_singleton (info.
                                        get ("ppd-make-and-model")) ==
                            value):
                            ppdname = ppd
                            break
                if ppdname:
                    status = "exact"
                else:
                    ppdname = 'raw'
                    self.ppd = ppdname
                    status = "generic"
            elif self.dialog_mode == "ppd":
                # Special CUPS names for a raw queue.
                ppdname = 'raw'
                self.ppd = ppdname
                status = "exact"
            else:
                (status, ppdname) = self.ppds.\
                    getPPDNameFromDeviceID ("Generic",
                                            "Printer",
                                            "Generic Printer",
                                            [],
                                            self.device.uri)
                status = "generic"
        except:
            nonfatalException ()

        if (ppdname and
            (not self.remotecupsqueue or self.dialog_mode == "ppd")):
            return self._installPrinterOrSearchForDriver (devid, ppdname, status, page_nr, step)

        # No operations are pending if reached.
        return self.INSTALL_RESULT_DONE

    def _installPrinterOrSearchForDriver (self, devid, ppdname, status, page_nr, step):
        try:
            if ppdname != "download":
                ppddict = self.ppds.getInfoFromPPDName (ppdname)
                make_model = _singleton (ppddict['ppd-make-and-model'])
                (make, model) = \
                    cupshelpers.ppds.ppdMakeModelSplit (make_model)
                self.auto_make = make
                self.auto_model = model
                self.auto_driver = ppdname
                self.fillDriverList(make, model)
            if ((status == "exact" or status == "exact-cmd") and \
                self.dialog_mode != "ppd"):
                self.exactdrivermatch = True
                if step == 0:
                    page_nr = self.PAGE_INSTALLABLE_OPTIONS;
            else:
                self.exactdrivermatch = False
                if (self.dialog_mode != "ppd" and
                    self.searchedfordriverpackages == False and
                    devid and len(devid) > 0 and
                    not (devid.find("MFG:generic;") >= 0 or
                         devid.find("MFG:Generic;") >= 0 or
                         devid.find("MFG:unknown") >= 0 or
                         devid.find("MFG:Unknown") >= 0 or
                         devid.find("MDL:unknown") >= 0 or
                         devid.find("MDL:Unknown") >= 0 or
                         devid.find("MFG:;") >= 0 or
                         devid.find("MDL:;") >= 0)):
                    # Query driver packages and PPD files on
                    # OpenPrinting
                    debugprint ("nextNPTab: No exact driver match, querying OpenPrinting")
                    debugprint ('nextNPTab: Searching for "%s"' % devid)
                    self.searchedfordriverpackages = True

                    self._searchdialog_canceled = False
                    fmt = _("Searching")
                    self._searchdialog = Gtk.MessageDialog (
                        parent=self.NewPrinterWindow,
                        modal=True,
                        destroy_with_parent=True,
                        message_type=Gtk.MessageType.INFO,
                        buttons=Gtk.ButtonsType.CANCEL,
                        text=fmt)

                    self._searchdialog.format_secondary_text (
                        _("Searching for drivers"))

                    self.opreq = OpenPrintingRequest ()
                    self._searchdialog.connect (
                        "response", self._searchdialog_response)
                    self._searchdialog.show_all ()

                    self.opreq_handlers = []
                    self.opreq_handlers.append (
                        self.opreq.connect (
                            'finished',
                            self.opreq_id_search_done))
                    self.opreq_handlers.append (
                        self.opreq.connect (
                            'error',
                            self.opreq_id_search_error))
                    self.opreq_user_search = False
                    self.opreq.searchPrinters (devid)

                    # Searching for drivers in OpenPrinting takes times, so
                    # let the caller know that it can't continue for now.
                    return self.INSTALL_RESULT_OPS_PENDING
        except:
            nonfatalException ()

        if (self.dialog_mode == "ppd" or
                (self.dialog_mode != "download_driver" and
                 not self.remotecupsqueue and page_nr != self.PAGE_DOWNLOAD_DRIVER)):
            self.fillMakeList()

        # No operations are pending if reached.
        return self.INSTALL_RESULT_DONE

    def _searchdialog_response (self, dialog, response):
        # Cancel clicked while performing openprinting search

        self.btnNPDownloadableDriverSearch.set_sensitive (True)
        self.btnNPDownloadableDriverSearch_label.set_text (_("Search"))

        self.installed_driver_files = []
        self.searchedfordriverpackages = True
        self.founddownloadabledrivers = False
        self.founddownloadableppd = False

        ready (self.NewPrinterWindow)

        # Cancel the openprinting request.
        GLib.idle_add (self.opreq.cancel)

    def opreq_id_search_done (self, opreq, printers, drivers):
        for handler in self.opreq_handlers:
            opreq.disconnect (handler)

        Gdk.threads_enter ()

        try:
            self.opreq_user_search = False
            self.opreq_handlers = None
            self.opreq = None
            self._searchdialog.hide ()
            self._searchdialog.destroy ()
            self._searchdialog = None

            # Check whether we have found something
            if len (printers) < 1:
                # No.
                ready (self.NewPrinterWindow)

                self.founddownloadabledrivers = False
                if self.dialog_mode == "download_driver":
                    self.on_NPCancel(None)
                else:
                    self.nextNPTab ()
            else:
                self.downloadable_printers = printers
                self.downloadable_drivers = drivers
                self.founddownloadabledrivers = True

                try:
                    self.NewPrinterWindow.show()
                    self.setNPButtons()
                    if not self.fillDownloadableDrivers():
                        ready (self.NewPrinterWindow)

                        self.founddownloadabledrivers = False
                        if self.dialog_mode == "download_driver":
                            self.on_NPCancel(None)
                        else:
                            self.nextNPTab ()
                    else:
                        if self.dialog_mode == "download_driver":
                            self.nextNPTab (step = 0)
                        else:
                            self.nextNPTab ()
                except:
                    nonfatalException ()
                    self.nextNPTab ()

        finally:
            Gdk.threads_leave ()

    def opreq_id_search_error (self, opreq, status, err):
        debugprint ("OpenPrinting request failed (%d): %s" % (status,
                                                              repr (err)))
        self.opreq_id_search_done (opreq, list(), dict())


    def _installSelectedDriverFromOpenPrinting(self):
        # Install package of the driver found on OpenPrinting
        treeview = self.tvNPDownloadableDrivers
        model, iter = treeview.get_selection ().get_selected ()
        driver = None
        if iter is not None:
            driver = model.get_value (iter, 1)
        if (driver is None or driver == 0 or 'packages' not in driver):
            return

        # Find the package name, repository, and fingerprint
        # and install the package
        if not self.installdriverpackage (driver) or \
                len(self.installed_driver_files) <= 0:
            return

        # We actually installed a package, delete the
        # PPD list to get it regenerated
        self.ppds = None
        if self.dialog_mode != "download_driver":
            if (not self.device.id and
                (not self.device.make_and_model or
                 self.device.make_and_model ==
                 "Unknown") and
                self.downloadable_driver_for_printer):
                self.device.make_and_model = \
                    self.downloadable_driver_for_printer

    def setNPButtons(self):
        nr = self.ntbkNewPrinter.get_current_page()

        if self.dialog_mode == "device":
            self.btnNPBack.hide()
            self.btnNPForward.hide()
            self.btnNPApply.show()
            try:
                uri = self.getDeviceURI ()
                valid = validDeviceURI (uri)
            except AttributeError:
                # No device selected yet.
                valid = False
            self.btnNPApply.set_sensitive (valid)
            return

        if self.dialog_mode == "ppd":
            if nr == self.PAGE_APPLY:
                if not self.installable_options:
                    # There are no installable options, so this is the
                    # last page.
                    debugprint ("No installable options")
                    self.btnNPForward.hide ()
                    self.btnNPApply.show ()
                else:
                    self.btnNPForward.show ()
                    self.btnNPApply.hide ()
                return
            elif nr == self.PAGE_INSTALLABLE_OPTIONS:
                self.btnNPForward.hide()
                self.btnNPApply.show()
                return
            else:
                self.btnNPForward.show()
                self.btnNPApply.hide()
            if nr == self.PAGE_SELECT_INSTALL_METHOD:
                self.btnNPBack.hide()
                self.btnNPForward.show()
                downloadable_selected = False
                if self.rbtnNPDownloadableDriverSearch.get_active ():
                    combobox = self.cmbNPDownloadableDriverFoundPrinters
                    iter = combobox.get_active_iter ()
                    if iter and combobox.get_model ().get_value (iter, 1):
                        downloadable_selected = True

                self.btnNPForward.set_sensitive(bool(
                        (self.rbtnNPFoomatic.get_active() and
                         self.tvNPMakes.get_cursor()[0] is not None) or
                        self.filechooserPPD.get_filename() or
                        downloadable_selected))
                return
            else:
                self.btnNPBack.show()

        if self.dialog_mode == "download_driver":
            self.btnNPBack.hide()
            self.btnNPForward.hide()
            self.btnNPApply.show()

            accepted = self._is_driver_license_accepted()
            self.btnNPApply.set_sensitive(accepted)
            return

        # class/printer

        if nr == self.PAGE_SELECT_DEVICE:
            valid = False
            try:
                uri = self.getDeviceURI ()
                valid = validDeviceURI (uri)
            except:
                nonfatalException ()
            self.btnNPForward.set_sensitive(valid)
            self.btnNPBack.hide ()
        else:
            self.btnNPBack.show()

        self.btnNPForward.show()
        self.btnNPApply.hide()

        if nr == self.PAGE_DESCRIBE_PRINTER:
            self.btnNPBack.show()
            if self.dialog_mode == "printer" or \
                    self.dialog_mode == "printer_with_uri":
                self.btnNPForward.hide()
                self.btnNPApply.show()
                self.btnNPApply.set_sensitive(
                    checkNPName(self.printers, self.entNPName.get_text()))
            if self.dialog_mode == "class":
                # This is the first page for the New Class dialog, so
                # hide the Back button.
                self.btnNPBack.hide ()
            if self.dialog_mode == "printer_with_uri" and \
                    (self.remotecupsqueue or \
                         (self.exactdrivermatch and \
                              not self.installable_options)):
                self.btnNPBack.hide ()
        if nr == self.PAGE_SELECT_INSTALL_METHOD:
            downloadable_selected = False
            if self.rbtnNPDownloadableDriverSearch.get_active ():
                combobox = self.cmbNPDownloadableDriverFoundPrinters
                iter = combobox.get_active_iter ()
                if iter and combobox.get_model ().get_value (iter, 1):
                    downloadable_selected = True

            self.btnNPForward.set_sensitive(bool(
                self.rbtnNPFoomatic.get_active() or
                self.filechooserPPD.get_filename() or
                downloadable_selected))
            # If we have an auto-detected printer for which there was no
            # driver found, we have already the URI and so this step is
            # not needed in the wizard. This makes manufacturer?PPD selection
            # the first step
            if self.dialog_mode == "printer_with_uri":
                self.btnNPBack.hide()
        if nr == self.PAGE_CHOOSE_DRIVER_FROM_DB:
            model, iter = self.tvNPDrivers.get_selection().get_selected()
            self.btnNPForward.set_sensitive(bool(iter))
        if nr == self.PAGE_CHOOSE_CLASS_MEMBERS:
            self.btnNPForward.hide()
            self.btnNPApply.show()
            self.btnNPApply.set_sensitive(
                bool(getCurrentClassMembers(self.tvNCMembers)))
        if nr == self.PAGE_INSTALLABLE_OPTIONS:
            if self.dialog_mode == "printer_with_uri" and \
                    self.exactdrivermatch:
                self.btnNPBack.hide ()
        if nr == self.PAGE_DOWNLOAD_DRIVER:
            accepted = self._is_driver_license_accepted()
            self.btnNPForward.set_sensitive(accepted)

    def _is_driver_license_accepted(self):
        current_page = self.ntbkNPDownloadableDriverProperties.get_current_page()
        if current_page == self.PAGE_SELECT_DEVICE:
            return self.rbtnNPDownloadLicenseYes.get_active ()

        treeview = self.tvNPDownloadableDrivers
        model, iter = treeview.get_selection ().get_selected ()
        if not iter:
            path, column = treeview.get_cursor()
            if path:
                iter = model.get_iter (path)
        return iter is not None

    def on_entNPName_changed(self, widget):
        # restrict
        text = widget.get_text()
        new_text = text
        new_text = new_text.replace("/", "")
        new_text = new_text.replace("#", "")
        new_text = new_text.replace(" ", "")
        if text!=new_text:
            widget.set_text(new_text)
        if self.dialog_mode == "printer":
            self.btnNPApply.set_sensitive(
                checkNPName(self.printers, new_text))
        else:
            self.btnNPForward.set_sensitive(
                checkNPName(self.printers, new_text))

    def fetchDevices(self, network=False, current_uri=None):
        debugprint ("fetchDevices")
        self.inc_spinner_task ()

        # Search for Bluetooth printers together with the network printers
        # as the Bluetooth search takes rather long time
        network_schemes = ["dnssd", "snmp", "driverless", "bjnp", "bluetooth"]
        error_handler = self.error_getting_devices
        if network == False:
            reply_handler = (lambda x, y:
                                 self.local_devices_reply (x, y,
                                                           current_uri))
            cupshelpers.getDevices (self.fetchDevices_conn,
                                    exclude_schemes=network_schemes,
                                    reply_handler=reply_handler,
                                    error_handler=error_handler)
        else:
            reply_handler = (lambda x, y:
                                 self.network_devices_reply (x, y,
                                                             current_uri))
            cupshelpers.getDevices (self.fetchDevices_conn,
                                    include_schemes=network_schemes,
                                    reply_handler=reply_handler,
                                    error_handler=error_handler)

    def error_getting_devices (self, conn, exc):
        # Just ignore the error.
        debugprint ("Error fetching devices: %s" % repr (exc))
        self.dec_spinner_task ()
        self.fetchDevices_conn._end_operation ()
        self.fetchDevices_conn.destroy ()
        self.fetchDevices_conn = None

    def local_devices_reply (self, conn, result, current_uri):
        self.dec_spinner_task ()

        # Now we've got the local devices, start a request for the
        # network devices.
        self.fetchDevices (network=True, current_uri=current_uri)

        # Add the local devices to the list.
        self.add_devices (result, current_uri)

    def network_devices_reply (self, conn, result, current_uri):
        self.fetchDevices_conn._end_operation ()
        self.fetchDevices_conn.destroy ()
        self.fetchDevices_conn = None

        # Add the network devices to the list.
        no_more = True
        need_resolving = {}
        for uri, device in result.items ():
            if uri.startswith ("dnssd://"):
                need_resolving[uri] = device
                no_more = False

        for uri in need_resolving.keys ():
            del result[uri]

        self.add_devices (result, current_uri, no_more=no_more)

        if len (need_resolving) > 0:
            resolver = dnssdresolve.DNSSDHostNamesResolver (need_resolving)
            self.inc_spinner_task ()
            resolver.resolve (reply_handler=lambda devices:
                                  self.dnssd_resolve_reply (current_uri,
                                                            devices))

        self.dec_spinner_task ()
        self.check_firewall ()

    def dnssd_resolve_reply (self, current_uri, devices):
        self.add_devices (devices, current_uri, no_more=True)
        self.dec_spinner_task ()
        self.check_firewall ()

    def get_hpfax_device_id(self, faxuri):
        new_environ = os.environ.copy()
        new_environ['LC_ALL'] = "C"
        new_environ['DISPLAY'] = ""
        args = ["hp-info", "-x", "-i", "-d" + faxuri]
        debugprint (faxuri + ": " + repr(args))
        try:
            p = subprocess.Popen (args, env=new_environ, close_fds=True,
                                  stdin=subprocess.DEVNULL,
                                  stdout=subprocess.PIPE,
                                  stderr=subprocess.PIPE)
            (stdout, stderr) = p.communicate ()
        except:
            # Problem executing command.
            return None

        faxtype = -1
        for line in stdout.decode ().split ("\n"):
            if line.find ("fax-type") == -1:
                continue
            res = re.search (r"(\d+)", line)
            if res:
                resg = res.groups()
                try:
                    faxtype = int(resg[0])
                except:
                    faxtype = -1
            if faxtype >= 0:
                break
        if faxtype <= 0:
            return None
        elif faxtype == 4:
            return 'MFG:HP;MDL:Fax 2;DES:HP Fax 2;'
        else:
            return 'MFG:HP;MDL:Fax;DES:HP Fax;'

    def get_hplip_scan_type_for_uri(self, uri):
        args = ["hp-query", "-k", "scan-type", "-d", uri]
        debugprint (uri + ": " + repr(args))
        try:
            p = subprocess.Popen (args, close_fds=True,
                                  stdin=subprocess.DEVNULL,
                                  stdout=subprocess.PIPE,
                                  stderr=subprocess.PIPE)
            (stdout, stderr) = p.communicate ()
            if p.returncode != 0:
                return None
        except:
            # Problem executing command.
            return None

        scan_type = stdout.decode ().strip ()
        fields = scan_type.split ("=", 1)
        if len (fields) < 2:
            return None

        value = fields[1]
        if value == '0':
            return None

        return value

    def get_hplip_uri_for_network_printer(self, host, mode):
        if mode == "print": mod = "-c"
        elif mode == "fax": mod = "-f"
        else: mod = "-c"
        args = ["hp-makeuri", mod, host]
        debugprint (host + ": " + repr(args))
        uri = None
        try:
            p = subprocess.Popen (args, close_fds=True,
                                  stdin=subprocess.DEVNULL,
                                  stdout=subprocess.PIPE,
                                  stderr=subprocess.PIPE)
            (stdout, stderr) = p.communicate ()
            if p.returncode != 0:
                return None
        except:
            # Problem executing command.
            return None

        uri = stdout.decode ().strip ()
        return uri

    def getNetworkPrinterMakeModel(self, host=None, device=None):
        """
        Try to determine the make and model for the currently selected
        network printer, and store this in the data structure for the
        printer.
        Returns (hostname or None, uri or None).
        """
        uri = None
        if device is None:
            device = self.device
        # Determine host name/IP
        if host is None:
            s = device.uri.find ("://")
            if s != -1:
                s += 3
                e = device.uri[s:].find (":")
                if e == -1: e = device.uri[s:].find ("/")
                if e == -1: e = device.uri[s:].find ("?")
                if e == -1: e = len (device.uri)
                host = device.uri[s:s+e]
        # Try to get make and model via SNMP
        if host:
            args = ["/usr/local/libexec/cups/backend/snmp", host]
            debugprint (host + ": " + repr(args))
            stdout = None
            try:
                p = subprocess.Popen (args, close_fds=True,
                                      stdin=subprocess.DEVNULL,
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.PIPE)
                (stdout, stderr) = p.communicate ()
                if p.returncode != 0:
                    stdout = None
            except:
                # Problem executing command.
                pass

            if stdout is not None:
                try:
                    line = stdout.decode ('utf-8').strip ()
                except UnicodeDecodeError:
                    # Work-around snmp backend output encoded as iso-8859-1 (despire RFC 2571).
                    # If it's neither iso-8859-1, make a best guess by ignoring problematic bytes.
                    line = stdout.decode (encoding='iso-8859-1', errors='ignore').strip ()
                words = probe_printer.wordsep (line)
                n = len (words)
                if n < 4:
                    words.extend (['','','',''])
                    words = words[:4]
                    n = 4
                elif n > 6:
                    words = words[:6]
                    n = 6

                if n == 6:
                    (device_class, uri, make_and_model,
                     info, device_id, device_location) = words
                elif n == 5:
                    (device_class, uri, make_and_model,
                     info, device_id) = words
                elif n == 4:
                    (device_class, uri, make_and_model, info) = words

                if n == 4:
                    # No Device ID given so we'll have to make one
                    # up.
                    debugprint ("No Device ID from snmp backend")
                    (mk, md) = cupshelpers.ppds.\
                        ppdMakeModelSplit (make_and_model)
                    device.id = "MFG:%s;MDL:%s;DES:%s %s;" % (mk, md,
                                                              mk, md)
                else:
                    debugprint ("Got Device ID: %s" % device_id)
                    device.id = device_id

                device.id_dict = cupshelpers.parseDeviceID (device.id)
                device.make_and_model = make_and_model
                device.info = info
                if n == 6:
                    device.location = device_location

        return (host, uri)

    def fillDeviceTab(self, current_uri=None):
        self.device_selected = -1
        model = Gtk.TreeStore (str,                   # device-info
                               GObject.TYPE_PYOBJECT, # PhysicalDevice obj
                               bool)                  # Separator?
        other = cupshelpers.Device('', **{'device-info' :_("Enter URI")})
        physother = PhysicalDevice (other)
        self.devices = [physother]
        uri_iter = model.append (None, row=[physother.get_info (),
                                            physother, False])
        network_iter = model.append (None, row=[_("Network Printer"),
                                                None,
                                                False])
        network_dict = { 'device-class': 'network',
                         'device-info': _("Find Network Printer") }
        network = cupshelpers.Device ('network', **network_dict)
        find_nw_iter = model.append (network_iter,
                                     row=[network_dict['device-info'],
                                          PhysicalDevice (network), False])
        model.insert_after (network_iter, find_nw_iter, row=['', None, True])
        smbdev_dict = { 'device-class': 'network',
                        'device-info': _("Windows Printer via SAMBA") }
        smbdev = cupshelpers.Device ('smb', **smbdev_dict)
        find_smb_iter = model.append (network_iter,
                                     row=[smbdev_dict['device-info'],
                                          PhysicalDevice (smbdev), False])
        model.insert_after (find_nw_iter, find_smb_iter, row=['', None, True])
        self.devices_uri_iter = uri_iter
        self.devices_find_nw_iter = find_nw_iter
        self.devices_network_iter = network_iter
        self.devices_network_fetched = False
        self.tvNPDevices.set_model (model)
        self.entNPTDevice.set_text ('')
        self.expNPDeviceURIs.hide ()
        column = self.tvNPDevices.get_column (0)
        self.tvNPDevices.set_cursor (Gtk.TreePath(), column, False)

        self.current_uri = current_uri
        self.firewall = None
        debugprint ("Fetching devices")
        self.start_fetching_devices ()

    def on_firewall_read (self, data):
        f = self.firewall
        allowed = True
        try:
            ipp_allowed = f.check_ipp_client_allowed ()
            mdns_allowed = f.check_mdns_allowed ()
            allowed = (ipp_allowed and mdns_allowed)

            secondary_text = TEXT_adjust_firewall + "\n\n"
            if not ipp_allowed:
                secondary_text += ("- " +
                                   _("Allow all incoming IPP Browse packets") +
                                   "\n")
                f.add_service (firewallsettings.IPP_CLIENT_SERVICE)
            if not mdns_allowed:
                secondary_text += ("- " +
                                   _("Allow all incoming mDNS traffic") + "\n")
                f.add_service (firewallsettings.MDNS_SERVICE)

            if not allowed:
                debugprint ("Asking for permission to adjust firewall:\n%s" %
                            secondary_text)
                dialog = Gtk.MessageDialog (parent=self.NewPrinterWindow,
                                            modal=True, destroy_with_parent=True,
                                            message_type=Gtk.MessageType.QUESTION,
                                            buttons=Gtk.ButtonsType.NONE,
                                            text= _("Adjust Firewall"))
                dialog.format_secondary_markup (secondary_text)
                dialog.add_buttons (_("Do It Later"), Gtk.ResponseType.NO,
                                    _("Adjust Firewall"), Gtk.ResponseType.YES)
                dialog.connect ('response', self.adjust_firewall_response)
                dialog.show ()
        except (dbus.DBusException, Exception):
            nonfatalException ()

        if allowed:
            debugprint ("Firewall all OK, no changes needed")

    def adjust_firewall_response (self, dialog, response):
        dialog.destroy ()
        if response == Gtk.ResponseType.YES:
            ipp_server_allowed = self.firewall.check_ipp_server_allowed ()
            if not ipp_server_allowed:
                self.firewall.add_service (firewallsettings.IPP_SERVER_SERVICE)
            self.firewall.write ()

        debugprint ("Fetching network devices after firewall dialog response")
        self.fetchDevices_conn = asyncconn.Connection ()
        self.fetchDevices_conn._begin_operation (_("fetching device list"))
        self.fetchDevices (network=True)

    def start_fetching_devices (self):
        self.fetchDevices_conn = asyncconn.Connection ()
        self.fetchDevices_conn._begin_operation (_("fetching device list"))
        self.fetchDevices (network=False, current_uri=self.current_uri)
        del self.current_uri

    def add_devices (self, devices, current_uri, no_more=False):
        if current_uri:
            if current_uri in devices:
                current = devices.pop(current_uri)
            elif current_uri.replace (":9100", "") in devices:
                current_uri = current_uri.replace (":9100", "")
                current = devices.pop(current_uri)
            elif no_more:
                current = cupshelpers.Device (current_uri)
                current.info = "Current device"
            else:
                current_uri = None

        devices = list(devices.values())

        for device in devices:
            if device.type == "socket":
                # Remove default port to more easily find duplicate URIs
                device.uri = device.uri.replace (":9100", "")

        # Map generic URIs to something canonical
        def replace_generic (device):
            if device.uri == "hp:/no_device_found":
                device.uri = "hp"
            elif device.uri == "hpfax:/no_device_found":
                device.uri = "hpfax"
            return device

        devices = list(map (replace_generic, devices))

        # Mark duplicate URIs for deletion
        for i in range (len (devices) - 1):
            for j in range (i + 1, len (devices)):
                device1 = devices[i]
                device2 = devices[j]
                if device1.uri == "delete" or device2.uri == "delete":
                    continue
                if device1.uri == device2.uri:
                    # Keep the one with the longer (better) device ID
                    if (not device1.id):
                        device1.uri = "delete"
                    elif (not device2.id):
                        device2.uri = "delete"
                    elif (len (device1.id) < len (device2.id)):
                        device1.uri = "delete"
                    else:
                        device2.uri = "delete"
        devices = [x for x in devices if x.uri not in ("hp", "hpfax",
                                                       "hal", "beh", "smb", 
                                                       "scsi", "http", "bjnp",
                                                       "delete")]
        newdevices = []
        for device in devices:
            debugprint("Adding device with URI %s" % device.uri)
            if (hasattr (device, 'address')):
                debugprint("   Device address %s" % device.address)
            if (hasattr (device, 'hostname')):
                debugprint("   Device host name %s" % device.hostname)
            physicaldevice = PhysicalDevice (device)
            debugprint ("   Created physical device %s" % repr(physicaldevice))
            try:
                i = self.devices.index (physicaldevice)
                debugprint ("   Physical device %d is the same printer" % i)
                self.devices[i].add_device (device)
                debugprint ("   New physical device %s is same as physical device %d: %s" %
                            (repr(physicaldevice), i, repr(self.devices[i])))
                debugprint ("   Joining devices")
            except ValueError:
                self.devices.append (physicaldevice)
                newdevices.append (physicaldevice)
                debugprint ("   Physical device %s is a completely new device" % repr(physicaldevice))

        self.devices.sort()
        if current_uri:
            current_device = PhysicalDevice (current)
            try:
                i = self.devices.index (current_device)
                self.devices[i].add_device (current)
                current_device = self.devices[i]
            except ValueError:
                self.devices.append (current_device)
                newdevices.append (current_device)
        else:
            current_device = None

        model = self.tvNPDevices.get_model ()

        network_iter = self.devices_network_iter
        find_nw_iter = self.devices_find_nw_iter
        for newdevice in newdevices:
            device = None
            try:
                i = self.devices.index (newdevice)
                device = self.devices[i]
            except ValueError:
                debugprint("ERROR: Cannot identify new physical device with its entry in the device list (%s)" %
                           repr(newdevice))
                continue
            devs = device.get_devices ()
            network = devs[0].device_class == 'network'
            info = device.get_info ()
            if device == current_device:
                info += _(" (Current)")
            row=[info, device, False]
            if network:
                if devs[0].uri != devs[0].type:
                    # An actual network printer device.  Put this at the top.
                    iter = model.insert_before (network_iter, find_nw_iter,
                                                row=row)

                    # If this is the currently selected device we need
                    # to expand the "Network Printer" row so that it
                    # is visible.
                    if device == current_device:
                        network_path = model.get_path (network_iter)
                        self.tvNPDevices.expand_row (network_path, False)
                else:
                    # Just a method of finding one.
                    iter = model.append (network_iter, row=row)
            else:
                # Insert this local device in order.
                network_path = model.get_path (network_iter)
                iter = model.get_iter_first ()
                while model.get_path (iter) != network_path:
                    physdev = model.get_value (iter, 1)
                    if physdev > device:
                        break

                    iter = model.iter_next (iter)

                iter = model.insert_before (None, iter, row=row)

            if device == current_device:
                device_select_path = model.get_path (iter)
                self.tvNPDevices.scroll_to_cell (device_select_path,
                                                 row_align=0.5)
                column = self.tvNPDevices.get_column (0)
                self.tvNPDevices.set_cursor (device_select_path, column, False)

        connection_select_path = 0
        if current_uri:
            model = self.tvNPDeviceURIs.get_model ()
            iter = model.get_iter_first ()
            i = 0
            while iter:
                dev = model.get_value (iter, 1)
                if current_uri == dev.uri:
                    connection_select_path = i
                    break

                iter = model.iter_next (iter)
                i += 1
        elif not self.device_selected:
            # Select the device.
            column = self.tvNPDevices.get_column (0)
            self.tvNPDevices.set_cursor (Gtk.TreePath(), column, False)

            # Select the connection.
            column = self.tvNPDeviceURIs.get_column (0)
            self.tvNPDeviceURIs.set_cursor (connection_select_path, column, False)

    ## SMB browsing

    def install_python3_smbc_if_needed (self):
        global PYSMB_AVAILABLE
        global pysmb # Make the import of pysmb globally available
        # Does the SMB client library  need to be installed?
        if PYSMB_AVAILABLE:
            return True

        debugprint ("No SMB client library present so attempting install")

        try:
            pk = installpackage.PackageKit ()
            # The following call means a blocking, synchronous, D-Bus call
            pk.InstallPackageName (0, 0, "python3-smbc")
        except DBusException as e:
            debugprint ("Error during installation/setup of SMB client.")
            debugprint("{}".format(e))
            return False

        try:
            import pysmb
            PYSMB_AVAILABLE=True
        except ModuleNotFoundError as e:
            debugprint ("SMB client setup failed.")
            debugprint("{}".format(e))
            return False

        debugprint ("SMB client successfully installed and set up.")

        return True

    def browse_smb_hosts(self):
        """Initialise the SMB tree store."""
        store = self.smb_store
        store.clear ()
        busy(self.SMBBrowseDialog)
        class X:
            pass
        dummy = X()
        dummy.smbc_type = pysmb.smbc.PRINTER_SHARE
        dummy.name = _('Scanning...')
        dummy.comment = ''
        store.append(None, [dummy])
        while Gtk.events_pending ():
            Gtk.main_iteration ()

        debug = 0
        if get_debugging ():
            debug = 10
        smbc_auth = pysmb.AuthContext (self.SMBBrowseDialog)
        ctx = pysmb.smbc.Context (debug=debug,
                                  auth_fn=smbc_auth.callback)
        entries = None
        try:
            while smbc_auth.perform_authentication () > 0:
                try:
                    entries = ctx.opendir ("smb://").getdents ()
                except Exception as e:
                    smbc_auth.failed (e)
        except RuntimeError as e:
            (e, s) = e.args
            if e != errno.ENOENT:
                debugprint ("Runtime error: %s" % repr ((e, s)))
        except:
            nonfatalException ()

        store.clear ()
        if entries:
            for entry in entries:
                if entry.smbc_type in [pysmb.smbc.WORKGROUP,
                                       pysmb.smbc.SERVER]:
                    iter = store.append (None, [entry])
                    i = store.append (iter)

        specified_uri = SMBURI (uri=self.entSMBURI.get_text ())
        (group, host, share, user, password) = specified_uri.separate ()
        if len (host) > 0:
            # The user has specified a server before clicking Browse.
            # Append the server as a top-level entry.
            class FakeEntry:
                pass
            toplevel = FakeEntry ()
            toplevel.smbc_type = pysmb.smbc.SERVER
            toplevel.name = host
            toplevel.comment = ''
            iter = store.append (None, [toplevel])
            i = store.append (iter)

            # Now expand it.
            path = store.get_path (iter)
            self.tvSMBBrowser.expand_row (path, 0)

        ready(self.SMBBrowseDialog)

        if store.get_iter_first () is None:
            self.SMBBrowseDialog.hide ()
            show_info_dialog (_("No Print Shares"),
                              _("There were no print shares found.  "
                                "Please check that the Samba service is "
                                "marked as trusted in your firewall "
                                "configuration."),
                              parent=self.NewPrinterWindow)

    def smb_select_function (self, selection, model, path, path_selected, data):
        """Don't allow this path to be selected unless it is a leaf."""
        iter = self.smb_store.get_iter (path)
        return not self.smb_store.iter_has_child (iter)

    def smbbrowser_cell_share (self, column, cell, model, iter, data):
        entry = model.get_value (iter, 0)
        share = ''
        if entry is not None:
            share = entry.name
        cell.set_property ('text', share)

    def smbbrowser_cell_comment (self, column, cell, model, iter, data):
        entry = model.get_value (iter, 0)
        comment = ''
        if entry is not None:
            comment = entry.comment
        cell.set_property ('text', comment)

    def on_tvSMBBrowser_row_activated (self, view, path, column):
        """Handle double-clicks in the SMB tree view."""
        store = self.smb_store
        iter = store.get_iter (path)
        entry = store.get_value (iter, 0)
        if entry and entry.smbc_type == pysmb.smbc.PRINTER_SHARE:
            # This is a share, not a host.
            self.btnSMBBrowseOk.clicked ()
            return

        if view.row_expanded (path):
            view.collapse_row (path)
        else:
            self.on_tvSMBBrowser_row_expanded (view, iter, path)

    def on_tvSMBBrowser_row_expanded (self, view, iter, path):
        """Handler for expanding a row in the SMB tree view."""
        model = view.get_model ()
        entry = model.get_value (iter, 0)
        if entry is None:
            return

        if entry.smbc_type == pysmb.smbc.WORKGROUP:
            # Workgroup
            # Be careful though: if there is a server with the
            # same name as the workgroup we will get a list of its
            # shares, not the workgroup's servers.
            try:
                if self.expanding_row:
                    return
            except:
                self.expanding_row = 1

            busy (self.SMBBrowseDialog)
            uri = "smb://%s/" % entry.name
            debug = 0
            if get_debugging ():
                debug = 10
            smbc_auth = pysmb.AuthContext (self.SMBBrowseDialog)
            ctx = pysmb.smbc.Context (debug=debug,
                                      auth_fn=smbc_auth.callback)
            entries = []
            try:
                while smbc_auth.perform_authentication () > 0:
                    try:
                        entries = ctx.opendir (uri).getdents ()
                    except Exception as e:
                        smbc_auth.failed (e)
            except RuntimeError as e:
                (e, s) = e.args
                if e != errno.ENOENT:
                    debugprint ("Runtime error: %s" % repr ((e, s)))
            except:
                nonfatalException()

            while model.iter_has_child (iter):
                i = model.iter_nth_child (iter, 0)
                model.remove (i)

            for entry in entries:
                if entry.smbc_type in [pysmb.smbc.SERVER,
                                       pysmb.smbc.PRINTER_SHARE]:
                    i = model.append (iter, [entry])
                if entry.smbc_type == pysmb.smbc.SERVER:
                    n = model.append (i)

            view.expand_row (path, 0)
            del self.expanding_row
            ready (self.SMBBrowseDialog)

        elif entry.smbc_type == pysmb.smbc.SERVER:
            # Server
            try:
                if self.expanding_row:
                    return
            except:
                self.expanding_row = 1

            busy (self.SMBBrowseDialog)
            uri = "smb://%s/" % entry.name
            debug = 0
            if get_debugging ():
                debug = 10
            smbc_auth = pysmb.AuthContext (self.SMBBrowseDialog)
            ctx = pysmb.smbc.Context (debug=debug,
                                      auth_fn=smbc_auth.callback)
            shares = []
            try:
                while smbc_auth.perform_authentication () > 0:
                    try:
                        shares = ctx.opendir (uri).getdents ()
                    except Exception as e:
                        smbc_auth.failed (e)
            except RuntimeError as e:
                (e, s) = e.args
                if e != errno.EACCES and e != errno.EPERM:
                    debugprint ("Runtime error: %s" % repr ((e, s)))
            except:
                nonfatalException()

            while model.iter_has_child (iter):
                i = model.iter_nth_child (iter, 0)
                model.remove (i)

            for share in shares:
                if share.smbc_type == pysmb.smbc.PRINTER_SHARE:
                    i = model.append (iter, [share])
                    debugprint (repr (share))

            view.expand_row (path, 0)
            del self.expanding_row
            ready (self.SMBBrowseDialog)

    def set_btnSMBVerify_sensitivity (self, on):
        self.btnSMBVerify.set_sensitive (on)
        if not PYSMB_AVAILABLE or not on:
            self.btnSMBVerify.set_tooltip_text (_("Verification requires the "
                                                  "%s module") % "pysmbc")

    def on_entSMBURI_changed (self, ent):
        allowed_chars = string.ascii_letters+string.digits+'_-./:%[]@'
        self.entry_changed(ent, allowed_chars)
        uri = ent.get_text ()
        (group, host, share, user, password) = SMBURI (uri=uri).separate ()
        if user:
            self.entSMBUsername.set_text (user)
        if password:
            self.entSMBPassword.set_text (password)
        if user or password:
            uri = SMBURI (group=group, host=host, share=share).get_uri ()
            ent.set_text(uri)
            self.rbtnSMBAuthSet.set_active(True)
        elif self.entSMBUsername.get_text () == '':
            self.rbtnSMBAuthPrompt.set_active(True)

        self.set_btnSMBVerify_sensitivity (bool(uri))
        self.setNPButtons ()

    def on_tvSMBBrowser_cursor_changed(self, widget):
        selection = self.tvSMBBrowser.get_selection()
        if selection is None:
            return

        store, iter = selection.get_selected()
        is_share = False
        if iter:
            entry = store.get_value (iter, 0)
            if entry:
                is_share = entry.smbc_type == pysmb.smbc.PRINTER_SHARE

        self.btnSMBBrowseOk.set_sensitive(iter is not None and is_share)

    def on_btnSMBBrowse_clicked(self, button):
        """Check whether the needed SMB client library is available and"""
        """install it if needed"""
        if not self.install_python3_smbc_if_needed():
            return

        self.btnSMBBrowseOk.set_sensitive(False)

        try:
            # Note: we do the browsing from *this* machine, regardless
            # of which CUPS server we are connected to.
            f = firewallsettings.FirewallD ()
            if not f.running:
                f = firewallsettings.SystemConfigFirewall ()
            allowed = f.check_samba_client_allowed ()
            secondary_text = TEXT_adjust_firewall + "\n\n"
            if not allowed:
                dialog = Gtk.MessageDialog (parent=self.NewPrinterWindow,
                                            modal=True, destroy_with_parent=True,
                                            message_type=Gtk.MessageType.QUESTION,
                                            buttons=Gtk.ButtonsType.NONE,
                                            text=_("Adjust Firewall"))
                secondary_text += ("- " +
                                   _("Allow all incoming SMB/CIFS "
                                     "browse packets"))
                dialog.format_secondary_markup (secondary_text)
                dialog.add_buttons (_("Do It Later"), Gtk.ResponseType.NO,
                                    _("Adjust Firewall"), Gtk.ResponseType.YES)
                response = dialog.run ()
                dialog.destroy ()

                if response == Gtk.ResponseType.YES:
                    f.add_service (firewallsettings.SAMBA_CLIENT_SERVICE)
                    f.write ()
        except (dbus.DBusException, Exception):
            nonfatalException ()

        self.SMBBrowseDialog.show()
        self.browse_smb_hosts()

    def on_btnSMBBrowseOk_clicked(self, button):
        store, iter = self.tvSMBBrowser.get_selection().get_selected()
        is_share = False
        if iter:
            entry = store.get_value (iter, 0)
            if entry:
                is_share = entry.smbc_type == pysmb.smbc.PRINTER_SHARE

        if not iter or not is_share:
            self.SMBBrowseDialog.hide()
            return

        parent_iter = store.iter_parent (iter)
        domain_iter = store.iter_parent (parent_iter)
        share = store.get_value (iter, 0)
        host = store.get_value (parent_iter, 0)
        if domain_iter:
            group = store.get_value (domain_iter, 0).name
        else:
            group = ''
        uri = SMBURI (group=group,
                      host=host.name,
                      share=share.name).get_uri ()

        self.entSMBUsername.set_text ('')
        self.entSMBPassword.set_text ('')
        self.entSMBURI.set_text (uri)

        self.SMBBrowseDialog.hide()

    def on_btnSMBBrowseCancel_clicked(self, widget, *args):
        self.SMBBrowseDialog.hide()

    def on_btnSMBBrowseRefresh_clicked(self, button):
        self.browse_smb_hosts()

    def on_rbtnSMBAuthSet_toggled(self, widget):
        self.tblSMBAuth.set_sensitive(widget.get_active())

    def on_btnSMBVerify_clicked(self, button):
        """Check whether the needed SMB client library is available and"""
        """install it if needed"""
        if not self.install_python3_smbc_if_needed():
            return

        uri = self.entSMBURI.get_text ()
        (group, host, share, u, p) = SMBURI (uri=uri).separate ()
        user = ''
        passwd = ''
        reason = None
        auth_set = self.rbtnSMBAuthSet.get_active()
        if auth_set:
            user = self.entSMBUsername.get_text ()
            passwd = self.entSMBPassword.get_text ()

        accessible = False
        canceled = False
        busy (self.NewPrinterWindow)
        try:
            debug = 0
            if get_debugging ():
                debug = 10

            if auth_set:
                # No prompting.
                def do_auth (svr, shr, wg, un, pw):
                    return (group, user, passwd)
                ctx = pysmb.smbc.Context (debug=debug, auth_fn=do_auth)
                try:
                    ctx.optionUseKerberos = True
                except AttributeError:
                    # requires pysmbc >= 1.0.12
                    pass

                f = ctx.open ("smb://%s/%s" % (host, share),
                              os.O_RDWR, 0o777)
                accessible = True
            else:
                # May need to prompt.
                smbc_auth = pysmb.AuthContext (self.NewPrinterWindow,
                                               workgroup=group,
                                               user=user,
                                               passwd=passwd)
                ctx = pysmb.smbc.Context (debug=debug,
                                          auth_fn=smbc_auth.callback)
                while smbc_auth.perform_authentication () > 0:
                    try:
                        f = ctx.open ("smb://%s/%s" % (host, share),
                                      os.O_RDWR, 0o777)
                        accessible = True
                    except Exception as e:
                        smbc_auth.failed (e)

                if not accessible:
                    canceled = True
        except RuntimeError as e:
            (e, s) = e.args
            debugprint ("Error accessing share: %s" % repr ((e, s)))
            reason = s
        except:
            nonfatalException()
        ready (self.NewPrinterWindow)

        if accessible:
            show_info_dialog (_("Print Share Verified"),
                              _("This print share is accessible."),
                              parent=self.NewPrinterWindow)
            return

        if not canceled:
            text = _("This print share is not accessible.")
            if reason:
                text = reason
            show_error_dialog (_("Print Share Inaccessible"), text,
                               parent=self.NewPrinterWindow)

    def entry_changed(self, entry, allowed_chars):
        "Remove all chars from entry's text that are not in allowed_chars."
        origtext = entry.get_text()
        new_text = origtext
        for char in origtext:
            if char not in allowed_chars:
                new_text = new_text.replace(char, "")
                debugprint ("removed disallowed character %s" % repr (char))
        if origtext!=new_text:
            entry.set_text(new_text)

    def on_entNPTDevice_changed(self, ent):
        allowed_chars = string.ascii_letters+string.digits+'_-./:%[]()@?=&+'
        self.entry_changed(ent, allowed_chars)
        self.setNPButtons()

    def on_entNPTJetDirectHostname_changed(self, ent):
        allowed_chars = string.ascii_letters+string.digits+'_-.:%[]'
        self.entry_changed(ent, allowed_chars)
        self.setNPButtons()

    def on_entNPTJetDirectPort_changed(self, ent):
        self.entry_changed(ent, string.digits)
        self.setNPButtons()

    def on_expNPDeviceURIs_expanded (self, widget, UNUSED):
        # When the expanded is not expanded we want its packing to be
        # 'expand = false' so that it aligns at the bottom (it packs
        # to the end of its vbox).  But when it is expanded we'd like
        # it to expand with the window.
        #
        # Adjust its 'expand' packing state depending on whether the
        # widget is expanded.

        parent = widget.get_parent ()
        (expand, fill,
         padding, pack_type) = parent.query_child_packing (widget)
        expand = widget.get_expanded ()
        parent.set_child_packing (widget, expand, fill,
                                  padding, pack_type)

    def device_row_separator_fn (self, model, iter, data):
        return model.get_value (iter, 2)

    def device_row_activated (self, view, path, column):
        if view.row_expanded (path):
            view.collapse_row (path)
        else:
            view.expand_row (path, False)

    def check_firewall (self):
        view = self.tvNPDevices
        model = view.get_model ()
        if not model:
            return

        network_path = model.get_path (self.devices_network_iter)
        if not view.row_expanded (network_path):
            # 'Network' not expanded
            return

        if self.firewall is not None:
            # Already checked
            return

        if self.spinner_count > 0:
            # Still discovering devices?
            debugprint ("Skipping firewall adjustment: "
                        "discovery in progress")
            return

        # Any network printers found?
        for physdev in self.devices:
            for device in physdev.get_devices ():
                if (device.device_class == 'network' and
                    device.uri != device.type):
                    debugprint ("Skipping firewall adjustment: "
                                "network printers found")
                    return

        # If not, ask about the firewall
        try:
            if (self._host == 'localhost' or
                self._host[0] == '/'):
                self.firewall = firewallsettings.FirewallD ()
                if not self.firewall.running:
                    self.firewall = firewallsettings.SystemConfigFirewall ()

                debugprint ("Examining firewall")
                self.firewall.read (reply_handler=self.on_firewall_read)
        except (dbus.DBusException, Exception):
            nonfatalException ()

    def device_row_expanded (self, view, iter, path):
        model = view.get_model ()
        if not model or not iter:
            return

        network_path = model.get_path (self.devices_network_iter)
        if path == network_path:
            self.check_firewall ()

    def device_select_function (self, selection, model, path, *UNUSED):
        """
        Allow this path to be selected as long as there
        is a device associated with it.  Otherwise, expand or collapse it.
        """
        model = self.tvNPDevices.get_model ()
        iter = model.get_iter (path)
        if model.get_value (iter, 1) is not None:
            return True

        self.device_row_activated (self.tvNPDevices, path, None)
        return False

    def on_tvNPDevices_cursor_changed(self, widget):
        # Reset previous driver search result
        self.installed_driver_files = []
        self.searchedfordriverpackages = False
        self.founddownloadabledrivers = False
        self.founddownloadableppd = False
        self.downloadable_printers = []

        self.device_selected += 1
        path, column = widget.get_cursor ()
        if path is None:
            return

        model = widget.get_model ()
        iter = model.get_iter (path)
        physicaldevice = model.get_value (iter, 1)
        if physicaldevice is None:
            return
        show_uris = True
        for device in physicaldevice.get_devices ():
            if device.type == "parallel":
                device.menuentry = _("Parallel Port")
            elif device.type == "serial":
                device.menuentry = _("Serial Port")
            elif device.type == "usb":
                if (hasattr(device, "uri") and
                    device.uri.lower().find("fax") > -1):
                    device.menuentry = _("Fax") + " - " + _("USB")
                else:
                    device.menuentry = _("USB")
            elif device.type == "bluetooth":
                device.menuentry = _("Bluetooth")
            elif device.type == "hp":
                device.menuentry = _("HP Linux Imaging and Printing (HPLIP)")
            elif device.type == "hpfax":
                device.menuentry = _("Fax") + " - " + \
                    _("HP Linux Imaging and Printing (HPLIP)")
            elif device.type == "hal":
                device.menuentry = _("Hardware Abstraction Layer (HAL)")
            elif device.type == "socket":
                device.menuentry = _("AppSocket/HP JetDirect")
            elif device.type == "lpd":
                (scheme, rest) = urllib.parse.splittype (device.uri)
                (hostport, rest) = urllib.parse.splithost (rest)
                (queue, rest) = urllib.parse.splitquery (rest)
                if queue != '':
                    if queue[0] == '/':
                        queue = queue[1:]

                    device.menuentry = (_("LPD/LPR queue '%s'")
                                        % queue)
                else:
                    device.menuentry = _("LPD/LPR queue")

            elif device.type == "smb":
                device.menuentry = _("Windows Printer via SAMBA")
            elif device.type == "ipp":
                (scheme, rest) = urllib.parse.splittype (device.uri)
                (hostport, rest) = urllib.parse.splithost (rest)
                (queue, rest) = urllib.parse.splitquery (rest)
                if queue != '':
                    if queue[0] == '/':
                        queue = queue[1:]
                    if queue.startswith("printers/"):
                        queue = queue[9:]
                if 'driverless' in device.info:
                    drvless = "Driverless "
                    device.driverless = True
                else:
                    drvless = ""
                if queue != '':
                    device.menuentry = (("%s" + _("IPP") + " (%s)") %
                                        (drvless, queue))
                else:
                    device.menuentry = (("%s" + _("IPP")) % drvless)
            elif device.type == "http" or device.type == "https":
                device.menuentry = _("HTTP")
            elif device.type == "dnssd" or device.type == "mdns":
                (scheme, rest) = urllib.parse.splittype (device.uri)
                (name, rest) = urllib.parse.splithost (rest)
                (cupsqueue, rest) = urllib.parse.splitquery (rest)
                if cupsqueue != '' and cupsqueue[0] == '/':
                    cupsqueue = cupsqueue[1:]
                if cupsqueue == 'cups':
                    device.menuentry = _("Remote CUPS printer via DNS-SD")
                    if device.info != '':
                        device.menuentry += " (%s)" % device.info
                else:
                    protocol = None
                    if name.find("._ipp") != -1:
                        protocol = "IPP"
                    elif name.find("._printer") != -1:
                        protocol = "LPD"
                    elif name.find("._pdl-datastream") != -1:
                        protocol = "AppSocket/JetDirect"
                    if protocol is not None:
                        device.menuentry = (_("%s network printer via DNS-SD")
                                            % protocol)
                    else:
                        device.menuentry = \
                            _("Network printer via DNS-SD")
            else:
                show_uris = False
                device.menuentry = device.uri

        model = Gtk.ListStore (str,                    # URI description
                               GObject.TYPE_PYOBJECT)  # cupshelpers.Device
        self.tvNPDeviceURIs.set_model (model)

        # If this is a network device, check whether HPLIP can drive it.
        if getattr (physicaldevice, 'checked_hplip', None) != True:
            hp_drivable = False
            hp_scannable = False
            is_network = False
            remotecups = False
            host = None
            device_dict = { 'device-class': 'network' }
            if physicaldevice._network_host:
                host = physicaldevice._network_host
            for device in physicaldevice.get_devices ():
                if device.type == "hp":
                    # We already know that HPLIP can drive this device.
                    hp_drivable = True

                    # But can we scan using it?
                    if self.get_hplip_scan_type_for_uri (device.uri):
                        hp_scannable = True

                    break
                elif device.type in ["socket", "lpd", "ipp", "dnssd", "mdns"]:
                    # This is a network printer.
                    if host is None and device.type in ["socket", "lpd", "ipp"]:
                        (scheme, rest) = urllib.parse.splittype (device.uri)
                        (hostport, rest) = urllib.parse.splithost (rest)
                        if hostport is not None:
                            (host, port) = urllib.parse.splitport (hostport)
                    if host:
                        is_network = True
                        remotecups = ((device.uri.startswith('dnssd:') or \
                                       device.uri.startswith('mdns:')) and \
                                      device.uri.endswith('/cups'))
                        if (not device.make_and_model or \
                            device.make_and_model == "Unknown") and not \
                           remotecups:
                            self.getNetworkPrinterMakeModel(host=host,
                                                            device=device)
                        device_dict['device-info'] = device.info
                        device_dict['device-make-and-model'] = (device.
                                                                make_and_model)
                        device_dict['device-id'] = device.id
                        device_dict['device-location'] = device.location

            if not hp_drivable and is_network and not remotecups and \
               (not device.make_and_model or \
                device.make_and_model == "Unknown" or \
                device.make_and_model.lower ().startswith ("hp") or \
                device.make_and_model.lower ().startswith ("hewlett")):
                if (hasattr (physicaldevice, "dnssd_hostname") and \
                    physicaldevice.dnssd_hostname):
                    hpliphost = physicaldevice.dnssd_hostname
                else:
                    hpliphost = host
                hplipuri = self.get_hplip_uri_for_network_printer (hpliphost,
                                                                   "print")
                if hplipuri:
                    dev = cupshelpers.Device (hplipuri, **device_dict)
                    dev.menuentry = "HP Linux Imaging and Printing (HPLIP)"
                    physicaldevice.add_device (dev)

                    # Can we scan using this device?
                    if self.get_hplip_scan_type_for_uri (device.uri):
                        hp_scannable = True

                    # Now check to see if we can also send faxes using
                    # this device.
                    faxuri = self.get_hplip_uri_for_network_printer (hpliphost,
                                                                     "fax")
                    if faxuri:
                        faxdevid = self.get_hpfax_device_id (faxuri)
                        device_dict['device-id'] = faxdevid
                        device_dict['device-info'] = _("Fax")
                        faxdev = cupshelpers.Device (faxuri, **device_dict)
                        faxdev.menuentry = _("Fax") + " - " + \
                            "HP Linux Imaging and Printing (HPLIP)"
                        physicaldevice.add_device (faxdev)

            if hp_scannable:
                physicaldevice.hp_scannable = True
            physicaldevice.checked_hplip = True

        device.hp_scannable = getattr (physicaldevice, 'hp_scannable', None)

        # Fill the list of connections for this device.
        n = 0
        for device in physicaldevice.get_devices ():
            model.append ((device.menuentry, device))
            n += 1
        column = self.tvNPDeviceURIs.get_column (0)
        self.tvNPDeviceURIs.set_cursor (Gtk.TreePath(), column, False)
        if show_uris:
            self.expNPDeviceURIs.show_all ()
        else:
            self.expNPDeviceURIs.hide ()

    def on_tvNPDeviceURIs_cursor_changed(self, widget):
        path, column = widget.get_cursor ()
        if path is None:
            return

        model = widget.get_model ()
        iter = model.get_iter (path)
        device = model.get_value(iter, 1)
        self.device = device
        self.lblNPDeviceDescription.set_text ('')
        page = self.new_printer_device_tabs.get (device.type, self.PAGE_SELECT_DEVICE)
        self.ntbkNPType.set_current_page(page)

        debugprint("Selected connection type. URI: %s" % device.uri)
        location = ''
        type = device.type
        url = device.uri.split(":", 1)[-1]
        if page == self.PAGE_DESCRIBE_PRINTER:
            # This is the "no options" page, with just a label to describe
            # the selected device.
            if device.type == "parallel":
                text = _("A printer connected to the parallel port.")
            elif device.type == "usb":
                if (hasattr(device, "uri") and
                    device.uri.lower().find("fax") > -1):
                    device.menuentry = _("Fax") + " - " + _("USB")
                    text = _("A fax machine or the fax function "
                             "of a multi-function device connected "
                             "to a USB port.")
                else:
                    text = _("A printer connected to a USB port.")
            elif device.type == "bluetooth":
                text = _("A printer connected via Bluetooth.")
            elif device.type == "hp":
                text = _("HPLIP software driving a printer, "
                         "or the printer function of a multi-function device.")
            elif device.type == "hpfax":
                text = _("HPLIP software driving a fax machine, "
                         "or the fax function of a multi-function device.")
            elif device.type == "hal":
                text = _("Local printer detected by the "
                         "Hardware Abstraction Layer (HAL).")
            elif device.type == "dnssd" or device.type == "mdns":
                (scheme, rest) = urllib.parse.splittype (device.uri)
                (name, rest) = urllib.parse.splithost (rest)
                (cupsqueue, rest) = urllib.parse.splitquery (rest)
                if cupsqueue != '' and cupsqueue[0] == '/':
                    cupsqueue = cupsqueue[1:]
                if cupsqueue == 'cups':
                    text = _("Remote CUPS printer via DNS-SD")
                else:
                    protocol = None
                    if name.find("._ipp") != -1:
                        protocol = "IPP"
                    elif name.find("._printer") != -1:
                        protocol = "LPD"
                    elif name.find("._pdl-datastream") != -1:
                        protocol = "AppSocket/JetDirect"
                    if protocol is not None:
                        text = _("%s network printer via DNS-SD") % protocol
                    else:
                        text = _("Network printer via DNS-SD")
            else:
                text = device.uri

            self.lblNPDeviceDescription.set_text (text)
        elif device.type=="socket":
            (scheme, rest) = urllib.parse.splittype (device.uri)
            host = ''
            port = 9100
            if scheme == "socket":
                (hostport, rest) = urllib.parse.splithost (rest)
                (host, port) = urllib.parse.splitnport (hostport, defport=port)
                debugprint ("socket: host is %s, port is %s" % (host,
                                                                repr (port)))
                if device.location != '':
                    location = device.location
                else:
                    location = host
            self.entNPTJetDirectHostname.set_text (host)
            self.entNPTJetDirectPort.set_text (str (port))
        elif device.type=="serial":
            if not device.is_class:
                parts = device.uri.split("?", 1)
                if len (parts) > 1:
                    options = parts[1]
                else:
                    options = ""

                options = options.split("+")
                option_dict = {}
                for option in options:
                    name, value = option.split("=")
                    option_dict[name] = value

                for widget, name in (
                    (self.cmbNPTSerialBaud, "baud"),
                    (self.cmbNPTSerialBits, "bits"),
                    (self.cmbNPTSerialParity, "parity"),
                    (self.cmbNPTSerialFlow, "flow")):
                    if name in option_dict: # option given in URI?
                        model = widget.get_model()
                        iter = model.get_iter_first()
                        nr = 0
                        while iter:
                            value = model.get(iter,1)[0]
                            if str (value) == str (option_dict[name]):
                                break
                            iter = model.iter_next(iter)
                            nr += 1

                        if iter:
                            widget.set_active(nr)
                        else:
                            widget.set_active (0)
                    else:
                        widget.set_active(0)

        # XXX FILL TABS FOR VALID DEVICE URIs
        elif device.type=="lpd":
            self.entNPTLpdHost.set_text ('')
            self.entNPTLpdQueue.set_text ('')
            self.entNPTLpdQueue.set_completion (None)
            self.btnNPTLpdProbe.set_sensitive (False)
            if len (device.uri) > 6:
                host = device.uri[6:]
                i = host.find ("/")
                if i != -1:
                    printer = host[i + 1:]
                    host = host[:i]
                else:
                    printer = ""
                self.entNPTLpdHost.set_text (host)
                self.entNPTLpdQueue.set_text (printer)
                location = host
                self.btnNPTLpdProbe.set_sensitive (True)
        elif device.uri == "smb":
            self.entSMBURI.set_text('')
            self.btnSMBVerify.set_sensitive(False)
        elif device.type == "smb":
            self.entSMBUsername.set_text ('')
            self.entSMBPassword.set_text ('')
            self.entSMBURI.set_text(device.uri[6:])
            self.set_btnSMBVerify_sensitivity (True)
        else:
            if device.uri:
                self.entNPTDevice.set_text(device.uri)

        try:
            if len (location) == 0 and self.device.device_class == "direct":
                # Set location to the name of this host.
                if (self._host == 'localhost' or
                    self._host[0] == '/'):
                    u = os.uname ()
                    location = u[1]
                else:
                    location = self._host

            # Pre-fill location field.
            self.entNPLocation.set_text (location)
        except:
            nonfatalException ()

        self.setNPButtons()

    def on_entNPTLpdHost_changed(self, ent):
        hostname = ent.get_text()
        self.btnNPTLpdProbe.set_sensitive (len (hostname) > 0)
        self.setNPButtons()

    def on_entNPTLpdQueue_changed(self, ent):
        self.setNPButtons()

    def on_btnNPTLpdProbe_clicked(self, button):
        # read hostname, probe, fill printer names
        hostname = self.entNPTLpdHost.get_text()
        server = probe_printer.LpdServer(hostname)

        self.lblWait.set_markup ('<span weight="bold" size="larger">' +
                                 _('Searching') + '</span>\n\n' +
                                 _('Searching for printers'))
        self.WaitWindow.set_transient_for (self.NewPrinterWindow)
        self.WaitWindow.show_now ()
        busy (self.WaitWindow)
        def stop (widget, event):
            server.destroy ()
            return True

        self.WaitWindow.disconnect (self.WaitWindow_handler)
        signal = self.WaitWindow.connect ("delete-event", stop)
        printers = server.probe()
        self.WaitWindow.disconnect (signal)
        self.WaitWindow_handler = self.WaitWindow.connect ("delete-event",
                                                           on_delete_just_hide)
        self.WaitWindow.hide ()

        model = Gtk.ListStore (str)
        for printer in printers:
            model.append ([printer])

        completion = Gtk.EntryCompletion ()
        completion.set_model (model)
        completion.set_text_column (0)
        completion.set_minimum_key_length (0)
        self.entNPTLpdQueue.set_completion (completion)

    ### Find Network Printer
    def on_entNPTNetworkHostname_changed(self, ent):
        text = ent.get_text ()
        if text.find (":") != -1:
            # The user is typing in a URI.  In that case, switch to URI entry.
            ent.set_text ('')
            debugprint ("URI detected (%s) -> Enter URI" % text)
            self.entNPTDevice.set_text (text)
            model = self.tvNPDevices.get_model ()
            path = model.get_path (self.devices_uri_iter)
            self.tvNPDevices.set_cursor (path=path,
                                         start_editing=False)
            self.entNPTDevice.select_region (0, 0)
            self.entNPTDevice.set_position (-1)
            return

        allowed_chars = string.ascii_letters+string.digits+'_-.:%[]'
        self.entry_changed(ent, allowed_chars)
        s = ent.get_text ()
        self.btnNetworkFind.set_sensitive (len (s) > 0)
        self.lblNetworkFindNotFound.hide ()
        self.setNPButtons ()

    def on_btnNetworkFind_clicked(self, button):
        host = self.entNPTNetworkHostname.get_text ()

        def found_callback (new_device):
            if self.printer_finder is None:
                return

            GLib.idle_add (self.found_network_printer_callback, new_device)

        self.btnNetworkFind.set_sensitive (False)
        self.entNPTNetworkHostname.set_sensitive (False)
        self.network_found = 0
        self.lblNetworkFindNotFound.hide ()
        self.lblNetworkFindSearching.show_all ()
        finder = probe_printer.PrinterFinder ()
        self.inc_spinner_task ()
        finder.find (host, found_callback)
        self.printer_finder = finder

    def found_network_printer_callback (self, new_device):
        Gdk.threads_enter ()
        if new_device:
            self.network_found += 1
            dev = PhysicalDevice (new_device)
            try:
                i = self.devices.index (dev)

                # Adding a new URI to an existing physical device.
                self.devices[i].add_device (new_device)

                (path, column) = self.tvNPDevices.get_cursor ()
                if path:
                    model = self.tvNPDevices.get_model ()
                    iter = model.get_iter (path)
                    if model.get_value (iter, 1) == self.devices[i]:
                        self.on_tvNPDevices_cursor_changed (self.tvNPDevices)
            except ValueError:
                # New physical device.
                dev.checked_hplip = True
                self.devices.append (dev)
                self.devices.sort ()
                model = self.tvNPDevices.get_model ()
                iter = model.insert_before (None, self.devices_find_nw_iter,
                                            row=[dev.get_info (), dev, False])

                # If this is the first one we've found, select it.
                if self.network_found == 1:
                    path = model.get_path (iter)
                    self.tvNPDevices.set_cursor (path, None, False)
        else:
            self.printer_finder = None
            self.dec_spinner_task ()
            self.lblNetworkFindSearching.hide ()
            self.entNPTNetworkHostname.set_sensitive (True)
            self.btnNetworkFind.set_sensitive (True)
            if self.network_found == 0:
                self.lblNetworkFindNotFound.set_markup ('<i>' +
                                                        _("No printer was "
                                                          "found at that "
                                                          "address.") + '</i>')
                self.lblNetworkFindNotFound.show ()

        Gdk.threads_leave ()
    ###

    def getDeviceURI(self):
        if self.dialog_mode in ['printer_with_uri', 'ppd']:
            return self.device.uri

        type = self.device.type
        page = self.new_printer_device_tabs.get (type, self.PAGE_SELECT_DEVICE)
        device = type
        if page == self.PAGE_DESCRIBE_PRINTER:
            # The "no options page".  We already have the URI.
            device = self.device.uri
        elif type == "socket": # JetDirect
            host = self.entNPTJetDirectHostname.get_text()
            port = self.entNPTJetDirectPort.get_text()
            if host:
                device += "://" + host
                if port:
                    device += ":" + port
        elif type == "lpd": # LPD
            host = self.entNPTLpdHost.get_text()
            printer = self.entNPTLpdQueue.get_text()
            if host:
                device += "://" + host
                if printer:
                    device += "/" + printer
        elif type == "serial": # Serial
            options = []
            for widget, name in (
                (self.cmbNPTSerialBaud, "baud"),
                (self.cmbNPTSerialBits, "bits"),
                (self.cmbNPTSerialParity, "parity"),
                (self.cmbNPTSerialFlow, "flow")):
                model = widget.get_model ()
                iter = widget.get_active_iter()
                option = model.get_value (iter, 1)
                if option != "":
                    options.append(name + "=" + option)
            options = "+".join(options)
            device =  self.device.uri.split("?")[0] #"serial:/dev/ttyS%s"
            if options:
                device = device + "?" + options
        elif type == "smb":
            uri = self.entSMBURI.get_text ()
            (group, host, share, u, p) = SMBURI (uri=uri).separate ()
            user = ''
            password = ''
            if self.rbtnSMBAuthSet.get_active ():
                user = self.entSMBUsername.get_text ()
                password = self.entSMBPassword.get_text ()
            uri = SMBURI (group=group, host=host, share=share,
                          user=user, password=password).get_uri ()
            if uri:
                device += "://" + uri
        else:
            device = self.entNPTDevice.get_text()
        return device

    # PPD

    def on_rbtnNPFoomatic_toggled(self, widget):
        rbtn1 = self.rbtnNPFoomatic.get_active()
        rbtn2 = self.rbtnNPPPD.get_active()
        rbtn3 = self.rbtnNPDownloadableDriverSearch.get_active()
        self.tvNPMakes.set_sensitive(rbtn1)
        self.filechooserPPD.set_sensitive(rbtn2)

        if rbtn1:
            page = self.PAGE_DESCRIBE_PRINTER
        if rbtn2:
            page = self.PAGE_SELECT_DEVICE
        if rbtn3:
            page = self.PAGE_SELECT_INSTALL_METHOD
        self.ntbkPPDSource.set_current_page (page)

        if not rbtn3 and self.opreq:
            # Need to cancel a search in progress.
            for handler in self.opreq_handlers:
                self.opreq.disconnect (handler)

            self.opreq_handlers = None
            self.opreq.cancel ()
            self.opreq = None

            self.btnNPDownloadableDriverSearch.set_sensitive (True)
            self.btnNPDownloadableDriverSearch_label.set_text (_("Search"))
            # Clear printer list.
            model = Gtk.ListStore (str, str)
            combobox = self.cmbNPDownloadableDriverFoundPrinters
            combobox.set_model (model)
            combobox.set_sensitive (False)

        for widget in [self.entNPDownloadableDriverSearch,
                       self.cmbNPDownloadableDriverFoundPrinters]:
            widget.set_sensitive(rbtn3)
        self.btnNPDownloadableDriverSearch.\
            set_sensitive (rbtn3 and (self.opreq is None))

        self.setNPButtons()

    def on_filechooserPPD_selection_changed(self, widget):
        self.setNPButtons()

    def on_btnNPDownloadableDriverSearch_clicked(self, widget):
        self.searchedfordriverpackages = True
        if self.opreq is not None:
            for handler in self.opreq_handlers:
                self.opreq.disconnect (handler)

            self.opreq_handlers = None
            self.opreq.cancel ()
            self.opreq = None

        widget.set_sensitive (False)
        label = self.btnNPDownloadableDriverSearch_label
        label.set_text (_("Searching"))
        searchterm = self.entNPDownloadableDriverSearch.get_text ()
        debugprint ('Searching for "%s"' % repr (searchterm))
        self.opreq = OpenPrintingRequest ()
        self.opreq_handlers = []
        self.opreq_handlers.append (
            self.opreq.connect ('finished',
                                self.opreq_user_search_done))
        self.opreq_handlers.append (
            self.opreq.connect ('error',
                                self.opreq_user_search_error))
        self.opreq.searchPrinters (searchterm)
        self.cmbNPDownloadableDriverFoundPrinters.set_sensitive (False)

    def opreq_user_search_done (self, opreq, printers, drivers):
        for handler in self.opreq_handlers:
            opreq.disconnect (handler)

        self.opreq_user_search = True
        self.opreq_handlers = None
        self.opreq = None

        self.founddownloadabledrivers = True
        self.downloadable_printers = printers
        self.downloadable_drivers = drivers

        button = self.btnNPDownloadableDriverSearch
        label = self.btnNPDownloadableDriverSearch_label
        Gdk.threads_enter ()
        try:
            label.set_text (_("Search"))
            button.set_sensitive (True)
            model = Gtk.ListStore (str, str)
            if len (self.downloadable_printers) != 1:
                if len (self.downloadable_printers) > 1:
                    first = _("-- Select from search results --")
                else:
                    first = _("-- No matches found --")

                iter = model.append (None)
                model.set_value (iter, 0, first)
                model.set_value (iter, 1, '')

            sorted_list = []
            for printer_id, printer_name in self.downloadable_printers:
                sorted_list.append ((printer_id, printer_name))

            sorted_list.sort (key=functools.cmp_to_key(lambda x, y: cups.modelSort (x[1], y[1])))
            sought = self.entNPDownloadableDriverSearch.get_text ().lower ()
            select_index = 0
            for id, name in sorted_list:
                iter = model.append (None)
                model.set_value (iter, 0, name)
                model.set_value (iter, 1, id)
                if name.lower () == sought:
                    select_index = model.get_path (iter)[0]
            combobox = self.cmbNPDownloadableDriverFoundPrinters
            combobox.set_model (model)
            combobox.set_active (select_index)
            combobox.set_sensitive (True)
            self.setNPButtons ()
        except:
            nonfatalException()

        Gdk.threads_leave ()

    def opreq_user_search_error (self, opreq, status, err):
        debugprint ("OpenPrinting request failed (%d): %s" % (status,
                                                              repr (err)))
        self.opreq_user_search_done (opreq, list(), dict())

    def on_cmbNPDownloadableDriverFoundPrinters_changed(self, widget):
        self.setNPButtons ()

        if self.opreq is not None:
            for handler in self.opreq_handlers:
                self.opreq.disconnect (handler)

            self.opreq_handlers = None
            self.opreq.cancel ()
            self.opreq = None
            self.btnNPDownloadableDriverSearch.set_sensitive (True)
            self.btnNPDownloadableDriverSearch_label.set_text (_("Search"))

    def fillDownloadableDrivers(self):
        print ("filldownloadableDrivers")
        self.downloadable_driver_for_printer = None
        if self.opreq_user_search == True:
            widget = self.cmbNPDownloadableDriverFoundPrinters
            model = widget.get_model ()
            iter = widget.get_active_iter ()
            if iter:
                printer_id = model.get_value (iter, 1)
                printer_str = model.get_value (iter, 0)
                if printer_id == '':
                    widget.set_active (1)
                    iter = widget.get_active_iter ()
                    if iter:
                        printer_id = model.get_value (iter, 1)
                        printer_str = model.get_value (iter, 0)
                    else:
                        printer_id = None
                        printer_str = None
            else:
                printer_id = None
                printer_str = None
        else:
            printer_id, printer_str = self.downloadable_printers[0]

        if printer_id is None:
            # If none selected, show all.
            # This also happens for ID-matching.
            printer_ids = [x[0] for x in self.downloadable_printers]
        else:
            printer_ids = [printer_id]

        if printer_str:
            self.downloadable_driver_for_printer = printer_str

        model = Gtk.ListStore (str,                     # driver name
                               GObject.TYPE_PYOBJECT)   # driver data
        recommended_iter = None
        first_iter = None
        for printer_id in printer_ids:
            drivers = self.downloadable_drivers[printer_id]
            for driver in drivers.values ():
                if ((not 'ppds' in driver or
                     len(driver['ppds']) <= 0) and
                    (config.DOWNLOADABLE_ONLYPPD or
                     (not self._getDriverInstallationInfo (driver)))):
                    debugprint ("Removed invalid driver entry %s" % \
                                driver['name'])
                    continue
                iter = model.append (None)
                if first_iter is None:
                    first_iter = iter

                model.set_value (iter, 0, driver['name'])
                model.set_value (iter, 1, driver)

                if driver['recommended']:
                    recommended_iter = iter

        if first_iter is None:
            return False

        if not self.rbtnNPDownloadableDriverSearch.get_active() and \
           self.dialog_mode != "download_driver":
            iter = model.append (None)
            model.set_value (iter, 0, _("Local Driver"))
            model.set_value (iter, 1, 0)

        if recommended_iter is None:
            recommended_iter = first_iter

        treeview = self.tvNPDownloadableDrivers
        treeview.set_model (model)
        if recommended_iter is not None:
            treeview.get_selection ().select_iter (recommended_iter)
        self.on_tvNPDownloadableDrivers_cursor_changed(treeview)
        return True

    def on_rbtnNPDownloadLicense_toggled(self, widget):
        self.setNPButtons ()

    # PPD from foomatic

    def fillMakeList(self):
        self.recommended_make_selected = False
        makes = self.ppds.getMakes()
        model = self.tvNPMakes.get_model()
        model.clear()
        found = False
        if self.auto_make:
            auto_make_norm = cupshelpers.ppds.normalize (self.auto_make)
        else:
            auto_make_norm = None

        for make in makes:
            recommended = (auto_make_norm and
                           cupshelpers.ppds.normalize (make) == auto_make_norm)
            if self.device and self.device.make_and_model and recommended:
                text = make + _(" (recommended)")
            else:
                text = make

            iter = model.append((text, make,))
            if recommended:
                path = model.get_path(iter)
                self.tvNPMakes.set_cursor (path, None, False)
                self.tvNPMakes.scroll_to_cell(path, None,
                                              True, 0.5, 0.5)
                found = True

        if not found:
            self.tvNPMakes.set_cursor (Gtk.TreePath(), None, False)
            self.tvNPMakes.scroll_to_cell(0, None, True, 0.0, 0.0)

        # Also pre-fill the OpenPrinting.org search box.
        search = ''
        if self.device and self.device.id_dict:
            devid_dict = self.device.id_dict
            if devid_dict["MFG"] and devid_dict["MDL"]:
                search = devid_dict["MFG"] + " " + devid_dict["MDL"]
            elif devid_dict["DES"]:
                search = devid_dict["DES"]
            elif devid_dict["MFG"]:
                search = devid_dict["MFG"]
        if search == '' and self.auto_make is not None:
            search += self.auto_make
            if self.auto_model is not None:
                search += " " + self.auto_model
        if (search.startswith("Generic") or
            search.startswith("Unknown")):
            search = ''
        self.entNPDownloadableDriverSearch.set_text (search)

    def on_tvNPMakes_cursor_changed(self, tvNPMakes):
        path, column = tvNPMakes.get_cursor()
        if path is not None and self.ppds is not None:
            model = tvNPMakes.get_model ()
            iter = model.get_iter (path)
            self.NPMake = model.get(iter, 1)[0]
            recommended_make = (self.auto_make and
                                cupshelpers.ppds.normalize (self.auto_make) ==
                                cupshelpers.ppds.normalize (self.NPMake))
            self.recommended_make_selected = recommended_make
            self.fillModelList()

    def fillModelList(self):
        self.recommended_model_selected = False
        models = self.ppds.getModels(self.NPMake)
        model = self.tvNPModels.get_model()
        model.clear()
        selected = False
        is_auto_make = (cupshelpers.ppds.normalize (self.NPMake) ==
                        cupshelpers.ppds.normalize (self.auto_make))
        if is_auto_make:
            auto_model_norm = cupshelpers.ppds.normalize (self.auto_model)

        for pmodel in models:
            recommended = (is_auto_make and
                           cupshelpers.ppds.normalize (pmodel) ==
                           auto_model_norm)
            if self.device and self.device.make_and_model and recommended:
                text = pmodel + _(" (recommended)")
            else:
                text = pmodel

            iter = model.append((text, pmodel,))
            if recommended:
                path = model.get_path(iter)
                self.tvNPModels.set_cursor (path, None, False)
                self.tvNPModels.scroll_to_cell(path, None,
                                               True, 0.5, 0.5)
                selected = True
        if not selected:
            self.tvNPModels.set_cursor (Gtk.TreePath(), None, False)
            self.tvNPModels.scroll_to_cell(0, None, True, 0.0, 0.0)
        self.tvNPModels.columns_autosize()

    def fillDriverList(self, pmake, pmodel):
        self.NPModel = pmodel
        model = self.tvNPDrivers.get_model()
        model.clear()

        if self.device:
            devid = self.device.id_dict
        else:
            devid = None

        if (self.device and self.device.make_and_model and
            self.recommended_model_selected and
            self.id_matched_ppdnames):
            # Use the actual device-make-and-model string.
            make_and_model = self.device.make_and_model

            # and the ID-matched list of PPDs.
            self.NPDrivers = self.id_matched_ppdnames
            debugprint ("ID matched PPDs: %s" % repr (self.NPDrivers))
        elif self.ppds:
            # Use a generic make and model string for generating the
            # driver preference list.
            make_and_model = pmake + " " + pmodel
            ppds = self.ppds.getInfoFromModel(pmake, pmodel)
            ppdnames = list(ppds.keys ())

            files = self.installed_driver_files
            try:
                self.NPDrivers = self.ppds.orderPPDNamesByPreference(ppdnames,
                                                                     files,
                                                                     make_and_model,
                                                                     devid)
            except:
                nonfatalException ()
                self.NPDrivers = ppdnames

            # Put the current driver first.
            if self.auto_driver and self.device:
                drivers = []
                for driver in self.NPDrivers:
                    if driver == self.auto_driver:
                        drivers.insert (0, driver)
                    else:
                        drivers.append (driver)

                self.NPDrivers = drivers
        else:
            # No available PPDs for some reason(!)
            debugprint ("No PPDs available?")
            self.NPDrivers = []

        driverlist = []
        NPDrivers = []
        i = 0
        for ppdname in self.NPDrivers:
            ppd = self.ppds.getInfoFromPPDName (ppdname)
            driver = _singleton (ppd["ppd-make-and-model"])
            driver = driver.replace(" (recommended)", "")

            try:
                lpostfix = " [%s]" % _singleton (ppd["ppd-natural-language"])
                driver += lpostfix
            except KeyError:
                pass

            duplicate = driver in driverlist

            if (not (self.device and self.device.make_and_model) and
                self.auto_driver == ppdname):
                driverlist.append (driver)
                NPDrivers.append (ppdname)
                i += 1
                iter = model.append ((driver +
                                      _(" (Current)"),))
                path = model.get_path (iter)
                self.tvNPDrivers.get_selection().select_path(path)
                self.tvNPDrivers.scroll_to_cell(path, None, True, 0.5, 0.0)
            elif self.device and i == 0:
                driverlist.append (driver)
                NPDrivers.append (ppdname)
                i += 1
                iter = model.append ((driver +
                                      _(" (recommended)"),))
                path = model.get_path (iter)
                self.tvNPDrivers.get_selection().select_path(path)
                self.tvNPDrivers.scroll_to_cell(path, None, True, 0.5, 0.0)
            else:
                if duplicate:
                    continue
                driverlist.append (driver)
                NPDrivers.append (ppdname)
                i += 1
                model.append((driver, ))

        self.NPDrivers = NPDrivers
        self.tvNPDrivers.columns_autosize()

    def on_NPDrivers_query_tooltip(self, tv, x, y, keyboard_mode, tooltip):
        if keyboard_mode:
            path = tv.get_cursor()[0]
            if path is None:
                return False
        else:
            bin_x, bin_y = tv.convert_widget_to_bin_window_coords(x, y)
            ret = tv.get_path_at_pos (bin_x, bin_y)
            if ret is None:
                return False
            path = ret[0]

        drivername = self.NPDrivers[path[0]]
        ppddict = self.ppds.getInfoFromPPDName(drivername)
        markup = _singleton (ppddict['ppd-make-and-model'])
        if (drivername.startswith ("foomatic:")):
            markup += " "
            markup += _("This PPD is generated by foomatic.")
        tooltip.set_markup(markup)
        return True

    def on_tvNPModels_cursor_changed(self, widget):
        path, column = widget.get_cursor()
        if path is not None:
            model = widget.get_model ()
            iter = model.get_iter (path)
            pmodel = model.get(iter, 1)[0]

            # Find out if this is the auto-detected make and model
            recommended_model = (self.recommended_make_selected and
                                 self.auto_model and
                                 self.auto_model.lower () == pmodel.lower ())
            self.recommended_model_selected = recommended_model
            self.fillDriverList(self.NPMake, pmodel)
            self.on_tvNPDrivers_cursor_changed(self.tvNPDrivers)

    def on_tvNPDrivers_cursor_changed(self, widget):
        self.setNPButtons()

    def on_tvNPDownloadableDrivers_cursor_changed(self, widget):
        # Clear out the properties.
        self.lblNPDownloadableDriverSupplier.set_text ('')
        self.lblNPDownloadableDriverLicense.set_text ('')
        self.lblNPDownloadableDriverDescription.set_text ('')
        self.lblNPDownloadableDriverSupportContacts.set_text ('')
        self.rbtnNPDownloadLicenseNo.set_active (True)
        self.frmNPDownloadableDriverLicenseTerms.hide ()

        selection = widget.get_selection ()
        if selection is None:
            return

        model, iter = selection.get_selected ()
        if not iter:
            path, column = widget.get_cursor()
            if iter:
                iter = model.get_iter (path)
            else:
                return
        driver = model.get_value (iter, 1)
        if driver == 0:
            self.ntbkNPDownloadableDriverProperties.set_current_page (self.PAGE_DESCRIBE_PRINTER)
            self.setNPButtons()
            return
        self.ntbkNPDownloadableDriverProperties.set_current_page (self.PAGE_SELECT_DEVICE)
        supplier = driver.get('supplier', _("OpenPrinting"))
        vendor = self.cbNPDownloadableDriverSupplierVendor
        active = driver['manufacturersupplied']

        def set_protect_active (widget, active):
            widget.protect_active = active
            widget.set_active (active)

        set_protect_active (vendor, active)
        self.lblNPDownloadableDriverSupplier.set_text (supplier)

        license = driver.get('license', _("Distributable"))
        patents = self.cbNPDownloadableDriverLicensePatents
        free = self.cbNPDownloadableDriverLicenseFree
        set_protect_active (patents, driver['patents'])
        set_protect_active (free, driver['freesoftware'])
        self.lblNPDownloadableDriverLicense.set_text (license)

        description = driver.get('shortdescription', _("None"))
        self.lblNPDownloadableDriverDescription.set_markup (description)

        if 'functionality' in driver:
            functionality = driver['functionality']
            for field in ["Graphics", "LineArt", "Photo", "Text"]:
                key = field.lower ()
                value = None
                hs = self.__dict__.get ("hsDownloadableDriverPerf%s" % field)
                unknown = self.__dict__.get ("lblDownloadableDriverPerf%sUnknown"
                                             % field)
                if key in functionality:
                    if hs:
                        try:
                            value = int (functionality[key])
                            hs.set_range (0, 100)
                            hs.set_value (value)
                            hs.show_all ()
                            unknown.hide ()
                        except:
                            pass

                if value is None:
                    hs.hide ()
                    unknown.show_all ()

        supportcontacts = ""
        if 'supportcontacts' in driver:
            for supportentry in driver['supportcontacts']:
                if supportentry['name']:
                    supportcontact = " - " + supportentry['name']
                    supportcontact_extra = ""
                    if supportentry['url']:
                        supportcontact_extra = supportcontact_extra + \
                            supportentry['url']
                    if supportentry['level']:
                        if supportcontact_extra:
                            supportcontact_extra = supportcontact_extra + _(", ")
                        supportcontact_extra = supportcontact_extra + \
                            supportentry['level']
                    if supportcontact_extra:
                        supportcontact = supportcontact + \
                            _("\n(%s)") % supportcontact_extra
                        if supportcontacts:
                            supportcontacts = supportcontacts + "\n"
                        supportcontacts = supportcontacts + supportcontact
        if not supportcontacts:
            supportcontacts = _("No support contacts known")
        self.lblNPDownloadableDriverSupportContacts.set_text (supportcontacts)
        if 'licensetext' in driver:
            self.frmNPDownloadableDriverLicenseTerms.show ()
            terms = driver.get('licensetext', _("Not specified."))
            self.tvNPDownloadableDriverLicense.get_buffer ().set_text (terms)
        else:
            self.frmNPDownloadableDriverLicenseTerms.hide ()
        if not driver['nonfreesoftware'] and not driver['patents']:
            self.rbtnNPDownloadLicenseYes.set_active (True)
            self.rbtnNPDownloadLicenseYes.hide ()
            self.rbtnNPDownloadLicenseNo.hide ()
        else:
            self.rbtnNPDownloadLicenseNo.set_active (True)
            self.rbtnNPDownloadLicenseYes.show ()
            self.rbtnNPDownloadLicenseNo.show ()
            self.frmNPDownloadableDriverLicenseTerms.show ()
            terms = driver.get('licensetext', _("Not specified."))
            self.tvNPDownloadableDriverLicense.get_buffer ().set_text (terms)

        if 'ppds' in driver and len(driver["ppds"]) > 0:
            self.founddownloadableppd = True
        else:
            self.founddownloadableppd = False

        self.setNPButtons()

    def getNPPPD(self):
        ppd = None
        try:
            if ((self.rbtnNPFoomatic.get_active() or
                    len(self.installed_driver_files) > 0) and
                self.founddownloadableppd == False):
                model, iter = self.tvNPDrivers.get_selection().get_selected()
                nr = model.get_path(iter)[0]
                ppd = self.NPDrivers[nr]
            elif self.rbtnNPPPD.get_active():
                ppd = cups.PPD(self.filechooserPPD.get_filename())
            else:
                # PPD of the driver downloaded from OpenPrinting XXX
                treeview = self.tvNPDownloadableDrivers
                model, iter = treeview.get_selection ().get_selected ()
                driver = model.get_value (iter, 1)
                if driver != 0 and 'ppds' in driver:
                    # Only need to download a PPD.
                    if (len(driver['ppds']) > 0):
                        file_to_download = driver['ppds'][0]
                        debugprint ("ppd file to download [" + file_to_download+ "]")
                        file_to_download = file_to_download.strip()
                        if (len(file_to_download) > 0):
                            ppdurlobj = urllib.request.urlopen(file_to_download)
                            ppdcontent = ppdurlobj.read()
                            ppdurlobj.close()
                            with tempfile.NamedTemporaryFile () as tmpf:
                                tmpf.write(ppdcontent)
                                tmpf.flush ()
                                ppd = cups.PPD(tmpf.name)

        except (RuntimeError, urllib.error.HTTPError) as e:
            debugprint ("RuntimeError: " + repr (e))
            if self.rbtnNPFoomatic.get_active():
                # Foomatic database problem of some sort.
                err_title = _('Database error')
                err_text = _("The '%s' driver cannot be "
                             "used with printer '%s %s'.")
                model, iter = (self.tvNPDrivers.get_selection().
                               get_selected())
                nr = model.get_path(iter)[0]
                driver = self.NPDrivers[nr]
                if driver.startswith ("gutenprint"):
                    # This printer references some XML that is not
                    # installed by default.  Point the user at the
                    # package they need to install.
                    err = _("You will need to install the '%s' package "
                            "in order to use this driver.") % \
                            "gutenprint-foomatic"
                else:
                    err = err_text % (driver, self.NPMake, self.NPModel)
            elif self.rbtnNPPPD.get_active():
                # This error came from trying to open the PPD file.
                err_title = _('PPD error')
                filename = self.filechooserPPD.get_filename()
                err = _('Failed to read PPD file.  Possible reason '
                        'follows:') + '\n'
                try:
                    # We want this to be in the current natural language,
                    # so we intentionally don't set LC_ALL=C here.
                    p = subprocess.Popen (['/usr/local/bin/cupstestppd',
                                           '-rvv', filename],
                                          close_fds=True,
                                          stdin=subprocess.DEVNULL,
                                          stdout=subprocess.PIPE,
                                          stderr=subprocess.PIPE)
                    (stdout, stderr) = p.communicate ()
                    err += stdout.decode ()
                except:
                    # Problem executing command.
                    raise
            else:
                # Failed to get PPD downloaded from OpenPrinting XXX
                err_title = _('Downloadable drivers')
                err = _("Failed to download PPD.")

            show_error_dialog (err_title, err, self.NewPrinterWindow)
            return None

        debugprint("ppd: " + repr(ppd))

        if isinstance(ppd, str):
            self.cups._begin_operation (_("fetching PPD"))
            try:
                if ppd != "raw":
                    f = self.cups.getServerPPD(ppd)
                    ppd = cups.PPD(f)
                    os.unlink(f)
            except RuntimeError:
                nonfatalException()
                debugprint ("libcups from CUPS 1.3 not available: never mind")
            except cups.IPPError:
                nonfatalException()
                debugprint ("CUPS 1.3 server not available: never mind")

            self.cups._end_operation ()

        return ppd

    # Installable Options

    def fillNPInstallableOptions(self):
        debugprint ("Examining installable options")
        self.installable_options = False
        self.options = { }

        container = self.vbNPInstallOptions
        for child in container.get_children():
            container.remove(child)

        if not self.ppd:
            l = Gtk.Label(label=_("No Installable Options"))
            container.add(l)
            l.show()
            debugprint ("No PPD so no installable options")
            return

        # build option tabs
        for group in self.ppd.optionGroups:
            if group.name != "InstallableOptions":
                continue
            self.installable_options = True
            grid = Gtk.Grid()
            grid.set_column_spacing(6)
            grid.set_row_spacing(6)
            container.add(grid)
            rows = 0

            for nr, option in enumerate(group.options):
                if option.keyword == "PageRegion":
                    continue
                rows += 1
                o = OptionWidget(option, self.ppd, self)
                grid.attach(o.conflictIcon, 0, nr, 1, 1)

                hbox = Gtk.Box()
                if o.label:
                    a = Gtk.Alignment.new (0.5, 0.5, 1.0, 1.0)
                    a.set_padding (0, 0, 0, 6)
                    a.add (o.label)
                    grid.attach(a, 1, nr, 1, 1)
                    grid.attach(hbox, 2, nr, 1, 1)
                else:
                    grid.attach(hbox, 1, nr, 2, 1)
                hbox.pack_start(o.selector, False, False, 0)
                self.options[option.keyword] = o
        if not self.installable_options:
            l = Gtk.Label(label=_("No Installable Options"))
            container.add(l)
            l.show()
        self.scrNPInstallableOptions.hide()
        self.scrNPInstallableOptions.show_all()


    # Create new Printer
    def on_btnNPApply_clicked(self, widget):
        if self.fetchDevices_conn:
            self.fetchDevices_conn.destroy ()
            self.fetchDevices_conn = None
            self.dec_spinner_task ()

        if self.ppdsloader:
            self.ppdsloader.destroy ()
            self.ppdsloader = None

        if self.printer_finder:
            self.printer_finder.cancel ()
            self.printer_finder = None
            self.dec_spinner_task ()

        if self.dialog_mode in ("class", "printer", "printer_with_uri"):
            name = self.entNPName.get_text()
            location = self.entNPLocation.get_text()
            info = self.entNPDescription.get_text()
        else:
            name = self._name

        ppd = self.ppd

        if self.dialog_mode == "class":
            members = getCurrentClassMembers(self.tvNCMembers)
            try:
                for member in members:
                    try:
                        self.cups.addPrinterToClass(member, name)
                    except RuntimeError:
                        # Printer already in class?
                        continue
            except cups.IPPError as e:
                (e, msg) = e.args
                self.show_IPP_Error(e, msg)
                return
        elif self.dialog_mode == "printer" or \
                self.dialog_mode == "printer_with_uri":
            uri = None
            if self.device.uri:
                uri = self.device.uri
            else:
                uri = self.getDeviceURI()
            if not self.ppd: # XXX needed?
                # Go back to previous page to re-select driver.
                self.nextNPTab(-1)
                return

            # write Installable Options to ppd
            for option in self.options.values():
                option.writeback()

            busy (self.NewPrinterWindow)
            while Gtk.events_pending ():
                Gtk.main_iteration ()
            self.cups._begin_operation (_("adding printer %s") % name)
            try:
                if isinstance(ppd, str):
                    self.cups.addPrinter(name, ppdname=ppd,
                         device=uri, info=info, location=location)
                elif ppd is None: # raw queue
                    self.cups.addPrinter(name, device=uri,
                                         info=info, location=location)
                else:
                    # Note: LC_MESSAGES is used here to determine the
                    # page size instead of LC_PAPER. This is not
                    # really correct, but it's what CUPS does so to
                    # avoid confusion we'll follow that method.
                    cupshelpers.setPPDPageSize(ppd, self.language[0])
                    self.cups.addPrinter(name, ppd=ppd, device=uri,
                                         info=info, location=location)
            except cups.IPPError as e:
                (e, msg) = e.args
                ready (self.NewPrinterWindow)
                self.show_IPP_Error(e, msg)
                self.cups._end_operation()
                return
            except:
                ready (self.NewPrinterWindow)
                self.cups._end_operation()
                fatalException (1)
            self.cups._end_operation()
            ready (self.NewPrinterWindow)
        if self.dialog_mode in ("class", "printer", "printer_with_uri"):
            self.cups._begin_operation (_("modifying printer %s") % name)
            try:
                cupshelpers.activateNewPrinter (self.cups, name)
                self.cups.setPrinterLocation(name, location)
                self.cups.setPrinterInfo(name, info)
            except cups.IPPError as e:
                (e, msg) = e.args
                self.show_IPP_Error(e, msg)
                self.cups._end_operation ()
                return
            self.cups._end_operation ()
        elif self.dialog_mode == "device":
            self.cups._begin_operation (_("modifying printer %s") % name)
            try:
                uri = self.getDeviceURI()
                self.cups.addPrinter(name, device=uri)
            except cups.IPPError as e:
                (e, msg) = e.args
                self.show_IPP_Error(e, msg)
                self.cups._end_operation ()
                return
            self.cups._end_operation ()
        elif self.dialog_mode == "ppd":
            if not ppd:
                ppd = self.ppd = self.getNPPPD()
                if not ppd:
                    # Go back to previous page to re-select driver.
                    self.nextNPTab(-1)
                    return

            self.cups._begin_operation (_("modifying printer %s") % name)
            # set ppd on server and retrieve it
            # cups doesn't offer a way to just download a ppd ;(=
            raw = False
            if isinstance(ppd, str):
                if self.rbtnChangePPDasIs.get_active():
                    # To use the PPD as-is we need to prevent CUPS copying
                    # the old options over.  Do this by setting it to a
                    # raw queue (no PPD) first.
                    try:
                        self.cups.addPrinter(name, ppdname='raw')
                    except cups.IPPError as e:
                        (e, msg) = e.args
                        self.show_IPP_Error(e, msg)
                try:
                    self.cups.addPrinter(name, ppdname=ppd)
                except cups.IPPError as e:
                    (e, msg) = e.args
                    self.show_IPP_Error(e, msg)
                    self.cups._end_operation ()
                    return

                try:
                    filename = self.cups.getPPD(name)
                    ppd = cups.PPD(filename)
                    os.unlink(filename)
                except cups.IPPError as e:
                    (e, msg) = e.args
                    if e == cups.IPP_NOT_FOUND:
                        raw = True
                    else:
                        self.show_IPP_Error(e, msg)
                        self.cups._end_operation ()
                        return
            else:
                # We have an actual PPD to upload, not just a name.
                if ((not self.rbtnChangePPDasIs.get_active()) and
                    isinstance (self.orig_ppd, cups.PPD)):
                    cupshelpers.copyPPDOptions(self.orig_ppd, ppd)
                else:
                    cupshelpers.setPPDPageSize(ppd, self.language[0])

                # write Installable Options to ppd
                for option in self.options.values():
                    option.writeback()

                try:
                    self.cups.addPrinter(name, ppd=ppd)
                except cups.IPPError as e:
                    (e, msg) = e.args
                    self.show_IPP_Error(e, msg)
                    self.cups._end_operation ()
                    return

            self.cups._end_operation ()

        elif self.dialog_mode == "download_driver":
            self.nextNPTab(0);

        self.NewPrinterWindow.hide()
        if self.dialog_mode in ["printer", "printer_with_uri", "class"]:
            self.emit ('printer-added', name)
        elif self.dialog_mode == "download_driver":
            self.emit ('driver-download-checked', self.installed_driver_files)
        else:
            self.emit ('printer-modified', name, self.orig_ppd != self.ppd)

        self.device = None
        self.printers = {}

def show_help():
    print ("\nThis is the test/debug mode of the new-printer dialog of " \
           "system-config-printer.\n\n"
           "Options:\n\n"
           "  --setup-printer URI\n"
           "            Select the (detected) CUPS device URI on start up\n"
           "            and run the new-printer wizard for it.\n\n"
           "  --devid   Supply a device ID which should be used for the\n"
           "            setup of the new printer with \"--setup-printer\".\n"
           "            This can be any printer's ID, so that driver \n"
           "            selection can be tested for printers which are not\n"
           "            physically available.\n")

if __name__ == '__main__':
    import getopt
    try:
        opts, args = getopt.gnu_getopt (sys.argv[1:], '',
                                        ['setup-printer=',
                                         'devid='])
    except getopt.GetoptError:
        show_help ()
        sys.exit (1)

    setup_printer = None
    devid = ""
    for opt, optarg in opts:
        if opt == '--setup-printer':
            setup_printer = optarg
        elif opt == '--devid':
            devid = optarg

    os.environ["SYSTEM_CONFIG_PRINTER_UI"] = "ui"
    import ppdippstr
    locale.setlocale (locale.LC_ALL, "")
    ppdippstr.init ()
    set_debugging (True)
    cupshelpers.set_debugprint_fn (debugprint)

    n = NewPrinterGUI ()
    def on_signal (*args):
        Gtk.main_quit ()

    n.connect ("printer-added", on_signal)
    n.connect ("printer-modified", on_signal)
    n.connect ("dialog-canceled", on_signal)
    if setup_printer is not None:
        n.init ("printer_with_uri", device_uri=setup_printer, devid=devid)
    else:
        n.init ("printer")
    Gtk.main ()
