#!/bin/env python ## system-config-printer ## Copyright (C) 2006, 2007, 2008 Red Hat, Inc. ## Copyright (C) 2006 Florian Festi ## Copyright (C) 2006, 2007, 2008 Tim Waugh ## 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., 675 Mass Ave, Cambridge, MA 02139, USA. # config is generated from config.py.in by configure import config import sys, os, tempfile, time import signal, thread try: import gtk.glade except RuntimeError, e: print "system-config-printer:", e print "This is a graphical application and requires DISPLAY to be set." sys.exit (1) if len(sys.argv)>1 and sys.argv[1] == '--help': print ("\nThis is system-config-printer, " \ "a CUPS server configuration program.\n") sys.exit (0) import cups import pysmb import cupshelpers, options import gobject # for TYPE_STRING and TYPE_PYOBJECT from optionwidgets import OptionWidget from foomatic import Foomatic from nametree import BuildTree from cupsd import CupsConfig import probe_printer import gtk_label_autowrap domain='system-config-printer' import locale locale.setlocale (locale.LC_ALL, "") from rhpl.translate import _, N_ import rhpl.translate as translate translate.textdomain (domain) gtk.glade.bindtextdomain (domain) pkgdata = '/usr/share/' + domain glade_file = pkgdata + '/' + domain + '.glade' sys.path.append (pkgdata) busy_cursor = gtk.gdk.Cursor(gtk.gdk.WATCH) ready_cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR) def percentEncode (text): """Percent-encode ASCII text ready for inclusion in a URI.""" l = len (text) i = 0 while i < l: c = text[i] a = ord (c) if (a <= 0x1f or a == 0x7f or c == ' ' or '<>#%"'.find (c) != -1 or '{}|\^[]`'.find (c) != -1): pre = text[:i] post = text[i + 1:] text = pre + "%" + ("%2X" % a) + post i += 2 l += 2 i += 1 return text def percentDecode (text): """Percent-decode URI text to ASCII.""" l = len (text) r = 0 w = '' xdigs = "01234567890abcdef" while r < l: if r + 2 < l and text[r] == '%': c10 = xdigs.find (text[r + 1].lower ()) if c10 != -1: c01 = xdigs.find (text[r + 2].lower ()) if c01 != -1: w += chr (c10 * 0x10 + c01) r += 3 else: w += text[r] r += 1 return w class GUI: def __init__(self): self.language = locale.getlocale(locale.LC_MESSAGES) self.encoding = locale.getlocale(locale.LC_CTYPE) self.printer = None self.conflicts = set() # of options self.password = '' self.passwd_retry = False cups.setPasswordCB(self.cupsPasswdCallback) self.changed = set() # of options self.servers = set(("localhost",)) try: self.cups = cups.Connection() except RuntimeError: self.cups = None # WIDGETS # ======= try: #raise ValueError # uncomment for development self.xml = gtk.glade.XML(glade_file, domain = domain) except: self.xml = gtk.glade.XML(domain + '.glade', domain = domain) self.getWidgets("MainWindow", "tvMainList", "ntbkMain", "statusbarMain", "btnNewPrinter", "btnNewClass", "btnCopy", "btnDelete", "new_printer", "new_class", "copy", "delete", "btnGotoServer", "btnApply", "btnRevert", "btnConflict", "chkServerBrowse", "chkServerShare", "chkServerRemoteAdmin", "chkServerAllowCancelAll", "chkServerLogDebug", "ntbkPrinter", "entPDescription", "entPLocation", "lblPMakeModel", "lblPMakeModel2", "lblPState", "entPDevice", "lblPDevice2", "btnSelectDevice", "btnChangePPD", "chkPEnabled", "chkPAccepting", "chkPShared", "btnPMakeDefault", "lblPDefault", "btnPrintTestPage", "cmbPStartBanner", "cmbPEndBanner", "cmbPErrorPolicy", "cmbPOperationPolicy", "rbtnPAllow", "rbtnPDeny", "tvPUsers", "entPUser", "btnPAddUser", "btnPDelUser", "swPInstallOptions", "vbPInstallOptions", "swPOptions", "lblPOptions", "vbPOptions", "algnClassMembers", "vbClassMembers", "lblClassMembers", "tvClassMembers", "tvClassNotMembers", "btnClassAddMember", "btnClassDelMember", "cmbentNewOption", "tblServerOptions", "btnNewOption", "ConnectDialog", "chkEncrypted", "cmbServername", "entUser", "ConnectingDialog", "lblConnecting", "PasswordDialog", "lblPasswordPrompt", "entPasswd", "ErrorDialog", "lblError", "InfoDialog", "lblInfo", "ApplyDialog", "NewPrinterWindow", "ntbkNewPrinter", "btnNPBack", "btnNPForward", "btnNPApply", "entNPName", "entNPDescription", "entNPLocation", "tvNPDevices", "ntbkNPType", "cmbNPTSerialBaud", "cmbNPTSerialParity", "cmbNPTSerialBits", "cmbNPTSerialFlow", "cmbentNPTLpdHost", "cmbentNPTLpdQueue", "entNPTIPPHostname", "entNPTIPPPrintername", "entNPTDirectJetHostname", "entNPTDirectJetPort", "entNPTIPPHostname", "entNPTIPPPrintername", "entSMBURI", "tvSMBBrowser", "entSMBUsername", "entSMBPassword", "entNPTDevice", "tvNCMembers", "tvNCNotMembers", "rbtnNPPPD", "tvNPMakes", "rbtnNPFoomatic", "filechooserPPD", "tvNPModels", "tvNPDrivers", "lblNPPDescription", "lblNPDDescription", "lblNPPPDDescription", "frmNPPDescription", "frmNPDDescription", "frmNPPPDDescription", "rbtnChangePPDasIs", "lblNPApply", "NewPrinterName", "entCopyName", "btnCopyOk", "AboutDialog", "lblVersion", "lblCopyright", "lblAuthors", ) # Set up "About" dialog strings. vertext = self.lblVersion.get_text () self.lblVersion.set_text (vertext % config.VERSION) self.lblCopyright.set_text (_("Copyright 2006 Red Hat, Inc.")) self.lblAuthors.set_text ("Florian Festi, Tim Waugh") self.static_tabs = 3 gtk_label_autowrap.set_autowrap(self.NewPrinterWindow) self.status_context_id = self.statusbarMain.get_context_id( "Connection") self.setConnected() self.ntbkMain.set_show_tabs(False) self.ntbkNewPrinter.set_show_tabs(False) self.ntbkNPType.set_show_tabs(False) self.prompt_primary = self.lblPasswordPrompt.get_label () # Paint Description labels black on white for label in (self.lblNPPDescription, self.lblNPDDescription, self.lblNPPPDDescription): parent = label.get_parent() parent.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(65535, 65535, 65535)) # Setup main list column = gtk.TreeViewColumn() cell = gtk.CellRendererText() cell.markup = True column.pack_start(cell, True) self.tvMainList.append_column(column) self.mainlist = gtk.TreeStore(str, str) self.tvMainList.set_model(self.mainlist) column.set_attributes(cell, text=0) selection = self.tvMainList.get_selection() selection.set_mode(gtk.SELECTION_BROWSE) selection.set_select_function(self.maySelectItem) self.mainlist.append(None, (_("Server Settings"), 'Settings')) self.tooltips = gtk.Tooltips() self.tooltips.enable() # setup some lists m = gtk.SELECTION_MULTIPLE s = gtk.SELECTION_SINGLE for name, treeview, selection_mode in ( (_("Members of this Class"), self.tvClassMembers, m), (_("Others"), self.tvClassNotMembers, m), (_("Members of this Class"), self.tvNCMembers, m), (_("Others"), self.tvNCNotMembers, m), (_("Devices"), self.tvNPDevices, s), (_("Makes"), self.tvNPMakes,s), (_("Models"), self.tvNPModels,s), (_("Drivers"), self.tvNPDrivers,s), (_("Users"), self.tvPUsers, m), ): model = gtk.ListStore(str) 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) ppd_filter = gtk.FileFilter() ppd_filter.set_name(_("PostScript Printer Description (*.ppd[.gz])")) ppd_filter.add_pattern("*.ppd") ppd_filter.add_pattern("*.PPD") ppd_filter.add_pattern("*.ppd.gz") self.filechooserPPD.add_filter(ppd_filter) self.conflict_dialog = gtk.MessageDialog( parent=None, flags=0, type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_OK) # SMB browser self.smb_store = gtk.TreeStore (str, # host or share str, # comment gobject.TYPE_PYOBJECT, # domain dict gobject.TYPE_PYOBJECT) # host dict self.tvSMBBrowser.set_model (self.smb_store) self.smb_store.set_sort_column_id (0, gtk.SORT_ASCENDING) # SMB list columns col = gtk.TreeViewColumn (_("Share"), gtk.CellRendererText (), text=0) col.set_resizable (True) col.set_sort_column_id (0) self.tvSMBBrowser.append_column (col) col = gtk.TreeViewColumn (_("Comment"), gtk.CellRendererText (), text=1) self.tvSMBBrowser.append_column (col) slct = self.tvSMBBrowser.get_selection () slct.set_select_function (self.smb_select_function) self.xml.signal_autoconnect(self) try: self.populateList() except cups.HTTPError, (s,): self.cups = None self.setConnected() self.populateList() self.show_HTTP_Error(s) def getWidgets(self, *names): for name in names: widget = self.xml.get_widget(name) if widget is None: raise ValueError, "Widget '%s' not found" % name setattr(self, name, widget) def busy (self, win = None): if not win: win = self.MainWindow win.window.set_cursor (busy_cursor) while gtk.events_pending (): gtk.main_iteration () def ready (self, win = None): if not win: win = self.MainWindow win.window.set_cursor (ready_cursor) while gtk.events_pending (): gtk.main_iteration () def loadFoomatic(self): try: return self.foomatic except: self.foomatic = Foomatic() # this works on the local db self.foomatic.addCupsPPDs(self.cups.getPPDs(), self.cups) return self.foomatic def unloadFoomatic(self): try: del self.foomatic except: pass def setConnected(self): connected = bool(self.cups) host = cups.getServer() if host[0] == '/': host = 'localhost' self.MainWindow.set_title(_("Printer configuration - %s") % host) if connected: status_msg = _("Connected to %s") % host else: status_msg = _("Not connected") self.statusbarMain.push(self.status_context_id, status_msg) for widget in (self.btnNewPrinter, self.btnNewClass, self.new_printer, self.new_class, self.chkServerBrowse, self.chkServerShare, self.chkServerRemoteAdmin, self.chkServerAllowCancelAll, self.chkServerLogDebug): widget.set_sensitive(connected) def getServers(self): self.servers.discard(None) known_servers = list(self.servers) known_servers.sort() return known_servers def populateList(self): old_name, old_type = self.getSelectedItem() select_path = None # get Printers if self.cups: try: self.printers = cupshelpers.getPrinters(self.cups) except cups.IPPError, (e, m): self.show_IPP_Error(e, m) self.printers = {} else: self.printers = {} self.default_printer = "" local_printers = [] local_classes = [] remote_printers = [] remote_classes = [] for name, printer in self.printers.iteritems(): if printer.default: self.default_printer = name self.servers.add(printer.getServer()) if printer.remote: if printer.is_class: remote_classes.append(name) else: remote_printers.append(name) else: if printer.is_class: local_classes.append(name) else: local_printers.append(name) local_printers.sort() local_classes.sort() remote_printers.sort() remote_classes.sort() expanded = { "_printers" : True, "_classes" : True, "_remote_printers" : True, "_remote_classes" : True, } # remove old printers/classes iter = self.mainlist.get_iter_first() iter = self.mainlist.iter_next(iter) # step over server settings while iter: entry = self.mainlist.get_value(iter, 1) path = self.mainlist.get_path(iter) expanded[entry] = self.tvMainList.row_expanded(path) more_entries = self.mainlist.remove(iter) if not more_entries: break # add new for printers, text, name in ( (local_printers, _("Local Printers"), "_printers"), (local_classes, _("Local Classes"), "_classes"), (remote_printers, _("Remote Printers"), "_remote_printers"), (remote_classes, _("Remote Classes"), "_remote_classes")): if not printers: continue iter = self.mainlist.append(None, (text, name)) path = self.mainlist.get_path(iter) for printer_name in printers: p_iter = self.mainlist.append(iter, (printer_name, "Printer")) if printer_name==old_name: select_path = self.mainlist.get_path(p_iter) if expanded[name]: self.tvMainList.expand_row(path, False) # Selection selection = self.tvMainList.get_selection() if select_path: selection.select_path(select_path) else: selection.unselect_all() self.on_tvMainList_cursor_changed(self.tvMainList) def maySelectItem(self, selection): result = self.mainlist.get_value( self.mainlist.get_iter(selection), 1) return result[0] != "_" def getSelectedItem(self): model, iter = self.tvMainList.get_selection().get_selected() if iter is None: return ("", 'None') name, type = model.get_value(iter, 0), model.get_value(iter, 1) return name.strip(), type # Connect to Server def on_connect_activate(self, widget): # check for unapplied changes if self.changed: response = self.ApplyDialog.run() self.ApplyDialog.hide() err = False if response == gtk.RESPONSE_APPLY: err = self.apply() if err or response == gtk.RESPONSE_CANCEL: return # Use browsed queues to build up a list of known IPP servers servers = self.getServers() current_server = (self.printer and self.printer.getServer()) \ or cups.getServer() store = gtk.ListStore (gobject.TYPE_STRING) self.cmbServername.set_model(store) for server in servers: self.cmbServername.append_text(server) self.cmbServername.show() self.cmbServername.child.set_text (current_server) self.entUser.set_text (cups.getUser()) self.chkEncrypted.set_active (cups.getEncryption() == cups.HTTP_ENCRYPT_ALWAYS) self.cmbServername.grab_focus () self.ConnectDialog.set_transient_for (self.MainWindow) response = self.ConnectDialog.run() self.ConnectDialog.hide() if response != gtk.RESPONSE_OK: return if self.chkEncrypted.get_active(): cups.setEncryption(cups.HTTP_ENCRYPT_ALWAYS) else: cups.setEncryption(cups.HTTP_ENCRYPT_IF_REQUESTED) servername = self.cmbServername.child.get_text() user = self.entUser.get_text() self.lblConnecting.set_text(_("Connecting to Server:\n%s") % servername) self.unloadFoomatic() self.ConnectingDialog.show() self.connect_thread = thread.start_new_thread( self.connect, (servername, user)) def on_cancel_connect_clicked(self, widget): """ Stop connection to new server (Doesn't really stop but sets flag for the connecting thread to ignore the connection) """ self.connect_thread = None self.ConnectingDialog.hide() def connect(self, servername, user): """ Open a connection to a new server. Is executed in a separate thread! """ cups.setServer(servername) cups.setPasswordCB(self.cupsPasswdCallback) # cups.setEncryption (...) if user: cups.setUser(user) self.password = '' try: connection = cups.Connection() foomatic = Foomatic() foomatic.addCupsPPDs(connection.getPPDs(), connection) except RuntimeError, s: if self.connect_thread != thread.get_ident(): return gtk.gdk.threads_enter() self.ConnectingDialog.hide() self.show_IPP_Error(None, s) gtk.gdk.threads_leave() return except cups.IPPError, (e, s): if self.connect_thread != thread.get_ident(): return gtk.gdk.threads_enter() self.ConnectingDialog.hide() self.show_IPP_Error(e, s) gtk.gdk.threads_leave() return if self.connect_thread != thread.get_ident(): return gtk.gdk.threads_enter() try: self.foomatic = foomatic self.ConnectingDialog.hide() self.cups = connection self.setConnected() self.populateList() except cups.HTTPError, (s,): self.cups = None self.setConnected() self.populateList() self.show_HTTP_Error(s) gtk.gdk.threads_leave() def reconnect (self): """Reconnect to CUPS after the server has reloaded.""" print "FIXME: need to reconnect to CUPS here" def on_btnCancelConnect_clicked(self, widget): """Close Connect dialog""" self.ConnectWindow.hide() # Password handling def cupsPasswdCallback(self, querystring): if self.passwd_retry or len(self.password) == 0: self.lblPasswordPrompt.set_label (self.prompt_primary + querystring) self.PasswordDialog.set_transient_for (self.MainWindow) self.entPasswd.grab_focus () result = self.PasswordDialog.run() self.PasswordDialog.hide() if result == gtk.RESPONSE_OK: self.password = self.entPasswd.get_text() else: self.password = '' self.passwd_retry = False else: self.passwd_retry = True return self.password def on_btnPasswdOk_clicked(self, widget): self.PasswordDialog.response(0) def on_btnPasswdCancel_clicked(self, widget): self.PasswordDialog.response(1) # refresh def on_btnRefresh_clicked(self, button): self.populateList() # Unapplied changes dialog def on_btnApplyApply_clicked(self, button): self.ApplyDialog.response(gtk.RESPONSE_APPLY) def on_btnApplyCancel_clicked(self, button): self.ApplyDialog.response(gtk.RESPONSE_CANCEL) def on_btnApplyDiscard_clicked(self, button): self.ApplyDialog.response(gtk.RESPONSE_REJECT) # Data handling def on_printer_changed(self, widget): if isinstance(widget, gtk.CheckButton): value = widget.get_active() elif isinstance(widget, gtk.Entry): value = widget.get_text() elif isinstance(widget, gtk.RadioButton): value = widget.get_active() elif isinstance(widget, gtk.ComboBox): value = widget.get_active_text() else: raise ValueError, "Widget type not supported (yet)" p = self.printer old_values = { self.entPDescription : p.info, self.entPLocation : p.location, self.entPDevice : p.device_uri, self.chkPEnabled : p.enabled, self.chkPAccepting : not p.rejecting, self.chkPShared : p.is_shared, self.cmbPStartBanner : p.job_sheet_start, self.cmbPEndBanner : p.job_sheet_end, self.cmbPErrorPolicy : p.error_policy, self.cmbPOperationPolicy : p.op_policy, self.rbtnPAllow: p.default_allow, } old_value = old_values[widget] if old_value == value: self.changed.discard(widget) else: self.changed.add(widget) self.setDataButtonState() 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() # Access control def getPUsers(self): """return list of usernames from the GUI""" model = self.tvPUsers.get_model() result = [] model.foreach(lambda model, path, iter: result.append(model.get(iter, 0)[0])) result.sort() return result def setPUsers(self, users): """write list of usernames inot the GUI""" model = self.tvPUsers.get_model() model.clear() for user in users: model.append((user,)) self.on_entPUser_changed(self.entPUser) self.on_tvPUsers_cursor_changed(self.tvPUsers) def checkPUsersChanged(self): """check if users in GUI and printer are different and set self.changed""" if self.getPUsers() != self.printer.except_users: self.changed.add(self.tvPUsers) else: self.changed.discard(self.tvPUsers) self.on_tvPUsers_cursor_changed(self.tvPUsers) self.setDataButtonState() def on_btnPAddUser_clicked(self, button): user = self.entPUser.get_text() if user: self.tvPUsers.get_model().insert(0, (user,)) self.entPUser.set_text("") self.checkPUsersChanged() def on_btnPDelUser_clicked(self, button): model, rows = self.tvPUsers.get_selection().get_selected_rows() rows = [gtk.TreeRowReference(model, row) for row in rows] for row in rows: path = row.get_path() iter = model.get_iter(path) model.remove(iter) self.checkPUsersChanged() def on_entPUser_changed(self, widget): self.btnPAddUser.set_sensitive(bool(widget.get_text())) def on_tvPUsers_cursor_changed(self, widget): model, rows = widget.get_selection().get_selected_rows() self.btnPDelUser.set_sensitive(bool(rows)) # Server side options def add_option(self, name, value, supported, is_new=False, editable=True): option = options.OptionWidget(name, value, supported, self.option_changed) option.is_new = is_new rows = self.tblServerOptions.get_property("n-rows") self.tblServerOptions.resize(rows+1, 3) self.tblServerOptions.attach(option.label, 0, 1, rows, rows+1, xoptions=gtk.FILL, yoptions=gtk.FILL) option.label.set_alignment(0.0, 0.0) option.label.set_padding(5, 5) align = gtk.Alignment() align.add(option.selector) option.align = align self.tblServerOptions.attach(align, 1, 2, rows, rows+1, xoptions=gtk.FILL, yoptions=0) option.selector.set_sensitive(editable) if editable: # remove button btn = gtk.Button(stock="gtk-remove") btn.connect("clicked", self.removeOption_clicked) btn.set_data("pyobject", option) align = gtk.Alignment() align.add(btn) self.tblServerOptions.attach(align, 2, 3, rows, rows+1, xoptions=0, yoptions=gtk.FILL) option.remove_button = align self.server_side_options[name] = option if name in self.changed: # was deleted before option.is_new = False self.changed.discard(name) if option.is_changed(): self.changed.add(option) def removeOption_clicked(self, button): option = button.get_data("pyobject") self.tblServerOptions.remove(option.label) self.tblServerOptions.remove(option.align) self.tblServerOptions.remove(option.remove_button) if option.is_new: self.changed.discard(option) else: # keep name as reminder that option got deleted self.changed.add(option.name) del self.server_side_options[option.name] # re add to combobox if option.name in self.printer.possible_attributes: for nr, row in enumerate(self.cmbentNewOption.get_model()): if row[0] > option.name: self.cmbentNewOption.insert_text(nr, option.name) break self.setDataButtonState() def on_btnNewOption_clicked(self, button): name = self.cmbentNewOption.get_active_text() if name in self.printer.possible_attributes: value, supported = self.printer.possible_attributes[name] else: value, supported = "", "" self.add_option(name, value, supported, is_new=True) self.tblServerOptions.show_all() active = self.cmbentNewOption.get_active() if active >= 0: self.cmbentNewOption.remove_text(active) self.cmbentNewOption.set_active(-1) self.on_cmbentNewOption_changed(self.cmbentNewOption) self.setDataButtonState() def on_cmbentNewOption_changed(self, widget): text = self.cmbentNewOption.get_active_text() active = bool(text) and text not in self.server_side_options self.btnNewOption.set_sensitive(active) # set Apply/Revert buttons sensitive def setDataButtonState(self): for button in [self.btnApply, self.btnRevert]: button.set_sensitive(bool(self.changed) and not bool(self.conflicts)) try: # Might not be a printer selected if not self.test_button_cancels: self.btnPrintTestPage.set_sensitive (not bool (self.changed) and self.printer.enabled and not self.printer.rejecting) except: pass if self.conflicts: self.btnConflict.show() else: self.btnConflict.hide() def on_btnConflict_clicked(self, button): message = _("There are conflicting options.\n" "Changes can only be applied after\n" "these conflictes are resolved.") self.conflict_dialog.set_markup(message) self.conflict_dialog.run() self.conflict_dialog.hide() # Apply Changes def on_btnApply_clicked(self, widget): err = self.apply() if not err: self.populateList() else: pass # XXX def apply(self): name, type = self.getSelectedItem() if type in ("Printer", "Class"): return self.save_printer(self.printer) elif type == "Settings": return self.save_serversettings() def show_IPP_Error(self, exception, message): if exception == cups.IPP_NOT_AUTHORIZED: error_text = ('' + _('Not authorized') + '\n\n' + _('The password may be incorrect.')) else: error_text = ('' + _('CUPS server error') + '\n\n' + _("There was an error during the CUPS "\ "operation: '%s'.")) % message self.lblError.set_markup(error_text) self.ErrorDialog.set_transient_for (self.MainWindow) self.ErrorDialog.run() self.ErrorDialog.hide() def show_HTTP_Error(self, status): if (status == cups.HTTP_UNAUTHORIZED or status == cups.HTTP_FORBIDDEN): error_text = ('' + _('Not authorized') + '\n\n' + _('The password may be incorrect, or the ' 'server may be configured to deny ' 'remote administration.')) else: if status == cups.HTTP_BAD_REQUEST: msg = _("Bad request") elif status == cups.HTTP_NOT_FOUND: msg = _("Not found") elif status == cups.HTTP_REQUEST_TIMEOUT: msg = _("Request timeout") elif status == cups.HTTP_UPGRADE_REQUIRED: msg = _("Upgrade required") elif status == cups.HTTP_SERVER_ERROR: msg = _("Server error") else: msg = _("status %d") % status error_text = ('' + _('CUPS server error') + '\n\n' + _("There was an HTTP error: %s.")) % message self.lblError.set_markup(error_text) self.ErrorDialog.set_transient_for (self.MainWindow) self.ErrorDialog.run() self.ErrorDialog.hide() def save_printer(self, printer, saveall=False): name = printer.name try: if not printer.is_class and self.ppd: self.getPrinterSettings() if self.ppd.nondefaultsMarked() or saveall: self.passwd_retry = False # use cached Passwd self.cups.addPrinter(name, ppd=self.ppd) if printer.is_class: # update member list new_members = self.getCurrentClassMembers(self.tvClassMembers) if not new_members: dialog = gtk.MessageDialog( flags=0, type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_YES_NO, message_format=_("This will delete this class!")) dialog.format_secondary_text(_("Proceed anyway?")) result = dialog.run() dialog.destroy() if result==gtk.RESPONSE_NO: return True # update member list old_members = printer.class_members[:] for member in new_members: if member in old_members: old_members.remove(member) else: self.cups.addPrinterToClass(member, name) for member in old_members: self.cups.deletePrinterFromClass(member, name) location = self.entPLocation.get_text() info = self.entPDescription.get_text() device_uri = self.entPDevice.get_text() enabled = self.chkPEnabled.get_active() accepting = self.chkPAccepting.get_active() shared = self.chkPShared.get_active() if info!=printer.info or saveall: self.passwd_retry = False # use cached Passwd self.cups.setPrinterInfo(name, info) if location!=printer.location or saveall: self.passwd_retry = False # use cached Passwd self.cups.setPrinterLocation(name, location) if (not printer.is_class and (device_uri!=printer.device_uri or saveall)): self.passwd_retry = False # use cached Passwd self.cups.setPrinterDevice(name, device_uri) if enabled != printer.enabled or saveall: self.passwd_retry = False # use cached Passwd self.printer.setEnabled(enabled) if accepting == printer.rejecting or saveall: self.passwd_retry = False # use cached Passwd self.printer.setAccepting(accepting) if shared != printer.is_shared or saveall: self.passwd_retry = False # use cached Passwd self.printer.setShared(shared) job_sheet_start = self.cmbPStartBanner.get_active_text() job_sheet_end = self.cmbPEndBanner.get_active_text() error_policy = self.cmbPErrorPolicy.get_active_text() op_policy = self.cmbPOperationPolicy.get_active_text() if (job_sheet_start != printer.job_sheet_start or job_sheet_end != printer.job_sheet_end) or saveall: self.passwd_retry = False # use cached Passwd printer.setJobSheets(job_sheet_start, job_sheet_end) if error_policy != printer.error_policy or saveall: self.passwd_retry = False # use cached Passwd printer.setErrorPolicy(error_policy) if op_policy != printer.op_policy or saveall: self.passwd_retry = False # use cached Passwd printer.setOperationPolicy(op_policy) default_allow = self.rbtnPAllow.get_active() except_users = self.getPUsers() if (default_allow != printer.default_allow or except_users != printer.except_users) or saveall: self.passwd_retry = False # use cached Passwd printer.setAccess(default_allow, except_users) for option in printer.attributes: if option not in self.server_side_options: printer.unsetOption(option) for option in self.server_side_options.itervalues(): if option.is_changed or saveall: printer.setOption(option.name, option.get_current_value()) except cups.IPPError, (e, s): self.show_IPP_Error(e, s) return True self.changed = set() # of options self.populateList() return False def getPrinterSettings(self): #self.ppd.markDefaults() for option in self.options.itervalues(): option.writeback() # revert changes def on_btnRevert_clicked(self, button): self.changed = set() # avoid asking the user self.on_tvMainList_cursor_changed(self.tvMainList) # set default printer def on_btnPMakeDefault_clicked(self, button): try: self.cups.setDefault(self.printer.name) except cups.IPPError, (e, msg): self.show_IPP_Error(e, msg) self.populateList() # print test page def on_btnPrintTestPage_clicked(self, button): if self.test_button_cancels: jobs = self.printer.testsQueued () for job in jobs: print "Cancelling job %s" % job try: self.cups.cancelJob (job) except cups.IPPError, (e, msg): self.show_IPP_Error(e, msg) self.setTestButton (self.printer) return try: job_id = self.cups.printTestPage(self.printer.name) self.lblInfo.set_markup ('' + _("Submitted") + '\n\n' + _("Test page submitted as " "job %d") % job_id) self.InfoDialog.set_transient_for (self.MainWindow) self.setTestButton (self.printer) self.InfoDialog.run () self.InfoDialog.hide () except cups.IPPError, (e, msg): if (e == cups.IPP_NOT_AUTHORIZED and self.printer.name != 'localhost'): self.lblError.set_markup (''+ _("Not possible") + '\n\n' + _("The remote server did not accept " "the print job, most likely " "because the printer is not " "shared.")) self.ErrorDialog.set_transient_for (self.MainWindow) self.ErrorDialog.run () self.ErrorDialog.hide () else: self.show_IPP_Error(e, msg) # select Item def on_tvMainList_cursor_changed(self, list): if self.changed: response = self.ApplyDialog.run() self.ApplyDialog.hide() err = False if response == gtk.RESPONSE_APPLY: err = self.apply() if err or response == gtk.RESPONSE_CANCEL: self.tvMainList.get_selection().select_iter( self.mainListSelected) return name, type = self.getSelectedItem() model, self.mainListSelected = self.tvMainList.get_selection().get_selected() item_selected = True if type == "Settings": self.ntbkMain.set_current_page(0) if self.cups: self.fillServerTab() item_selected = False elif type in ['Printer', 'Class']: self.fillPrinterTab(name) self.ntbkMain.set_current_page(1) elif type == "None": self.ntbkMain.set_current_page(2) self.setDataButtonState() item_selected = False for widget in [self.copy, self.delete, self.btnCopy, self.btnDelete]: widget.set_sensitive(item_selected) def fillComboBox(self, combobox, values, value): combobox.get_model().clear() for nr, val in enumerate(values): combobox.append_text(val) if val == value: combobox.set_active(nr) def fillPrinterTab(self, name): self.changed = set() # of options self.options = {} # keyword -> Option object self.conflicts = set() # of options printer = self.printers[name] self.printer = printer editable = not self.printer.remote try: self.ppd = printer.getPPD() except cups.IPPError, (e, m): self.show_IPP_Error(e, m) self.ppd = False for widget in (self.entPDescription, self.entPLocation, self.entPDevice, self.btnSelectDevice, self.btnChangePPD, self.chkPEnabled, self.chkPAccepting, self.chkPShared, self.cmbPStartBanner, self.cmbPEndBanner, self.cmbPErrorPolicy, self.cmbPOperationPolicy, self.rbtnPAllow, self.rbtnPDeny, self.tvPUsers, self.entPUser, self.btnPAddUser, self.btnPDelUser, self.cmbentNewOption): widget.set_sensitive(editable) # Description page self.entPDescription.set_text(printer.info) self.entPLocation.set_text(printer.location) self.lblPMakeModel.set_text(printer.make_and_model) self.lblPState.set_text(printer.state_description) uri = printer.device_uri if uri.startswith("smb://"): group, host, share, user, password = self.parse_SMBURI(uri[6:]) if password: ellipsis = unichr(0x2026) uri = "smb://" if len (user) or len (password): uri += ellipsis uri += self.construct_SMBURI(group, host, share) self.entPDevice.set_sensitive(False) self.entPDevice.set_text(uri) self.changed.discard(self.entPDevice) # Hide make/model and Device URI for classes for widget in (self.lblPMakeModel2, self.lblPMakeModel, self.btnChangePPD, self.lblPDevice2, self.entPDevice, self.btnSelectDevice): if printer.is_class: widget.hide() else: widget.show() self.chkPEnabled.set_active(printer.enabled) self.chkPAccepting.set_active(not printer.rejecting) self.chkPShared.set_active(printer.is_shared) # default printer self.btnPMakeDefault.set_sensitive(not printer.default) if printer.default: self.lblPDefault.set_text(_("This is the default printer")) elif self.default_printer: self.lblPDefault.set_text(_("Default printer is %s") % self.default_printer) else: self.lblPDefault.set_text(_("No default printer set.")) self.setTestButton (printer) # Policy tab # ---------- # Job sheets self.fillComboBox(self.cmbPStartBanner, printer.job_sheets_supported, printer.job_sheet_start), self.fillComboBox(self.cmbPEndBanner, printer.job_sheets_supported, printer.job_sheet_end) self.cmbPStartBanner.set_sensitive(editable) self.cmbPEndBanner.set_sensitive(editable) # Policies self.fillComboBox(self.cmbPErrorPolicy, printer.error_policy_supported, printer.error_policy) self.fillComboBox(self.cmbPOperationPolicy, printer.op_policy_supported, printer.op_policy) self.cmbPErrorPolicy.set_sensitive(editable) self.cmbPOperationPolicy.set_sensitive(editable) # Access control self.rbtnPAllow.set_active(printer.default_allow) self.rbtnPDeny.set_active(not printer.default_allow) self.setPUsers(printer.except_users) self.entPUser.set_text("") # Server side options self.server_side_options = {} self.cmbentNewOption.get_model().clear() self.cmbentNewOption.get_child().set_text("") self.btnNewOption.set_sensitive(False) attrs = self.printer.possible_attributes.keys() attrs.sort() for attr in attrs: if attr not in self.printer.attributes: self.cmbentNewOption.append_text(attr) self.tblServerOptions.resize(1, 3) for child in self.tblServerOptions.get_children(): self.tblServerOptions.remove(child) attrs = printer.attributes.keys() attrs.sort() for attr in attrs: value = printer.attributes[attr] supported = printer.possible_attributes[attr][1] self.add_option(attr, value, supported, is_new=False, editable=editable) self.tblServerOptions.show_all() self.tblServerOptions.queue_draw() if printer.is_class: # remove InstallOptions tab tab_nr = self.ntbkPrinter.page_num(self.swPInstallOptions) if tab_nr != -1: self.ntbkPrinter.remove_page(tab_nr) self.fillClassMembers(name, editable) else: # real Printer self.fillPrinterOptions(name, editable) self.setDataButtonState() def setTestButton (self, printer): if printer.testsQueued (): self.test_button_cancels = True self.btnPrintTestPage.set_label (_('Cancel Tests')) self.btnPrintTestPage.set_sensitive (True) else: self.test_button_cancels = False self.btnPrintTestPage.set_label (_('Print Test Page')) self.setDataButtonState () def fillPrinterOptions(self, name, editable): # remove Class membership tab tab_nr = self.ntbkPrinter.page_num(self.algnClassMembers) if tab_nr != -1: self.ntbkPrinter.remove_page(tab_nr) # clean Installable Options Tab for widget in self.vbPInstallOptions.get_children(): self.vbPInstallOptions.remove(widget) # clean Options Tab for widget in self.vbPOptions.get_children(): self.vbPOptions.remove(widget) # insert Options Tab if self.ntbkPrinter.page_num(self.swPOptions) == -1: self.ntbkPrinter.insert_page( self.swPOptions, self.lblPOptions, self.static_tabs) if not self.ppd: tab_nr = self.ntbkPrinter.page_num(self.swPInstallOptions) if tab_nr != -1: self.ntbkPrinter.remove_page(tab_nr) tab_nr = self.ntbkPrinter.page_num(self.swPOptions) if tab_nr != -1: self.ntbkPrinter.remove_page(tab_nr) return ppd = self.ppd ppd.markDefaults() hasInstallableOptions = False # build option tabs for group in ppd.optionGroups: if group.name == "InstallableOptions": hasInstallableOptions = True container = self.vbPInstallOptions tab_nr = self.ntbkPrinter.page_num(self.swPInstallOptions) if tab_nr == -1: self.ntbkPrinter.insert_page( self.swPInstallOptions, gtk.Label(group.text), self.static_tabs) else: frame = gtk.Frame("%s" % group.text) frame.get_label_widget().set_use_markup(True) frame.set_shadow_type (gtk.SHADOW_NONE) self.vbPOptions.pack_start (frame, False, False, 0) container = gtk.Alignment (0.5, 0.5, 1.0, 1.0) container.set_padding (0, 0, 12, 0) frame.add (container) table = gtk.Table(1, 3, False) table.set_col_spacing(0, 6) container.add(table) rows = 0 for nr, option in enumerate(group.options): if option.keyword == "PageRegion": continue rows += 1 table.resize (rows, 3) o = OptionWidget(option, ppd, self) table.attach(o.conflictIcon, 0, 1, nr, nr+1, 0, 0, 0, 0) hbox = gtk.HBox() if o.label: a = gtk.Alignment (0.5, 0.5, 1.0, 1.0) a.set_padding (0, 0, 0, 6) a.add (o.label) table.attach(a, 1, 2, nr, nr+1, gtk.FILL, 0, 0, 0) table.attach(hbox, 2, 3, nr, nr+1, gtk.FILL, 0, 0, 0) else: table.attach(hbox, 1, 3, nr, nr+1, gtk.FILL, 0, 0, 0) hbox.pack_start(o.selector, False) self.options[option.keyword] = o o.selector.set_sensitive(editable) # remove Installable Options tab if not needed if not hasInstallableOptions: tab_nr = self.ntbkPrinter.page_num(self.swPInstallOptions) if tab_nr != -1: self.ntbkPrinter.remove_page(tab_nr) # check for conflicts for option in self.options.itervalues(): conflicts = option.checkConflicts() if conflicts: self.conflicts.add(option) self.swPInstallOptions.show_all() self.swPOptions.show_all() # Class members def fillClassMembers(self, name, editable): printer = self.printers[name] self.btnClassAddMember.set_sensitive(editable) self.btnClassDelMember.set_sensitive(editable) # remove Options tab tab_nr = self.ntbkPrinter.page_num(self.swPOptions) if tab_nr != -1: self.ntbkPrinter.remove_page(tab_nr) # insert Member Tab if self.ntbkPrinter.page_num(self.algnClassMembers) == -1: self.ntbkPrinter.insert_page( self.algnClassMembers, self.lblClassMembers, self.static_tabs) model_members = self.tvClassMembers.get_model() model_not_members = self.tvClassNotMembers.get_model() model_members.clear() model_not_members.clear() names = self.printers.keys() names.sort() for name in names: p = self.printers[name] if p is not printer: if name in printer.class_members: model_members.append((name, )) else: model_not_members.append((name, )) def on_btnClassAddMember_clicked(self, button): self.moveClassMembers(self.tvClassNotMembers, self.tvClassMembers) if self.getCurrentClassMembers(self.tvClassMembers) != self.printer.class_members: self.changed.add(self.tvClassMembers) else: self.changed.discard(self.tvClassMembers) self.setDataButtonState() def on_btnClassDelMember_clicked(self, button): self.moveClassMembers(self.tvClassMembers, self.tvClassNotMembers) if self.getCurrentClassMembers(self.tvClassMembers) != self.printer.class_members: self.changed.add(self.tvClassMembers) else: self.changed.discard(self.tvClassMembers) self.setDataButtonState() def moveClassMembers(self, treeview_from, treeview_to): selection = treeview_from.get_selection() model_from, rows = selection.get_selected_rows() rows = [gtk.TreeRowReference(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(self, treeview): model = treeview.get_model() iter = model.get_iter_root() result = [] while iter: result.append(model.get(iter, 0)[0]) iter = model.iter_next(iter) result.sort() return result # Quit def on_quit_activate(self, widget, event=None): # check for unapplied changes if self.changed: response = self.ApplyDialog.run() self.ApplyDialog.hide() err = False if response == gtk.RESPONSE_APPLY: err = self.apply() if err or response == gtk.RESPONSE_CANCEL: return gtk.main_quit() # Copy def on_copy_activate(self, widget): # check for unapplied changes if self.changed: response = self.ApplyDialog.run() self.ApplyDialog.hide() err = False if response == gtk.RESPONSE_REJECT: self.changed = set() # avoid asking the user self.on_tvMainList_cursor_changed(self.tvMainList) elif response == gtk.RESPONSE_APPLY: err = self.apply() if err or response == gtk.RESPONSE_CANCEL: return self.entCopyName.set_text(self.printer.name) result = self.NewPrinterName.run() self.NewPrinterName.hide() if result == gtk.RESPONSE_CANCEL: return self.printer.name = self.entCopyName.get_text() self.printer.class_members = [] # for classes make shure all members # will get added self.save_printer(self.printer, saveall=True) self.populateList() def on_entCopyName_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) self.btnCopyOk.set_sensitive( self.check_NPName(new_text)) # Delete def on_delete_activate(self, widget): name, type = self.getSelectedItem() # Confirm dialog = gtk.MessageDialog( self.MainWindow, buttons=gtk.BUTTONS_OK_CANCEL, message_format=_("Really delete %s %s?") % (_(type), name)) result = dialog.run() dialog.destroy() if result == gtk.RESPONSE_CANCEL: return try: self.cups.deletePrinter(name) except cups.IPPError, (e, msg): self.show_IPP_Error(e, msg) self.changed = set() self.populateList() # About dialog def on_about_activate(self, widget): self.AboutDialog.run() self.AboutDialog.hide() # ==================================================================== # == New Printer Dialog ============================================== # ==================================================================== new_printer_device_tabs = { "parallel" : 0, # empty tab "usb" : 0, "hal" : 0, "beh" : 0, "hp" : 0, "socket": 2, "ipp" : 3, "http" : 3, "lpd" : 4, "scsi" : 5, "serial" : 6, "smb" : 7, } # new printer def on_new_printer_activate(self, widget): self.dialog_mode = "printer" self.NewPrinterWindow.set_title(_("New Printer")) self.on_rbtnNPFoomatic_toggled(self.rbtnNPFoomatic) self.initNewPrinterWindow() # new class def on_new_class_activate(self, widget): self.dialog_mode = "class" self.NewPrinterWindow.set_title(_("New Class")) self.fillNewClassMembers() self.initNewPrinterWindow() # change device def on_btnSelectDevice_clicked(self, button): self.busy (self.MainWindow) self.loadFoomatic() self.dialog_mode = "device" self.initNewPrinterWindow() self.NewPrinterWindow.set_title(_("Change Device URI")) self.ntbkNewPrinter.set_current_page(1) self.fillDeviceTab(self.printer.device_uri) self.initNewPrinterWindow() self.ready (self.MainWindow) # change PPD def on_btnChangePPD_clicked(self, button): self.busy (self.MainWindow) self.loadFoomatic() self.dialog_mode = "ppd" self.initNewPrinterWindow() self.NewPrinterWindow.set_title(_("Change Driver")) self.ntbkNewPrinter.set_current_page(2) self.on_rbtnNPFoomatic_toggled(self.rbtnNPFoomatic) self.auto_model = "" if self.ppd: attr = self.ppd.findAttr("Manufacturer") if attr: self.auto_make = attr.value else: self.auto_make = "" attr = self.ppd.findAttr("ModelName") if not attr: attr = self.ppd.findAttr("ShortNickName") if not attr: attr = self.ppd.findAttr("NickName") if attr: if attr.value.startswith(self.auto_make): self.auto_model = attr.value[len(self.auto_make):].strip () else: try: self.auto_model = attr.value.split(" ", 1)[1] except IndexError: self.auto_model = "" else: self.auto_model = "" else: # Special CUPS names for a raw queue. self.auto_make = 'Raw' self.auto_model = 'Queue' self.fillMakeList() self.initNewPrinterWindow() self.ready (self.MainWindow) def initNewPrinterWindow(self): if self.dialog_mode in ("printer", "class"): self.ntbkNewPrinter.set_current_page(0) self.entNPName.set_text ('printer') self.entNPName.grab_focus() for widget in [self.entNPLocation, self.entNPDescription, self.entSMBURI, self.entSMBUsername, self.entSMBPassword, self.entNPTDirectJetHostname]: widget.set_text('') self.entNPTDirectJetPort.set_text('9100') self.setNPButtons() self.NewPrinterWindow.set_transient_for(self.MainWindow) self.NewPrinterWindow.show() # Class members def fillNewClassMembers(self): model = self.tvNCMembers.get_model() model.clear() model = self.tvNCNotMembers.get_model() model.clear() for printer in self.printers.itervalues(): model.append((printer.name,)) def on_btnNCAddMember_clicked(self, button): self.moveClassMembers(self.tvNCNotMembers, self.tvNCMembers) self.btnNPForward.set_sensitive( bool(self.getCurrentClassMembers(self.tvNCMembers))) def on_btnNCDelMember_clicked(self, button): self.moveClassMembers(self.tvNCMembers, self.tvNCNotMembers) self.btnNPForward.set_sensitive( bool(self.getCurrentClassMembers(self.tvNCMembers))) # Navigation buttons def on_NPCancel(self, widget, event=None): self.NewPrinterWindow.hide() return True def on_btnNPBack_clicked(self, widget): self.nextNPTab(-1) def on_btnNPForward_clicked(self, widget): self.nextNPTab() def nextNPTab(self, step=1): page_nr = self.ntbkNewPrinter.get_current_page() if self.dialog_mode == "class": order = [0, 4, 5] elif self.dialog_mode == "printer": self.busy (self.NewPrinterWindow) self.loadFoomatic() if page_nr == 0: self.fillDeviceTab() self.fillMakeList() self.ready (self.NewPrinterWindow) if self.rbtnNPFoomatic.get_active(): order = [0, 1, 2, 3, 5] else: order = [0, 1, 2, 5] elif self.dialog_mode == "device": order = [1] elif self.dialog_mode == "ppd": if self.rbtnNPFoomatic.get_active(): order = [2, 3, 6] else: order = [2, 6] page_nr = self.ntbkNewPrinter.set_current_page( order[order.index(page_nr)+step]) self.setNPButtons() def setNPButtons(self): nr = self.ntbkNewPrinter.get_current_page() if self.dialog_mode == "device": self.btnNPBack.hide() self.btnNPForward.hide() self.btnNPApply.show() return if self.dialog_mode == "ppd": if nr == 6: # Apply self.btnNPForward.hide() self.btnNPApply.show() return else: self.btnNPForward.show() self.btnNPApply.hide() if nr == 2: self.btnNPBack.hide() self.btnNPForward.show() self.btnNPForward.set_sensitive(True) return else: self.btnNPBack.show() # class/printer if nr == 0: # name self.btnNPBack.hide() self.btnNPForward.set_sensitive( self.check_NPName(self.entNPName.get_text())) else: self.btnNPBack.show() if nr == 1: # Device pass if nr == 2: # Make/PPD file self.btnNPForward.set_sensitive(bool( self.rbtnNPFoomatic.get_active() or self.filechooserPPD.get_filename())) if nr == 3: # Model/Driver model, iter = self.tvNPDrivers.get_selection().get_selected() self.btnNPForward.set_sensitive(bool(iter)) if nr == 4: # Class Members self.btnNPForward.set_sensitive( bool(self.getCurrentClassMembers(self.tvNCMembers))) if nr == 5: # Apply self.btnNPForward.hide() self.btnNPApply.show() self.fillNPApply() else: self.btnNPForward.show() self.btnNPApply.hide() def check_NPName(self, name): if not name: return False name = name.lower() for printer in self.printers.values(): if not printer.remote and printer.name.lower()==name: return False return True 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) self.btnNPForward.set_sensitive( self.check_NPName(new_text)) # Device URI def fillDeviceTab(self, current_uri=None): try: devices = cupshelpers.getDevices(self.cups, current_uri) except cups.IPPError, (e, msg): self.show_IPP_Error(e, msg) devices = {} if current_uri: current = devices.pop(current_uri) devices = devices.values() devices.sort() self.devices = filter(lambda x: x.uri not in ("hp:/no_device_found", "hpfax:/no_device_found", "hal", "beh", "scsi", "http"), devices) self.devices.append(cupshelpers.Device('', **{'device-info' :_("Other")})) if current_uri: current.info += _(" (Current)") self.devices.insert(0, current) model = self.tvNPDevices.get_model() model.clear() for device in self.devices: model.append((device.info,)) self.tvNPDevices.get_selection().select_path(0) self.on_tvNPDevices_cursor_changed(self.tvNPDevices) def browse_smb_hosts (self): """Initialise the SMB tree store.""" store = self.smb_store store.clear () self.busy(self.NewPrinterWindow) iter = None domains = pysmb.get_domain_list () for domain in domains.keys (): d = domains[domain] iter = store.append (None) store.set_value (iter, 0, d['DOMAIN']) store.set_value (iter, 2, d) if iter: dummy = store.append (iter) self.ready(self.NewPrinterWindow) def smb_select_function (self, path): """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 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) if store.iter_depth (iter) == 2: # This is a share, not a host. 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.""" store = self.smb_store if len (path) == 2: # Click on host, look for shares try: if self.expanding_row: return except: self.expanding_row = 1 host = store.get_value (iter, 3) if host: self.busy (self.NewPrinterWindow) printers = pysmb.get_printer_list (host) while store.iter_has_child (iter): i = store.iter_nth_child (iter, 0) store.remove (i) for printer in printers.keys(): i = store.append (iter) store.set_value (i, 0, printer) store.set_value (i, 1, printers[printer]) self.ready (self.NewPrinterWindow) view.expand_row (path, 1) del self.expanding_row else: # Click on domain, look for hosts try: if self.expanding_row: return except: self.expanding_row = 1 domain = store.get_value (iter, 2) if domain: self.busy (self.NewPrinterWindow) hosts = pysmb.get_host_list (domain['IP']) while store.iter_has_child (iter): i = store.iter_nth_child (iter, 0) store.remove (i) i = None for host in hosts.keys(): h = hosts[host] i = store.append (iter) store.set_value (i, 0, h['NAME']) store.set_value (i, 3, h) if i: dummy = store.append (i) self.ready (self.NewPrinterWindow) view.expand_row (path, 0) del self.expanding_row def parse_SMBURI (self, uri): user = '' password = '' auth = uri.find ('@') if auth != -1: u = uri[:auth].find(':') if u != -1: user = uri[:u] password = uri[u + 1:auth] else: user = uri[:auth] uri = uri[auth + 1:] sep = uri.count ('/') group = '' if sep == 2: g = uri.find('/') group = uri[:g] uri = uri[g + 1:] if sep < 1: host = 'localhost' else: h = uri.find('/') host = uri[:h] uri = uri[h + 1:] p = host.find(':') if p != -1: host = host[:p] share = uri return (group, host, share, percentDecode (user), percentDecode (password)) def construct_SMBURI (self, group, host, share, user = '', password = ''): uri_password = '' if password: uri_password = ':' + percentEncode (password) if user: uri_password += '@' return "%s%s%s/%s/%s" % (percentEncode (user), uri_password, group, host, share) def on_entSMBURI_changed (self, ent): try: if self.ignore_signals: return except: pass uri = ent.get_text () (group, host, share, user, password) = self.parse_SMBURI (uri) if user: self.entSMBUsername.set_text (user) if password: self.entSMBPassword.set_text (password) self.tvSMBBrowser.get_selection ().unselect_all () if user or password: ent.set_text(self.construct_SMBURI(group, host, share)) def on_tvSMBBrowser_cursor_changed (self, view): store, iter = view.get_selection ().get_selected () if not iter: return parent_iter = store.iter_parent (iter) domain_iter = store.iter_parent (parent_iter) group = store.get_value (domain_iter, 0) host = store.get_value (parent_iter, 0) share = store.get_value (iter, 0) uri = self.construct_SMBURI (group, host, share) self.ignore_signals = True # Avoid 'changed' signal from Entry self.entSMBURI.set_text (uri) del self.ignore_signals def on_btnSMBVerify_clicked(self, button): uri = self.entSMBURI.get_text () (group, host, share, u, p) = self.parse_SMBURI (uri) user = self.entSMBUsername.get_text () passwd = self.entSMBPassword.get_text () accessible = pysmb.printer_share_accessible ("//%s/%s" % (host, share), group = group, user = user, passwd = passwd) if accessible: self.lblInfo.set_markup ('' + _("Verified") + '\n\n' + _("This print share is accessible.")) self.InfoDialog.set_transient_for (self.NewPrinterWindow) self.InfoDialog.run() self.InfoDialog.hide () return self.lblError.set_markup ('' + _("Inaccessible") + '\n\n' + _("This print share is not accessible.")) self.ErrorDialog.set_transient_for (self.NewPrinterWindow) self.ErrorDialog.run() self.ErrorDialog.hide () def on_tvNPDevices_cursor_changed(self, widget): model, iter = widget.get_selection().get_selected() path = model.get_path(iter) device = self.devices[path[0]] self.device = device self.ntbkNPType.set_current_page( self.new_printer_device_tabs.get(device.type, 1)) type = device.type url = device.uri.split(":", 1)[-1] if device.type=="serial": if not device.is_class: options = device.uri.split("?")[1] options = options.split("+") option_dict = {} for option in options: name, value = option.split("=") option_dict[name] = value for widget, name, optionvalues in ( (self.cmbNPTSerialBaud, "baud", None), (self.cmbNPTSerialBits, "bits", None), (self.cmbNPTSerialParity, "parity", ["none", "odd", "even"]), (self.cmbNPTSerialFlow, "flow", ["none", "soft", "hard", "hard"])): if option_dict.has_key(name): # option given in URI? if optionvalues is None: # use text in widget model = widget.get_model() iter = model.get_iter_first() nr = 0 while iter: value = model.get(iter,0)[0] if value == option_dict[name]: widget.set_active(nr) break iter = model.iter_next(iter) nr += 1 else: # use optionvalues nr = optionvalues.index( option_dict[name]) widget.set_active(nr+1) # compensate "Default" else: widget.set_active(0) # XXX FILL TABS FOR VALID DEVICE URIs elif device.type in ("ipp", "http"): server = "" printer = "" if url[:2] == "//": p = url[2:] t = p.find ('/') if t != -1: server = p[:t] p = p[t + 1:] # Skip over 'printers/' or 'classes/' t = p.find ('/') if t != -1: printer = p[t + 1:] self.entNPTIPPHostname.set_text(server) self.entNPTIPPPrintername.set_text(printer) elif device.type == "lpd": pass # XXX elif device.uri == "smb": self.entSMBURI.set_text('') self.browse_smb_hosts () elif device.type == "smb": self.entSMBURI.set_text(device.uri[6:]) else: self.entNPTDevice.set_text(device.uri) self.auto_make, self.auto_model = None, None printer_name = self.foomatic.getPrinterFromCupsDevice(self.device) if printer_name: printer = self.foomatic.getPrinter(printer_name) if printer: self.auto_make, self.auto_model = printer.make, printer.model self.fillMakeList() def on_btnNPTLpdProbe_clicked(self, button): # read hostname, probe, fill printer names hostname = self.cmbentNPTLpdHost.get_active_text() server = probe_printer.LpdServer(hostname) printers = server.probe() model = self.cmbentNPTLpdQueue.get_model() model.clear() for printer in printers: self.cmbentNPTLpdQueue.append_text(printer) if printers: self.cmbentNPTLpdQueue.set_active(0) def getDeviceURI(self): type = self.device.type if type == "socket": # DirectJet host = self.entNPTDirectJetHostname.get_text() port = self.entNPTDirectJetPort.get_text() device = "socket://" + host if port: device = device + ':' + port elif type in ("http", "ipp"): # IPP host = self.entNPTIPPHostname.get_text() printer = self.entNPTIPPPrintername.get_text() device = "ipp://" + host if printer: device = device + "/printers/" + printer elif type == "lpd": # LPD host = self.cmbentNPTLpdHost.get_active_text() printer = self.cmbentNPTLpdQueue.get_active_text() device = "lpd://" + host if printer: device = device + "/" + printer elif type == "parallel": # Parallel device = self.device.uri elif type == "scsi": # SCSII device = "" elif type == "serial": # Serial options = [] for widget, name, optionvalues in ( (self.cmbNPTSerialBaud, "baud", None), (self.cmbNPTSerialBits, "bits", None), (self.cmbNPTSerialParity, "parity", ("none", "odd", "even")), (self.cmbNPTSerialFlow, "flow", ("none", "soft", "hard", "hard"))): nr = widget.get_active() if nr: if optionvalues is not None: option = optionvalues[nr-1] else: option = widget.get_active_text() 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) = self.parse_SMBURI (uri) user = self.entSMBUsername.get_text () password = self.entSMBPassword.get_text () uri = self.construct_SMBURI (group, host, share, user, password) device = "smb://" + uri elif not self.device.is_class: device = self.device.uri else: device = self.entNPTDevice.get_text() return device # PPD def on_rbtnNPFoomatic_toggled(self, widget): foo = self.rbtnNPFoomatic.get_active() self.tvNPMakes.set_sensitive(foo) self.filechooserPPD.set_sensitive(not foo) self.setNPButtons() def on_filechooserPPD_selection_changed(self, widget): self.setNPButtons() # PPD from foomatic def fillMakeList(self): makes = self.foomatic.getMakes() model = self.tvNPMakes.get_model() model.clear() found = False for make in makes: iter = model.append((make,)) if make==self.auto_make: self.tvNPMakes.get_selection().select_iter(iter) path = model.get_path(iter) self.tvNPMakes.scroll_to_cell(path, None, True, 0.5, 0.5) found = True if not found: self.tvNPMakes.get_selection().select_path(0) self.tvNPMakes.scroll_to_cell(0, None, True, 0.0, 0.0) self.on_tvNPMakes_cursor_changed(self.tvNPMakes) #tree = BuildTree(names, 3, 3) #tree.name = maker #self._fillPPDList(None, tree) #names = [] #for printername in self.foomatic.get_printers(): # printer = self.foomatic.get_printer(printername) # names.append(printer.make + " " + printer.model) #names.sort(cups.modelSort) #tree = BuildTree(names, mindepth=3, minwidth=3) #self._fillPPDList(None, tree) #def _fillPPDList(self, iter, treenode): # if treenode.name: # iter = self.tvNPDriversModel.append(iter, (treenode.name,)) # for leaf in treenode.leafs: # self._fillPPDList(iter, leaf) def on_tvNPMakes_cursor_changed(self, widget): model, iter = self.tvNPMakes.get_selection().get_selected() self.NPMake = model.get(iter, 0)[0] self.fillModelList() def fillModelList(self): models = self.foomatic.getModels(self.NPMake) model = self.tvNPModels.get_model() model.clear() selected = False for pmodel in models: iter = model.append((pmodel,)) if self.NPMake==self.auto_make and pmodel==self.auto_model: path = model.get_path(iter) self.tvNPModels.scroll_to_cell(path, None, True, 0.5, 0.5) self.tvNPModels.get_selection().select_iter(iter) selected = True if not selected: self.tvNPModels.get_selection().select_path(0) self.tvNPModels.scroll_to_cell(0, None, True, 0.0, 0.0) self.on_tvNPModels_cursor_changed(self.tvNPModels) def fillDriverList(self, printer): model = self.tvNPDrivers.get_model() model.clear() self.NPDrivers = printer.drivers.keys() self.NPDrivers.sort() found = False remote_driver="foomatic:%s-%s.ppd" % (printer.name, printer.driver) for driver in self.NPDrivers: if (driver==printer.driver or ((not found) and driver == remote_driver)): 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.5) found = True else: model.append((driver, )) if not found: self.tvNPDrivers.get_selection().select_path(0) def on_tvNPModels_cursor_changed(self, widget): model, iter = widget.get_selection().get_selected() pmodel = model.get(iter, 0)[0] printer = self.foomatic.getMakeModel(self.NPMake, pmodel) self.NPModel = pmodel self.fillDriverList(printer) if self.frmNPPDescription.flags() & gtk.VISIBLE: self.lblNPPDescription.set_markup(printer.getCommentPango( self.language, "en")) self.on_tvNPDrivers_cursor_changed(self.tvNPDrivers) def on_tvNPDrivers_cursor_changed(self, widget): if ((self.frmNPDDescription.flags() & gtk.VISIBLE) or (self.frmNPPPDDescription.flags() & gtk.VISIBLE)): model, iter = widget.get_selection().get_selected() if iter is None: self.lblNPDDescription.set_markup('') self.lblNPPPDDescription.set_markup('') else: nr = model.get_path(iter)[0] drivername = self.NPDrivers[nr] if self.frmNPDDescription.flags() & gtk.VISIBLE: driver = self.foomatic.getDriver (drivername) self.lblNPDDescription.set_markup( driver.getCommentPango(self.language, "en")) if self.frmNPPPDDescription.flags() & gtk.VISIBLE: printer = self.foomatic.getMakeModel (self.NPMake, self.NPModel) ppd = printer.drivers[drivername] if (not ppd or ppd.startswith ("foomatic:")): markup = _("This PPD is generated by foomatic.") elif (ppd.startswith ("foomatic-db-ppds/") or ppd.startswith ("PPD/")): markup = _("This PPD is provided by the manufacturer " "and is included with the foomatic " "package.") else: try: fppd = self.foomatic.ppds[ppd] markup = fppd['ppd-make-and-model'] + '\n' except KeyError: markup += _("This PPD is provided by CUPS.") self.lblNPPPDDescription.set_markup (markup) self.setNPButtons() # toggle Comments def on_tglNPShowPrinterDescription_toggled(self, widget): if widget.get_active(): self.frmNPPDescription.show() self.on_tvNPModels_cursor_changed(self.tvNPModels) else: self.frmNPPDescription.hide() def on_tglNPShowDriverDescription_toggled(self, widget): if widget.get_active(): self.frmNPDDescription.show() self.on_tvNPDrivers_cursor_changed(self.tvNPDrivers) else: self.frmNPDDescription.hide() def on_tglNPShowPPDInfo_toggled(self, widget): if widget.get_active(): self.frmNPPPDDescription.show() self.on_tvNPDrivers_cursor_changed(self.tvNPDrivers) else: self.frmNPPPDDescription.hide() def getNPPPD(self): if self.rbtnNPFoomatic.get_active(): model, iter = self.tvNPDrivers.get_selection().get_selected() nr = model.get_path(iter)[0] driver = self.NPDrivers[nr] printer = self.foomatic.getMakeModel(self.NPMake, self.NPModel) return printer.getPPD(driver) else: return cups.PPD(self.filechooserPPD.get_filename()) def fillNPApply(self): name = self.entNPName.get_text() if self.dialog_mode=="class": # XXX msg = _("Going to create a new class %s.") % name else: # XXX uri = self.getDeviceURI() # Don't reveal SMB authentication details. if uri[:6] == "smb://": (group, host, share, u, p) = self.parse_SMBURI (uri[6:]) ellipsis = unichr(0x2026) uri = "smb://" if len (u) or len (p): uri += ellipsis uri += self.construct_SMBURI (group, host, share) msg = _( """Going to create a new printer %s at %s. """ ) % (name, uri) self.lblNPApply.set_markup(msg) # Create new Printer def on_btnNPApply_clicked(self, widget): if self.dialog_mode in ("class", "printer"): name = self.entNPName.get_text() location = self.entNPLocation.get_text() info = self.entNPDescription.get_text() else: name = self.printer.name # Whether to check for missing drivers. check = False checkppd = None DBErr_title = _('Database error') DBErr_text = _("The '%s' driver cannot be used with printer '%s %s'.") def get_PPD_but_handle_errors (): try: ppd = self.getNPPPD() except RuntimeError, e: 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. DBErr = _("You will need to install the '%s' package " "in order to use this driver.") % \ "gutenprint-foomatic" else: DBErr = DBErr_text % (driver, self.NPMake, self.NPModel) error_text = ('' + DBErr_title + '\n\n' + DBErr) self.lblError.set_markup(error_text) self.ErrorDialog.set_transient_for(self.NewPrinterWindow) self.ErrorDialog.run() self.ErrorDialog.hide() return None return ppd if self.dialog_mode=="class": members = self.getCurrentClassMembers(self.tvNCMembers) try: for member in members: self.passwd_retry = False # use cached Passwd self.cups.addPrinterToClass(member, name) except cups.IPPError, (e, msg): self.show_IPP_Error(e, msg) return elif self.dialog_mode=="printer": uri = self.getDeviceURI() ppd = get_PPD_but_handle_errors () if not ppd: # Go back to previous page to re-select driver. self.nextNPTab(-1) return try: self.passwd_retry = False # use cached Passwd if isinstance(ppd, str) or isinstance(ppd, unicode): self.cups.addPrinter(name, ppdname=ppd, device=uri, info=info, location=location) check = True elif ppd is None: # raw queue self.cups.addPrinter(name, device=uri, info=info, location=location) else: cupshelpers.setPPDPageSize(ppd, self.language[0]) self.cups.addPrinter(name, ppd=ppd, device=uri, info=info, location=location) check = True checkppd = ppd self.cups.enablePrinter (name) self.cups.acceptJobs (name) except cups.IPPError, (e, msg): self.show_IPP_Error(e, msg) return if self.dialog_mode in ("class", "printer"): try: self.passwd_retry = False # use cached Passwd self.cups.setPrinterLocation(name, location) self.passwd_retry = False # use cached Passwd self.cups.setPrinterInfo(name, info) except cups.IPPError, (e, msg): self.show_IPP_Error(e, msg) return elif self.dialog_mode == "device": try: uri = self.getDeviceURI() self.passwd_retry = False # use cached Passwd self.cups.addPrinter(name, device=uri) except cups.IPPError, (e, msg): self.show_IPP_Error(e, msg) return elif self.dialog_mode == "ppd": ppd = get_PPD_but_handle_errors () if not ppd: # Go back to previous page to re-select driver. self.nextNPTab(-1) return # set ppd on server and retrieve it # cups doesn't offer a way to just download a ppd ;(= raw = False if isinstance(ppd, str) or isinstance(ppd, unicode): 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.passwd_retry = False # use cached Passwd self.cups.addPrinter(name, ppdname='raw') except cups.IPPError, (e, msg): self.show_IPP_Error(e, msg) try: self.passwd_retry = False # use cached Passwd self.cups.addPrinter(name, ppdname=ppd) except cups.IPPError, (e, msg): self.show_IPP_Error(e, msg) return try: self.passwd_retry = False # use cached Passwd filename = self.cups.getPPD(name) ppd = cups.PPD(filename) os.unlink(filename) except cups.IPPError, (e, msg): if e == cups.IPP_NOT_FOUND: raw = True else: self.show_IPP_Error(e, msg) return else: # We have an actual PPD to upload, not just a name. if not self.rbtnChangePPDasIs.get_active(): cupshelpers.copyPPDOptions(self.ppd, ppd) else: cupshelpers.setPPDPageSize(ppd, self.language[0]) try: self.passwd_retry = False # use cached Passwd self.cups.addPrinter(name, ppd=ppd) except cups.IPPError, (e, msg): self.show_IPP_Error(e, msg) if not raw: check = True checkppd = ppd self.NewPrinterWindow.hide() self.populateList() if check: self.checkDriverExists (name, ppd=checkppd) def checkDriverExists(self, name, ppd=None): """Check that the driver for an existing queue actually exists, and prompt to install the appropriate package if not. ppd: cups.PPD object, if already created""" # Is this queue on the local machine? If not, we can't check # anything at all. server = cups.getServer () if not (server == 'localhost' or server == '127.0.0.1' or server == '::1' or server[0] == '/'): return # Fetch the PPD if we haven't already. if not ppd: try: filename = self.cups.getPPD(name) except cups.IPPError, (e, msg): if e == cups.IPP_NOT_FOUND: # This is a raw queue. Nothing to check. return else: self.show_IPP_Error(e, msg) return ppd = cups.PPD(filename) os.unlink(filename) # How to check that something exists in a path: def pathcheck (name, path="/usr/bin:/bin"): if name[0] == '/': if os.access (file, os.X_OK): print "%s: found" % file return file else: print "%s: NOT found" % file return None for component in path.split (':'): file = component.rstrip (os.path.sep) + os.path.sep + name if os.access (file, os.X_OK): print "%s: found" % file return file print "%s: NOT found in %s" % (name,path) return None # Find a 'FoomaticRIPCommandLine' attribute. exe = None attr = ppd.findAttr ('FoomaticRIPCommandLine') if attr: # Foomatic RIP command line to check. cmdline = attr.value.replace ('&&\n', '') args = cmdline.split (' ') argn = len (args) argi = 1 exe = args[0] exepath = pathcheck (exe) if exepath: # Main executable found. But if it's 'gs', perhaps there is # an IJS server we also need to check. if os.path.basename (exepath) == 'gs': search = "-sIjsServer=" while argi < argn: arg = args[argi] if arg == '|': break if arg.startswith (search): exe = arg[len (search):] # Strip out foomatic '%'-style place-holders p = exe.find ('%') if p != -1: exe = exe[:p] exepath = pathcheck (exe) break argi += 1 # If that was found, is there a pipeline? while exepath: pipe = 0 while argi < argn - 1: if args[argi] == '|': pipe = 1 break argi += 1 if pipe == 0: break argi += 1 exe = args[argi] # Strip out foomatic '%'-style place-holders p = exe.find ('%') if p != -1: exe = exe[:p] exepath = pathcheck (exe) # Look for '*cupsFilter' lines in the PPD and check that the filters # are installed. (tmpfd, tmpfname) = tempfile.mkstemp () ppd.writeFd (tmpfd) search = "*cupsFilter:" for line in file (tmpfname).readlines (): if line.startswith (search): line = line[len (search):].strip ().strip ('"') try: (mimetype, cost, exe) = line.split (' ') except: continue exepath = pathcheck (exe, "/usr/lib/cups/filter:" "/usr/lib64/cups/filter") if exe and not exepath: # We didn't find a necessary executable. Complain. pkgs = { # Foomatic command line executables 'gs': 'ghostscript', 'perl': 'perl', 'foo2oak-wrapper': None, 'pnm2ppa': 'pnm2ppa', # IJS servers (used by foomatic) 'hpijs': 'hpijs', 'ijsgutenprint.5.0': 'gutenprint', # CUPS filters 'rastertogutenprint.5.0': 'gutenprint', 'commandtoepson': 'gimp-print-cups', 'commandtocanon': 'gimp-print-cups', 'rastertoprinter': 'gimp-print-cups', } try: pkg = pkgs[exe] except: pkg = None if pkg: print "%s included in package %s" % (exe, pkg) error_text = ('' + _('Missing driver') + '\n\n' + _("Printer '%s' requires the %s package but " "it is not currently installed. Please " "install it before using this printer.") % (name, pkg)) else: error_text = ('' + _('Missing driver') + '\n\n' + _("Printer '%s' requires the '%s' program but " "it is not currently installed. Please " "install it before using this printer.") % (name, exe)) self.lblError.set_markup(error_text) self.ErrorDialog.set_transient_for (self.MainWindow) self.ErrorDialog.run() self.ErrorDialog.hide() ########################################################################## ### Server settings ########################################################################## def fillServerTab(self): self.changed = set() try: self.server_settings = self.cups.adminGetServerSettings() except cups.IPPError, (e, m): self.show_IPP_Error(e, m) self.tvMainList.get_selection().unselect_all() self.on_tvMainList_cursor_changed(self.tvMainList) return for widget, setting in [ (self.chkServerBrowse, cups.CUPS_SERVER_REMOTE_PRINTERS), (self.chkServerShare, cups.CUPS_SERVER_SHARE_PRINTERS), (self.chkServerRemoteAdmin, cups.CUPS_SERVER_REMOTE_ADMIN), (self.chkServerAllowCancelAll, cups.CUPS_SERVER_USER_CANCEL_ANY), (self.chkServerLogDebug, cups.CUPS_SERVER_DEBUG_LOGGING),]: widget.set_data("setting", setting) if self.server_settings.has_key(setting): widget.set_active(int(self.server_settings[setting])) widget.show() else: widget.hide() self.setDataButtonState() def on_server_changed(self, widget): if (str(int(widget.get_active())) == self.server_settings[widget.get_data("setting")]): self.changed.discard(widget) else: self.changed.add(widget) self.setDataButtonState() def save_serversettings(self): setting_dict = self.server_settings.copy() for widget, setting in [ (self.chkServerBrowse, cups.CUPS_SERVER_REMOTE_PRINTERS), (self.chkServerShare, cups.CUPS_SERVER_SHARE_PRINTERS), (self.chkServerRemoteAdmin, cups.CUPS_SERVER_REMOTE_ADMIN), (self.chkServerAllowCancelAll, cups.CUPS_SERVER_USER_CANCEL_ANY), (self.chkServerLogDebug, cups.CUPS_SERVER_DEBUG_LOGGING),]: if not self.server_settings.has_key(setting): continue setting_dict[setting] = str(int(widget.get_active())) try: self.cups.adminSetServerSettings(setting_dict) except cups.IPPError, (e, m): self.show_IPP_Error(e, m) return True self.changed = set() self.setDataButtonState() time.sleep(0.1) # give the server a chance to process our request # Now reconnect, in case the server needed to reload. self.reconnect () def main(): # The default configuration requires root for administration. cups.setUser ("root") gtk.gdk.threads_init() gtk.gdk.threads_enter() mainwindow = GUI() if gtk.__dict__.has_key("main"): gtk.main() else: gtk.mainloop() gtk.gdk.threads_leave() if __name__ == "__main__": main()