From da5a90520469350ef5d102b69f7f748e351edec0 Mon Sep 17 00:00:00 2001 From: Tim Waugh Date: Thu, 14 Aug 2008 15:26:32 +0000 Subject: [PATCH] - Include other fixes from upstream including: - OpenPrinting API change (trac #74). - libnotify API change for 'closed' signal. - Notification for job authentication (trac #91). - Glade delete-event fixes (trac #88). - Pre-fill username in job authentication dialog (trac #87). --- system-config-printer-forbidden.patch | 85 ----- system-config-printer-git.patch | 457 ++++++++++++++++++++++++++ system-config-printer.spec | 14 +- 3 files changed, 468 insertions(+), 88 deletions(-) delete mode 100644 system-config-printer-forbidden.patch create mode 100644 system-config-printer-git.patch diff --git a/system-config-printer-forbidden.patch b/system-config-printer-forbidden.patch deleted file mode 100644 index fff2e29..0000000 --- a/system-config-printer-forbidden.patch +++ /dev/null @@ -1,85 +0,0 @@ -diff -up system-config-printer-1.0.5/authconn.py.forbidden system-config-printer-1.0.5/authconn.py ---- system-config-printer-1.0.5/authconn.py.forbidden 2008-08-11 14:33:09.000000000 +0100 -+++ system-config-printer-1.0.5/authconn.py 2008-08-13 22:21:03.000000000 +0100 -@@ -130,7 +130,13 @@ class Connection: - - def _authloop (self, fname, fn, *args, **kwds): - self._passes = 0 -+ c = self._connection - while self._perform_authentication () != 0: -+ if c != self._connection: -+ # We have reconnected. -+ fn = getattr (self._connection, fname) -+ c = self._connection -+ - try: - result = fn.__call__ (*args, **kwds) - -@@ -146,15 +152,17 @@ class Connection: - else: - raise - except cups.HTTPError, (s,): -- if not self._cancel and s == cups.HTTP_UNAUTHORIZED: -- self._failed () -+ if not self._cancel and (s == cups.HTTP_UNAUTHORIZED or -+ s == cups.HTTP_FORBIDDEN): -+ self._failed (s == cups.HTTP_FORBIDDEN) - else: - raise - - return result - -- def _failed (self): -+ def _failed (self, forbidden=False): - self._has_failed = True -+ self._forbidden = forbidden - - def _password_callback (self, prompt): - debugprint ("Got password callback") -@@ -173,6 +181,7 @@ class Connection: - # Haven't yet tried the operation. Set the password - # callback and return > 0 so we try it for the first time. - self._has_failed = False -+ self._forbidden = False - self._auth_called = False - self._cancel = False - cups.setPasswordCB (self._password_callback) -@@ -192,16 +201,17 @@ class Connection: - # Tried the operation without a password and it failed. - if (self._try_as_root and - self._user != 'root' and -- self._server[0] == '/'): -+ (self._server[0] == '/' or self._forbidden)): - # This is a UNIX domain socket connection so we should -- # not have needed a password, and so the operation must -- # not be something that the current user is authorised to -- # do. They need to try as root, and supply the password. -- # However, to get the right prompt, we need to try as -- # root but with no password first. -+ # not have needed a password (or it is not a UDS but -+ # we got an HTTP_FORBIDDEN response), and so the -+ # operation must not be something that the current -+ # user is authorised to do. They need to try as root, -+ # and supply the password. However, to get the right -+ # prompt, we need to try as root but with no password -+ # first. - debugprint ("Authentication: Try as root") - self._use_user = 'root' -- cups.setUser (self._use_user) - self._auth_called = False - self._connect () - return 1 -diff -U0 system-config-printer-1.0.5/ChangeLog.forbidden system-config-printer-1.0.5/ChangeLog ---- system-config-printer-1.0.5/ChangeLog.forbidden 2008-08-11 14:51:07.000000000 +0100 -+++ system-config-printer-1.0.5/ChangeLog 2008-08-13 22:21:03.000000000 +0100 -@@ -0,0 +1,10 @@ -+2008-08-13 Tim Waugh -+ -+ * authconn.py (Connection._authloop): Re-bind to the named method -+ on reconnection. Handle HTTP_FORBIDDEN (trac #89). -+ (Connection._failed): New optional parameter forbidden. Remember -+ whether we saw HTTP_FORBIDDEN. -+ (Connection._perform_authentication): Initialise self._forbidden. -+ Use it to decide whether to try as root. Don't call setUser() -+ here; _connect() will do that. -+ diff --git a/system-config-printer-git.patch b/system-config-printer-git.patch new file mode 100644 index 0000000..6d49a53 --- /dev/null +++ b/system-config-printer-git.patch @@ -0,0 +1,457 @@ +diff -up system-config-printer-1.0.5/authconn.py.git system-config-printer-1.0.5/authconn.py +--- system-config-printer-1.0.5/authconn.py.git 2008-08-11 14:33:09.000000000 +0100 ++++ system-config-printer-1.0.5/authconn.py 2008-08-14 16:23:49.000000000 +0100 +@@ -130,7 +130,13 @@ class Connection: + + def _authloop (self, fname, fn, *args, **kwds): + self._passes = 0 ++ c = self._connection + while self._perform_authentication () != 0: ++ if c != self._connection: ++ # We have reconnected. ++ fn = getattr (self._connection, fname) ++ c = self._connection ++ + try: + result = fn.__call__ (*args, **kwds) + +@@ -146,15 +152,17 @@ class Connection: + else: + raise + except cups.HTTPError, (s,): +- if not self._cancel and s == cups.HTTP_UNAUTHORIZED: +- self._failed () ++ if not self._cancel and (s == cups.HTTP_UNAUTHORIZED or ++ s == cups.HTTP_FORBIDDEN): ++ self._failed (s == cups.HTTP_FORBIDDEN) + else: + raise + + return result + +- def _failed (self): ++ def _failed (self, forbidden=False): + self._has_failed = True ++ self._forbidden = forbidden + + def _password_callback (self, prompt): + debugprint ("Got password callback") +@@ -173,6 +181,7 @@ class Connection: + # Haven't yet tried the operation. Set the password + # callback and return > 0 so we try it for the first time. + self._has_failed = False ++ self._forbidden = False + self._auth_called = False + self._cancel = False + cups.setPasswordCB (self._password_callback) +@@ -192,16 +201,17 @@ class Connection: + # Tried the operation without a password and it failed. + if (self._try_as_root and + self._user != 'root' and +- self._server[0] == '/'): ++ (self._server[0] == '/' or self._forbidden)): + # This is a UNIX domain socket connection so we should +- # not have needed a password, and so the operation must +- # not be something that the current user is authorised to +- # do. They need to try as root, and supply the password. +- # However, to get the right prompt, we need to try as +- # root but with no password first. ++ # not have needed a password (or it is not a UDS but ++ # we got an HTTP_FORBIDDEN response), and so the ++ # operation must not be something that the current ++ # user is authorised to do. They need to try as root, ++ # and supply the password. However, to get the right ++ # prompt, we need to try as root but with no password ++ # first. + debugprint ("Authentication: Try as root") + self._use_user = 'root' +- cups.setUser (self._use_user) + self._auth_called = False + self._connect () + return 1 +diff -U0 system-config-printer-1.0.5/ChangeLog.git system-config-printer-1.0.5/ChangeLog +--- system-config-printer-1.0.5/ChangeLog.git 2008-08-11 14:51:07.000000000 +0100 ++++ system-config-printer-1.0.5/ChangeLog 2008-08-14 16:23:49.000000000 +0100 +@@ -0,0 +1,67 @@ ++2008-08-14 Tim Waugh ++ ++ * cupshelpers/openprinting.py ++ (OpenPrinting.listDrivers.parse_result): OpenPrinting API change: ++ freesoftware -> nonfreesoftware (trac #74). ++ * system-config-printer.py ++ (NewPrinterGUI.on_tvNPDownloadableDrivers_cursor_changed): ++ Likewise. ++ ++2008-08-14 Tim Waugh ++ ++ * jobviewer.py (JobViewer.on_new_printer_notification_closed): ++ Optional reason parameter to handle newer libnotify signal ++ interface. ++ (JobViewer.on_state_reason_notification_closed): Likewise. ++ (JobViewer.on_auth_notification_closed): Likewise. ++ ++2008-08-14 Tim Waugh ++ ++ * jobviewer.py (JobViewer.update_job): Display a notification when ++ a job requires authentication (trac #91). ++ (JobViewer.on_auth_notification_authenticate): New method. ++ Display an authentication dialog when the notification's ++ authenticate action is triggered. ++ (JobViewer.on_auth_notification_closed): New method. Handle an ++ auth notification being closed. ++ ++2008-08-14 Tim Waugh ++ ++ * jobviewer.py (JobViewer.update_job): Moved code for displaying ++ authentication dialog... ++ (JobViewer.display_auth_info_dialog): ...here. New method. ++ ++2008-08-14 Rui Tiago Cação Matos ++ ++ * system-config-printer.py (GUI.__init__): ++ * system-config-printer.py (NewPrinterGUI.__init__): Since some ++ dialogs are reused we can't let the delete-event's default handler ++ destroy them. ++ * system-config-printer.py (on_delete_just_hide): This method is ++ connected to those dialog's delete-event (trac #88). ++ ++2008-08-14 Tim Waugh ++ ++ * jobviewer.py (JobViewer.update_job): When a job is held for ++ authentication, say so in the status column. ++ ++2008-08-14 Tim Waugh ++ ++ * jobviewer.py (JobViewer.update_job): Show authentication dialog ++ in the middle of the screen (trac #90). ++ ++2008-08-13 Tim Waugh ++ ++ * authconn.py (Connection._authloop): Re-bind to the named method ++ on reconnection. Handle HTTP_FORBIDDEN (trac #89). ++ (Connection._failed): New optional parameter forbidden. Remember ++ whether we saw HTTP_FORBIDDEN. ++ (Connection._perform_authentication): Initialise self._forbidden. ++ Use it to decide whether to try as root. Don't call setUser() ++ here; _connect() will do that. ++ ++2008-08-12 Tim Waugh ++ ++ * jobviewer.py (JobViewer.update_job): Pre-fill the username field ++ when asking for authentication for the job (trac #87). ++ +diff -up system-config-printer-1.0.5/cupshelpers/openprinting.py.git system-config-printer-1.0.5/cupshelpers/openprinting.py +--- system-config-printer-1.0.5/cupshelpers/openprinting.py.git 2008-08-11 10:05:02.000000000 +0100 ++++ system-config-printer-1.0.5/cupshelpers/openprinting.py 2008-08-14 16:23:49.000000000 +0100 +@@ -217,7 +217,7 @@ class OpenPrinting: + # 'supplier': supplier, + # 'license': short license string e.g. GPLv2, + # 'licensetext': license text (HTML), +- # 'freesoftware': Boolean, ++ # 'nonfreesoftware': Boolean, + # 'patents': Boolean, + # 'shortdescription': short description, + # 'recommended': Boolean, +@@ -260,10 +260,15 @@ class OpenPrinting: + if element != None: + dict['licensetext'] = element.text + +- for boolean in ['freesoftware', 'recommended', ++ for boolean in ['nonfreesoftware', 'recommended', + 'patents']: + dict[boolean] = driver.find (boolean) != None + ++ # Make a 'freesoftware' tag for compatibility with ++ # how the OpenPrinting API used to work (see trac ++ # #74). ++ dict['freesoftware'] = not dict['nonfreesoftware'] ++ + if not dict.has_key ('name') or not dict.has_key ('url'): + continue + +diff -up system-config-printer-1.0.5/jobviewer.py.git system-config-printer-1.0.5/jobviewer.py +--- system-config-printer-1.0.5/jobviewer.py.git 2008-08-11 14:51:07.000000000 +0100 ++++ system-config-printer-1.0.5/jobviewer.py 2008-08-14 16:23:49.000000000 +0100 +@@ -31,6 +31,7 @@ import gtk.glade + import monitor + import os + import pango ++import pwd + import sys + import time + +@@ -160,6 +161,7 @@ class JobViewer (monitor.Watcher): + self.num_jobs_when_hidden = 0 + self.connecting_to_device = {} # dict of printer->time first seen + self.state_reason_notifications = {} ++ self.auth_notifications = {} + self.job_creation_times_timer = None + self.special_status_icon = False + self.new_printer_notifications = {} +@@ -320,7 +322,7 @@ class JobViewer (monitor.Watcher): + notification.attach_to_status_icon (self.statusicon) + notification.show () + +- def on_new_printer_notification_closed (self, notification): ++ def on_new_printer_notification_closed (self, notification, reason=None): + printer = notification.get_data ('printer-name') + del self.new_printer_notifications[printer] + self.set_statusicon_visibility () +@@ -471,17 +473,24 @@ class JobViewer (monitor.Watcher): + store.set_value (iter, 4, size) + + state = None ++ job_requires_auth = False + if data.has_key ('job-state'): + try: + jstate = data['job-state'] + s = int (jstate) +- state = { cups.IPP_JOB_PENDING: _("Pending"), +- cups.IPP_JOB_HELD: _("Held"), +- cups.IPP_JOB_PROCESSING: _("Processing"), +- cups.IPP_JOB_STOPPED: _("Stopped"), +- cups.IPP_JOB_CANCELED: _("Canceled"), +- cups.IPP_JOB_ABORTED: _("Aborted"), +- cups.IPP_JOB_COMPLETED: _("Completed") }[s] ++ job_requires_auth = (jstate == cups.IPP_JOB_HELD and ++ data.get ('job-hold-until', 'none') == ++ 'auth-info-required') ++ if job_requires_auth: ++ state = _("Held for authentication") ++ else: ++ state = { cups.IPP_JOB_PENDING: _("Pending"), ++ cups.IPP_JOB_HELD: _("Held"), ++ cups.IPP_JOB_PROCESSING: _("Processing"), ++ cups.IPP_JOB_STOPPED: _("Stopped"), ++ cups.IPP_JOB_CANCELED: _("Canceled"), ++ cups.IPP_JOB_ABORTED: _("Aborted"), ++ cups.IPP_JOB_COMPLETED: _("Completed") }[s] + except ValueError: + pass + except IndexError: +@@ -492,64 +501,115 @@ class JobViewer (monitor.Watcher): + store.set_value (iter, 6, state) + + # Check whether authentication is required. +- if (self.trayicon and +- data['job-state'] == cups.IPP_JOB_HELD and +- data.get ('job-hold-until', 'none') == 'auth-info-required' and +- not self.auth_info_dialog): ++ if self.trayicon: ++ if (job_requires_auth and ++ not self.auth_notifications.has_key (job) and ++ not self.auth_info_dialog): ++ try: ++ cups.require ("1.9.37") ++ except: ++ debugprint ("Authentication required but " ++ "authenticateJob() not available") ++ return ++ ++ title = _("Authentication Required") ++ text = _("Job requires authentication to proceed.") ++ notification = pynotify.Notification (title, text, 'printer') ++ notification.set_data ('job-id', job) ++ notification.set_urgency (pynotify.URGENCY_NORMAL) ++ notification.set_timeout (pynotify.EXPIRES_NEVER) ++ notification.connect ('closed', ++ self.on_auth_notification_closed) ++ self.set_statusicon_visibility () ++ notification.attach_to_status_icon (self.statusicon) ++ notification.add_action ("authenticate", _("Authenticate"), ++ self.on_auth_notification_authenticate) ++ notification.show () ++ self.auth_notifications[job] = notification ++ elif (not job_requires_auth and ++ self.auth_notifications.has_key (job)): ++ self.auth_notifications[job].close () ++ ++ def on_auth_notification_closed (self, notification, reason=None): ++ job = notification.get_data ('job-id') ++ debugprint ("auth notification closed for job %s" % job) ++ del self.auth_notifications[job] ++ ++ def on_auth_notification_authenticate (self, notification, action): ++ job = notification.get_data ('job-id') ++ debugprint ("auth notification authenticate for job %s" % job) ++ self.display_auth_info_dialog (job) ++ ++ def display_auth_info_dialog (self, job): ++ data = self.jobs[job] ++ # Find out which auth-info is required. ++ try: ++ c = authconn.Connection (self.MainWindow) + try: +- cups.require ("1.9.37") +- except: +- debugprint ("Authentication required but " +- "authenticateJob() not available") +- return ++ uri = data['job-printer-uri'] ++ attributes = c.getPrinterAttributes (uri = uri) ++ except TypeError: # uri keyword introduced in pycups-1.9.32 ++ debugprint ("Fetching printer attributes by name") ++ attributes = c.getPrinterAttributes (printer) ++ except cups.IPPError, (e, m): ++ self.show_IPP_Error (e, m) ++ return ++ except RuntimeError: ++ debugprint ("Failed to connect when fetching printer attrs") ++ return + +- # Find out which auth-info is required. ++ try: ++ auth_info_required = attributes['auth-info-required'] ++ except KeyError: ++ debugprint ("No auth-info-required attribute; guessing instead") ++ auth_info_required = ['username', 'password'] ++ ++ if not isinstance (auth_info_required, list): ++ auth_info_required = [auth_info_required] ++ ++ if auth_info_required == ['negotiate']: ++ # Try Kerberos authentication. + try: +- c = authconn.Connection (self.MainWindow) +- try: +- uri = data['job-printer-uri'] +- attributes = c.getPrinterAttributes (uri = uri) +- except TypeError: # uri keyword introduced in pycups-1.9.32 +- debugprint ("Fetching printer attributes by name") +- attributes = c.getPrinterAttributes (printer) ++ debugprint ("Trying Kerberos auth for job %d" % jobid) ++ c.authenticateJob (jobid) ++ except TypeError: ++ # Requires pycups-1.9.39 for optional auth parameter. ++ # Treat this as a normal job error. ++ debugprint ("... need newer pycups for that") ++ return + except cups.IPPError, (e, m): + self.show_IPP_Error (e, m) + return +- except RuntimeError: +- debugprint ("Failed to connect when fetching printer attrs") +- return + +- try: +- auth_info_required = attributes['auth-info-required'] +- except KeyError: +- debugprint ("No auth-info-required attribute; guessing instead") +- auth_info_required = ['username', 'password'] +- +- if not isinstance (auth_info_required, list): +- auth_info_required = [auth_info_required] ++ dialog = authconn.AuthDialog (auth_info_required=auth_info_required) ++ dialog.set_position (gtk.WIN_POS_CENTER) + +- if auth_info_required == ['negotiate']: +- # Try Kerberos authentication. +- try: +- debugprint ("Trying Kerberos auth for job %d" % jobid) +- c.authenticateJob (jobid) +- except TypeError: +- # Requires pycups-1.9.39 for optional auth parameter. +- # Treat this as a normal job error. +- debugprint ("... need newer pycups for that") +- return +- except cups.IPPError, (e, m): +- self.show_IPP_Error (e, m) +- return ++ # Pre-fill 'username' field. ++ if 'username' in auth_info_required: ++ try: ++ auth_info = map (lambda x: '', auth_info_required) ++ username = pwd.getpwuid (os.getuid ())[0] ++ ind = auth_info_required.index ('username') ++ auth_info[ind] = username ++ dialog.set_auth_info (auth_info) ++ ++ index = 0 ++ for field in auth_info_required: ++ if auth_info[index] == '': ++ # Focus on the first empty field. ++ dialog.field_grab_focus (field) ++ break ++ index += 1 ++ except: ++ nonfatalException () + +- dialog = authconn.AuthDialog (auth_info_required=auth_info_required) +- dialog.set_prompt (_("Authentication required for " +- "printing document `%s' (job %d)") % +- (data.get('job-name', _("Unknown")), job)) +- self.auth_info_dialog = dialog +- dialog.connect ('response', self.auth_info_dialog_response) +- dialog.set_data ('job-id', job) +- dialog.show_all () ++ dialog.set_prompt (_("Authentication required for " ++ "printing document `%s' (job %d)") % ++ (data.get('job-name', _("Unknown")), job)) ++ self.auth_info_dialog = dialog ++ dialog.connect ('response', self.auth_info_dialog_response) ++ dialog.set_data ('job-id', job) ++ dialog.show_all () + + def auth_info_dialog_response (self, dialog, response): + dialog.hide () +@@ -862,7 +922,7 @@ class JobViewer (monitor.Watcher): + notification.attach_to_status_icon (self.statusicon) + notification.show () + +- def on_state_reason_notification_closed (self, notification): ++ def on_state_reason_notification_closed (self, notification, reason=None): + debugprint ("Notification %s closed" % repr (notification)) + reason = notification.get_data ('printer-state-reason') + tuple = reason.get_tuple () +diff -up system-config-printer-1.0.5/system-config-printer.py.git system-config-printer-1.0.5/system-config-printer.py +--- system-config-printer-1.0.5/system-config-printer.py.git 2008-08-11 14:51:07.000000000 +0100 ++++ system-config-printer-1.0.5/system-config-printer.py 2008-08-14 16:23:49.000000000 +0100 +@@ -152,6 +152,10 @@ class GtkGUI: + result.sort() + return result + ++def on_delete_just_hide (widget, event): ++ widget.hide () ++ return True # stop other handlers ++ + class GUI(GtkGUI, monitor.Watcher): + + printer_states = { cups.IPP_PRINTER_IDLE: _("Idle"), +@@ -271,6 +275,14 @@ class GUI(GtkGUI, monitor.Watcher): + "AboutDialog", + "WaitWindow", "lblWait", + ) ++ ++ # Since some dialogs are reused we can't let the delete-event's ++ # default handler destroy them ++ for dialog in [self.PrinterPropertiesDialog, ++ self.ServerSettingsDialog, ++ self.ConnectingDialog]: ++ dialog.connect ("delete-event", on_delete_just_hide) ++ + self.tooltips = gtk.Tooltips() + self.tooltips.enable() + gtk.window_set_default_icon_name ('printer') +@@ -2597,6 +2609,13 @@ class NewPrinterGUI(GtkGUI): + "rbtnNPDownloadLicenseNo", + "NewPrinterName", "entCopyName", "btnCopyOk", + "InstallDialog", "lblInstall") ++ ++ # Since some dialogs are reused we can't let the delete-event's ++ # default handler destroy them ++ for dialog in [self.IPPBrowseDialog, ++ self.SMBBrowseDialog]: ++ dialog.connect ("delete-event", on_delete_just_hide) ++ + # share with mainapp + self.WaitWindow = mainapp.WaitWindow + self.lblWait = mainapp.lblWait +@@ -4722,7 +4741,7 @@ class NewPrinterGUI(GtkGUI): + self.lblNPDownloadableDriverLicense.set_text (license) + description = driver.get('shortdescription', _("None")) + self.lblNPDownloadableDriverDescription.set_text (description) +- if driver['freesoftware'] and not driver['patents']: ++ if not driver['nonfreesoftware'] and not driver['patents']: + self.rbtnNPDownloadLicenseYes.set_active (True) + self.frmNPDownloadableDriverLicenseTerms.hide () + else: diff --git a/system-config-printer.spec b/system-config-printer.spec index b12415b..6d6d05b 100644 --- a/system-config-printer.spec +++ b/system-config-printer.spec @@ -7,7 +7,7 @@ Summary: A printer administration tool Name: system-config-printer Version: 1.0.5 -Release: 2%{?dist} +Release: 3%{?dist} License: GPLv2+ URL: http://cyberelk.net/tim/software/system-config-printer/ Group: System Environment/Base @@ -15,7 +15,7 @@ Source0: http://cyberelk.net/tim/data/system-config-printer/1.0.x/system-config- Source1: http://cyberelk.net/tim/data/pycups/pycups-%{pycups_version}.tar.bz2 Source2: http://cyberelk.net/tim/data/pysmbc/pysmbc-%{pysmbc_version}.tar.bz2 -Patch0: system-config-printer-forbidden.patch +Patch0: system-config-printer-git.patch Patch1: pysmbc-debug.patch BuildRequires: cups-devel >= 1.2 @@ -64,7 +64,7 @@ the configuration tool. %prep %setup -q -a 1 -a 2 -%patch0 -p1 -b .forbidden +%patch0 -p1 -b .git pushd pysmbc-%{pysmbc_version} %patch1 -p1 -b .debug @@ -157,6 +157,14 @@ rm -rf %buildroot exit 0 %changelog +* Thu Aug 14 2008 Tim Waugh 1.0.5-3 +- Include other fixes from upstream including: + - OpenPrinting API change (trac #74). + - libnotify API change for 'closed' signal. + - Notification for job authentication (trac #91). + - Glade delete-event fixes (trac #88). + - Pre-fill username in job authentication dialog (trac #87). + * Wed Aug 13 2008 Tim Waugh 1.0.5-2 - Handle HTTP_FORBIDDEN.