3
#############################################################################
5
## Copyright 2007 Canonical Ltd
6
## Author: Jonathan Riddell <jriddell@ubuntu.com>
8
## Includes code from System Config Printer
9
## Copyright 2007 Tim Waugh <twaugh@redhat.com>
10
## Copyright 2007 Red Hat, Inc.
12
## This program is free software; you can redistribute it and/or
13
## modify it under the terms of the GNU General Public License as
14
## published by the Free Software Foundation; either version 2 of
15
## the License, or (at your option) any later version.
17
## This program is distributed in the hope that it will be useful,
18
## but WITHOUT ANY WARRANTY; without even the implied warranty of
19
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
## GNU General Public License for more details.
22
## You should have received a copy of the GNU General Public License
23
## along with this program. If not, see <http://www.gnu.org/licenses/>.
25
#############################################################################
28
A systray applet to show that documents are being printed, show printer warnings and errors and a GUI for hal-cups-utils automatic setup for new printers.
30
It is a Qt port of the applet from Red Hat's System Config Printer
31
http://cyberelk.net/tim/software/system-config-printer/
32
svn co http://svn.fedorahosted.org/svn/system-config-printer/trunk
40
KDE4CONFIG = "kde4-config"
41
KDEDIR = subprocess.Popen([KDE4CONFIG, "--prefix"], stdout=subprocess.PIPE).communicate()[0]
42
KDEDATADIR = subprocess.Popen([KDE4CONFIG, "--path", "data"], stdout=subprocess.PIPE).communicate()[0]
45
KDE4CONFIG = "/usr/lib/kde4/bin/kde4-config"
46
KDEDIR = subprocess.Popen([KDE4CONFIG, "--prefix"], stdout=subprocess.PIPE).communicate()[0]
47
KDEDATADIR = subprocess.Popen([KDE4CONFIG, "--path", "data"], stdout=subprocess.PIPE).communicate()[0]
49
print "Could not find kde4-config, exiting."
51
KDEDIR = KDEDIR.rstrip()
52
KDEDATADIR = KDEDATADIR.rstrip()
53
KDEDATADIR = KDEDATADIR.split(":")[-1]
55
if os.path.exists("printer-applet.ui"):
58
APPDIR=KDEDATADIR + "/printer-applet"
60
MIN_REFRESH_INTERVAL = 1 # seconds
61
DOMAIN="printer-applet"
62
CONNECTING_TIMEOUT = 60 # seconds
66
from PyQt4.QtCore import *
67
from PyQt4.QtGui import *
71
gettext.textdomain(DOMAIN)
73
return unicode(gettext.gettext(string), "utf-8")
75
def translate(self, prop):
76
"""reimplement method from uic to change it to use gettext"""
77
if prop.get("notr", None) == "true":
78
return self._cstring(prop)
82
text = prop.text.encode("UTF-8")
85
uic.properties.Properties._string = translate
90
import dbus.mainloop.qt
93
class MainWindow(QMainWindow):
94
"""Our main GUI dialogue, overridden so that closing it doesn't quit the app"""
96
def closeEvent(self, event):
100
class PrintersWindow(QWidget):
101
"""The printer status dialogue, overridden so that closing it doesn't quit the app and to untick the show menu entry"""
103
def __init__(self, applet):
104
QWidget.__init__(self)
107
def closeEvent(self, event):
109
self.applet.on_printer_status_delete_event()
118
REPORT: "dialog-info",
119
WARNING: "dialog-warning",
120
ERROR: "dialog-error"
123
def __init__(self, printer, reason):
124
self.printer = printer
127
self.canonical_reason = None
129
def get_printer (self):
132
def get_level (self):
133
if self.level != None:
136
if (self.reason.endswith ("-report") or
137
self.reason == "connecting-to-device"):
138
self.level = self.REPORT
139
elif self.reason.endswith ("-warning"):
140
self.level = self.WARNING
142
self.level = self.ERROR
145
def get_reason (self):
146
if self.canonical_reason:
147
return self.canonical_reason
149
level = self.get_level ()
151
if level == self.WARNING and reason.endswith ("-warning"):
153
elif level == self.ERROR and reason.endswith ("-error"):
155
self.canonical_reason = reason
156
return self.canonical_reason
158
def get_description (self):
160
'toner-low': (i18n("Toner low"),
161
i18n("Printer '%s' is low on toner.")),
162
'toner-empty': (i18n("Toner empty"),
163
i18n("Printer '%s' has no toner left.")),
164
'cover-open': (i18n("Cover open"),
165
i18n("The cover is open on printer '%s'.")),
166
'door-open': (i18n("Door open"),
167
i18n("The door is open on printer '%s'.")),
168
'media-low': (i18n("Paper low"),
169
i18n("Printer '%s' is low on paper.")),
170
'media-empty': (i18n("Out of paper"),
171
i18n("Printer '%s' is out of paper.")),
172
'marker-supply-low': (i18n("Ink low"),
173
i18n("Printer '%s' is low on ink.")),
174
'marker-supply-empty': (i18n("Ink empty"),
175
i18n("Printer '%s' has no ink left.")),
176
'connecting-to-device': (i18n("Not connected?"),
177
i18n("Printer '%s' may not be connected.")),
180
(title, text) = messages[self.get_reason ()]
181
text = text % self.get_printer ()
183
if self.get_level () == self.REPORT:
184
title = i18n("Printer report")
185
elif self.get_level () == self.WARNING:
186
title = i18n("Printer warning")
187
elif self.get_level () == self.ERROR:
188
title = i18n("Printer error")
189
text = i18n("Printer '%s': '%s'.") % (self.get_printer (),
193
def get_tuple (self):
194
return (self.get_level (), self.get_printer (), self.get_reason ())
196
def __cmp__(self, other):
199
if other.get_level () != self.get_level ():
200
return self.get_level () < other.get_level ()
201
if other.get_printer () != self.get_printer ():
202
return other.get_printer () < self.get_printer ()
203
return other.get_reason () < self.get_reason ()
205
def collect_printer_state_reasons (connection):
207
printers = connection.getPrinters ()
208
for name, printer in printers.iteritems ():
209
reasons = printer["printer-state-reasons"]
210
if type (reasons) == str:
211
# Work around a bug that was fixed in pycups-1.9.20.
213
for reason in reasons:
216
if (reason.startswith ("moving-to-paused") or
217
reason.startswith ("paused") or
218
reason.startswith ("shutdown") or
219
reason.startswith ("stopping") or
220
reason.startswith ("stopped-partly")):
222
result.append (StateReason (name, reason))
225
def worst_printer_state_reason (connection, printer_reasons=None):
226
"""Fetches the printer list and checks printer-state-reason for
227
each printer, returning a StateReason for the most severe
228
printer-state-reason, or None."""
230
if printer_reasons == None:
231
printer_reasons = collect_printer_state_reasons (connection)
232
for reason in printer_reasons:
233
if worst_reason == None:
234
worst_reason = reason
236
if reason > worst_reason:
237
worst_reason = reason
242
class JobManager(QObject):
243
"""our main class creates the systray icon and the dialogues and refreshes the dialogues for new information"""
244
def __init__(self, parent = None):
245
QObject.__init__(self)
247
self.will_refresh = False # whether timeout is set
248
self.last_refreshed = 0
250
self.suppress_icon_hide = False
251
self.which_jobs = "not-completed"
255
self.will_update_job_creation_times = False # whether timeout is set
256
self.statusbar_set = False
257
self.reasons_seen = {}
258
self.connecting_to_device = {} # dict of printer->time first seen
259
self.still_connecting = set()
260
self.special_status_icon = False
262
self.mainWindow = MainWindow()
263
uic.loadUi(APPDIR + "/" + "printer-applet.ui", self.mainWindow)
265
self.printersWindow = PrintersWindow(self)
266
uic.loadUi(APPDIR + "/" + "printer-applet-printers.ui", self.printersWindow)
268
self.sysTray = QSystemTrayIcon(QIcon(KDEDIR + "/share/icons/oxygen/22x22/devices/printer.png"), self.mainWindow)
270
self.connect(self.sysTray, SIGNAL("activated( QSystemTrayIcon::ActivationReason )"), self.showMainWindow)
273
self.menu.addAction(i18n("_Hide").replace("_", ""), self.on_icon_hide_activate)
274
self.menu.addAction(QIcon(KDEDIR + "/share/icons/oxygen/16x16/actions/application-exit.png"), i18n("Quit"), self.on_icon_quit_activate)
275
self.sysTray.setContextMenu(self.menu)
277
self.mainWindow.actionClose.setIcon(QIcon(KDEDIR + "/share/icons/oxygen/16x16/actions/window-close.png"))
278
self.mainWindow.actionRefresh_2.setIcon(QIcon(KDEDIR + "/share/icons/oxygen/16x16/actions/view-refresh.png"))
280
self.connect(self.mainWindow.actionRefresh_2, SIGNAL("triggered(bool)"), self.on_refresh_activate)
281
self.connect(self.mainWindow.actionShow_Completed_Jobs_2, SIGNAL("triggered(bool)"), self.on_show_completed_jobs_activate)
282
self.connect(self.mainWindow.actionShow_Printer_Status, SIGNAL("triggered(bool)"), self.on_show_printer_status_activate)
283
self.connect(self.mainWindow.actionClose, SIGNAL("triggered(bool)"), self.hideMainWindow)
285
self.mainWindow.treeWidget.setContextMenuPolicy(Qt.CustomContextMenu)
286
self.connect(self.mainWindow.treeWidget, SIGNAL("customContextMenuRequested(const QPoint&)"), self.on_treeview_button_press_event)
287
#self.connect(self.mainWindow.treeWidget, SIGNAL("itemClicked(QTreeWidgetItem*, int)"), self.printItemClicked)
288
self.rightClickMenu = QMenu(self.mainWindow.treeWidget)
289
self.cancel = self.rightClickMenu.addAction(i18n("Cancel"), self.on_job_cancel_activate)
290
self.hold = self.rightClickMenu.addAction(i18n("_Hold").replace("_",""), self.on_job_hold_activate)
291
self.release = self.rightClickMenu.addAction(i18n("_Release").replace("_",""), self.on_job_release_activate)
292
self.reprint = self.rightClickMenu.addAction(i18n("Re_print").replace("_",""), self.on_job_reprint_activate)
294
cups.setPasswordCB(self.cupsPasswdCallback)
296
dbus.mainloop.qt.DBusQtMainLoop(set_as_default=True)
299
bus = dbus.SystemBus()
301
print >> sys.stderr, "%s: failed to connect to system D-Bus" % PROGRAM_NAME
304
notification = NewPrinterNotification(bus, self)
307
bus.add_signal_receiver (self.handle_dbus_signal,
308
path="/com/redhat/PrinterSpooler",
309
dbus_interface="com.redhat.PrinterSpooler")
312
"""Used in gtk frontend to set magnifing glass icon when configuring printer, I don't have a suitable icon so using bubbles instead
313
# Handle "special" status icon
314
def set_special_statusicon (self, iconname):
315
self.special_status_icon = True
316
self.statusicon.set_from_icon_name (iconname)
317
self.set_statusicon_visibility ()
319
def unset_special_statusicon (self):
320
self.special_status_icon = False
321
self.statusicon.set_from_pixbuf (self.saved_statusicon_pixbuf)
324
def notify_new_printer (self, printer, title, text):
326
self.showMessage(title, text)
328
def showMessage(self, title, message):
329
"""show a message, delayed slightly to ensure the systray is visible else it appears in the wrong place
330
Gtk uses libnotify, for Qt we just show the message directly"""
332
self.sysTrayTitle = title
333
self.sysTrayMessage = message
334
QTimer.singleShot(1000, self.showSysTrayMessage)
336
def showSysTrayMessage(self):
337
self.sysTray.showMessage(self.sysTrayTitle, self.sysTrayMessage)
339
"""unused, see set_special_statusicon
340
def set_statusicon_from_pixbuf (self, pb):
341
self.saved_statusicon_pixbuf = pb
342
if not self.special_status_icon:
343
self.statusicon.set_from_pixbuf (pb)
346
"""unused, see MainWindow and PrintersWindow
347
def on_delete_event(self, *args):
349
self.MainWindow.hide ()
350
if self.show_printer_status.get_active ():
351
self.PrintersWindow.hide ()
357
def on_printer_status_delete_event(self):
358
self.mainWindow.actionShow_Printer_Status.setChecked(False)
360
def cupsPasswdCallback(self, querystring):
361
(text, ok) = QInputDialog.getText(self.mainWindow, i18n("Password required"), querystring, QLineEdit.Password)
367
def show_IPP_Error(self, exception, message):
368
if exception == cups.IPP_NOT_AUTHORIZED:
369
error_text = ('<span weight="bold" size="larger">' +
370
i18n('Not authorized') + '</span>\n\n' +
371
i18n('The password may be incorrect.'))
373
error_text = ('<span weight="bold" size="larger">' +
374
i18n('CUPS server error') + '</span>\n\n' +
375
i18n("There was an error during the CUPS "\
376
"operation: '%s'.")) % message
377
#fix Gtk's non-HTML for Qt
378
error_text = error_text.replace("\n", "<br />")
379
error_text = error_text.replace("span", "strong")
380
QMessageBox.critical(self.mainWindow, i18n("Error"), error_text)
383
def toggle_window_display(self, icon):
385
#FIXME, hide printer status window?
386
def hideMainWindow(self):
387
self.mainWindow.hide()
389
def showMainWindow(self, activationReason):
390
if activationReason == QSystemTrayIcon.Trigger:
391
if self.mainWindow.isVisible():
392
self.mainWindow.hide()
394
self.mainWindow.show()
396
def on_show_completed_jobs_activate(self, activated):
398
self.which_jobs = "all"
400
self.which_jobs = "not-completed"
403
def on_show_printer_status_activate(self, activated):
405
self.printersWindow.show()
407
self.printersWindow.hide()
409
def check_still_connecting(self):
410
"""Timer callback to check on connecting-to-device reasons."""
411
c = cups.Connection ()
412
printer_reasons = collect_printer_state_reasons (c)
415
if self.update_connecting_devices (printer_reasons):
418
# Don't run this callback again.
421
def update_connecting_devices(self, printer_reasons=[]):
422
"""Updates connecting_to_device dict and still_connecting set.
423
Returns True if a device has been connecting too long."""
424
time_now = time.time ()
425
connecting_to_device = {}
427
for reason in printer_reasons:
428
if reason.get_reason () == "connecting-to-device":
429
# Build a new connecting_to_device dict. If our existing
430
# dict already has an entry for this printer, use that.
431
printer = reason.get_printer ()
432
t = self.connecting_to_device.get (printer, time_now)
433
connecting_to_device[printer] = t
434
if time_now - t >= CONNECTING_TIMEOUT:
437
# Clear any previously-notified errors that are now fine.
439
for printer in self.still_connecting:
440
if not self.connecting_to_device.has_key (printer):
443
self.still_connecting = self.still_connecting.difference (remove)
445
self.connecting_to_device = connecting_to_device
448
def check_state_reasons(self, connection, my_printers=set()):
449
printer_reasons = collect_printer_state_reasons (connection)
451
# Look for any new reasons since we last checked.
452
old_reasons_seen_keys = self.reasons_seen.keys ()
455
for reason in printer_reasons:
456
tuple = reason.get_tuple ()
457
printer = reason.get_printer ()
458
reasons_now.add (tuple)
459
if not self.reasons_seen.has_key (tuple):
461
iter = QTreeWidgetItem(self.printersWindow.treeWidget)
462
#iter.setText(0, reason.get_level ())
463
iter.setText(0, reason.get_printer ())
464
title, text = reason.get_description ()
465
iter.setText(1, text)
466
self.printersWindow.treeWidget.addTopLevelItem(iter)
468
self.reasons_seen[tuple] = iter
469
if (reason.get_reason () == "connecting-to-device" and
470
not self.connecting_to_device.has_key (printer)):
471
# First time we've seen this.
475
# Check on them again in a minute's time.
476
QTimer.singleShot(CONNECTING_TIMEOUT * 1000, self.check_still_connecting)
478
self.update_connecting_devices (printer_reasons)
479
items = self.reasons_seen.keys ()
481
if not tuple in reasons_now:
482
# Reason no longer present.
483
iter = self.reasons_seen[tuple]
484
index = self.mainWindow.treeWidget.indexOfTopLevelItem(iter)
485
self.mainWindow.treeWidget.takeTopLevelItem(index)
486
del self.reasons_seen[tuple]
487
# Update statusbar and icon with most severe printer reason
488
# across all printers.
489
self.icon_has_emblem = False
490
reason = worst_printer_state_reason (connection, printer_reasons)
491
if reason != None and reason.get_level () >= StateReason.WARNING:
492
title, text = reason.get_description ()
493
#if self.statusbar_set:
494
# self.statusbar.pop (0)
495
self.mainWindow.statusBar().showMessage(text)
496
#self.statusbar.push (0, text)
497
self.worst_reason_text = text
498
self.statusbar_set = True
501
icon = StateReason.LEVEL_ICON[reason.get_level ()]
502
emblem = QPixmap(KDEDIR + "/share/icons/oxygen/16x16/" + icon+".png")
503
pixbuf = QPixmap(KDEDIR + "/share/icons/oxygen/22x22/devices/printer.png")
504
painter = QPainter(pixbuf)
505
painter.drawPixmap(pixbuf.width()-emblem.width(),pixbuf.height()-emblem.height(),emblem)
507
self.sysTray.setIcon(QIcon(pixbuf))
508
self.icon_has_emblem = True
511
if self.statusbar_set:
512
#self.statusbar.pop (0)
513
self.mainWindow.statusBar().clearMessage()
514
self.statusbar_set = False
516
"""not using notifications in qt frontend
517
def on_notification_closed(self, notify):
520
def update_job_creation_times(self):
523
for job, data in self.jobs.iteritems():
524
if self.jobs.has_key (job):
525
iter = self.jobiters[job]
528
if data.has_key ('time-at-creation'):
529
created = data['time-at-creation']
532
t = time.ctime (created)
535
hours = int (ago / 3600)
536
mins = int ((ago % 3600) / 60)
539
t = i18n("1 hour ago")
541
t = i18n("1 hour and 1 minute ago")
543
t = i18n("1 hour and %d minutes ago") % mins
546
t = i18n("%d hours ago")
548
t = i18n("%d hours and 1 minute ago") % hours
550
t = i18n("%d hours and %d minutes ago") % \
556
t = i18n("a minute ago")
558
t = i18n("%d minutes ago") % mins
560
#self.store.set_value (iter, 4, t)
563
if need_update and not self.will_update_job_creation_times:
564
#gobject.timeout_add (60 * 1000,
565
# self.update_job_creation_times)
566
QTimer.singleShot(60 * 1000, self.update_job_creation_times)
567
self.will_update_job_creation_times = True
570
self.will_update_job_creation_times = False
572
# Return code controls whether the timeout will recur.
573
return self.will_update_job_creation_times
576
"""updates the print dialogue"""
578
if (now - self.last_refreshed) < MIN_REFRESH_INTERVAL:
579
if self.will_refresh:
582
#gobject.timeout_add (MIN_REFRESH_INTERVAL * 1000,
584
QTimer.singleShot(MIN_REFRESH_INTERVAL * 1000, self.update_job_creation_times)
585
self.will_refresh = True
588
self.will_refresh = False
589
self.last_refreshed = now
592
c = cups.Connection ()
593
jobs = c.getJobs (which_jobs=self.which_jobs, my_jobs=True)
594
except cups.IPPError, (e, m):
595
self.show_IPP_Error (e, m)
600
if self.which_jobs == "not-completed":
601
num_jobs = len (jobs)
604
num_jobs = len (c.getJobs (my_jobs=True))
605
except cups.IPPError, (e, m):
606
self.show_IPP_Error (e, m)
612
self.num_jobs = num_jobs
613
if self.hidden and self.num_jobs != self.num_jobs_when_hidden:
616
tooltip = i18n("No documents queued")
617
#FIXMEself.set_statusicon_from_pixbuf (self.icon_no_jobs)
619
tooltip = i18n("1 document queued")
620
#self.set_statusicon_from_pixbuf (self.icon_jobs)
622
tooltip = i18n("%d documents queued") % num_jobs
623
#self.set_statusicon_from_pixbuf (self.icon_jobs)
626
for job, data in jobs.iteritems ():
627
state = data.get ('job-state', cups.IPP_JOB_CANCELED)
628
if state >= cups.IPP_JOB_CANCELED:
630
uri = data.get ('job-printer-uri', '/')
632
my_printers.add (uri[i + 1:])
634
self.check_state_reasons (c, my_printers)
638
# If there are no jobs but there is a printer
639
# warning/error indicated by the icon, set the icon
640
# tooltip to the reason description.
641
if self.num_jobs == 0 and self.icon_has_emblem:
642
tooltip = self.worst_reason_text
644
self.sysTray.setToolTip (tooltip)
645
self.set_statusicon_visibility ()
647
for job in self.jobs:
648
if not jobs.has_key (job):
649
#self.store.remove (self.jobiters[job])
650
index = self.mainWindow.treeWidget.indexOfTopLevelItem(self.jobiters[job])
651
self.mainWindow.treeWidget.takeTopLevelItem(index)
652
del self.jobiters[job]
654
for job, data in jobs.iteritems():
655
if self.jobs.has_key (job):
656
iter = self.jobiters[job]
658
iter = QTreeWidgetItem(self.mainWindow.treeWidget)
659
iter.setText(0, str(job))
660
iter.setText(1, data.get('job-name', 'Unknown'))
661
self.mainWindow.treeWidget.addTopLevelItem(iter)
662
self.jobiters[job] = iter
664
uri = data.get('job-printer-uri', '')
667
printer = uri[i + 1:]
668
iter.setText(2, printer)
670
if data.has_key ('job-k-octets'):
671
size = str (data['job-k-octets']) + 'k'
674
iter.setText(3, size)
675
#self.store.set_value (iter, 3, size)
678
if data.has_key ('job-state'):
680
jstate = data['job-state']
682
state = { cups.IPP_JOB_PENDING:i18n("Pending"),
683
cups.IPP_JOB_HELD:i18n("Held"),
684
cups.IPP_JOB_PROCESSING: i18n("Processing"),
685
cups.IPP_JOB_STOPPED: i18n("Stopped"),
686
cups.IPP_JOB_CANCELED: i18n("Canceled"),
687
cups.IPP_JOB_ABORTED: i18n("Aborted"),
688
cups.IPP_JOB_COMPLETED: i18n("Completed") }[s]
694
state = i18n("Unknown")
695
iter.setText(5, state)
696
columns = self.mainWindow.treeWidget.columnCount()
697
for i in range(columns):
698
self.mainWindow.treeWidget.resizeColumnToContents(i)
701
self.update_job_creation_times ()
703
def set_statusicon_visibility (self):
705
if self.suppress_icon_hide:
706
# Avoid hiding the icon if we've been woken up to notify
707
# about a new printer.
708
self.suppress_icon_hide = False
711
if (not self.hidden) and (self.num_jobs > 0 or self.icon_has_emblem) or self.special_status_icon:
716
def on_treeview_button_press_event(self, postition):
718
items = self.mainWindow.treeWidget.selectedItems ()
719
print "items" + str(items)
723
print "selected: " + str(items)
728
self.jobid = int(iter.text(0))
729
job = self.jobs[self.jobid]
730
self.cancel.setEnabled (True)
731
self.hold.setEnabled (True)
732
self.release.setEnabled (True)
733
self.reprint.setEnabled (True)
734
if job.has_key ('job-state'):
737
if s >= cups.IPP_JOB_CANCELED:
738
self.cancel.setEnabled (False)
739
if s != cups.IPP_JOB_PENDING and s != cups.IPP_JOB_PROCESSING:
740
self.hold.setEnabled (False)
741
if s != cups.IPP_JOB_HELD:
742
self.release.setEnabled (False)
743
if (s != cups.IPP_JOB_CANCELED or
744
not job.get('job-preserved', False)):
745
self.reprint.setEnabled (False)
746
self.rightClickMenu.popup(QCursor.pos())
748
def on_icon_popupmenu(self, icon, button, time):
749
self.icon_popupmenu.popup (None, None, None, button, time)
751
def on_icon_hide_activate(self):
752
self.num_jobs_when_hidden = self.num_jobs
754
self.set_statusicon_visibility ()
756
def on_icon_quit_activate(self):
759
def on_job_cancel_activate(self):
761
c = cups.Connection ()
762
c.cancelJob (self.jobid)
764
except cups.IPPError, (e, m):
765
self.show_IPP_Error (e, m)
770
def on_job_hold_activate(self):
772
c = cups.Connection ()
773
c.setJobHoldUntil (self.jobid, "indefinite")
775
except cups.IPPError, (e, m):
776
self.show_IPP_Error (e, m)
781
def on_job_release_activate(self):
783
c = cups.Connection ()
784
c.setJobHoldUntil (self.jobid, "no-hold")
786
except cups.IPPError, (e, m):
787
self.show_IPP_Error (e, m)
792
def on_job_reprint_activate(self):
794
c = cups.Connection ()
795
c.restartJob (self.jobid)
797
except cups.IPPError, (e, m):
798
self.show_IPP_Error (e, m)
805
def on_refresh_activate(self, menuitem):
808
def handle_dbus_signal(self, *args):
811
## Printer status window
813
def set_printer_status_icon (self, column, cell, model, iter, *user_data):
814
level = model.get_value (iter, 0)
815
icon = StateReason.LEVEL_ICON[level]
816
theme = gtk.icon_theme_get_default ()
818
pixbuf = theme.load_icon (icon, 22, 0)
819
cell.set_property("pixbuf", pixbuf)
820
except gobject.GError, exc:
821
pass # Couldn't load icon
824
def set_printer_status_name (self, column, cell, model, iter, *user_data):
825
cell.set_property("text", model.get_value (iter, 1))
829
#### NewPrinterNotification DBus server (the 'new' way). Note: this interface
830
#### is not final yet.
832
PDS_PATH="/com/redhat/NewPrinterNotification"
833
PDS_IFACE="com.redhat.NewPrinterNotification"
834
PDS_OBJ="com.redhat.NewPrinterNotification"
835
class NewPrinterNotification(dbus.service.Object):
836
"""listen for dbus signals"""
838
STATUS_MODEL_MISMATCH = 1
839
STATUS_GENERIC_DRIVER = 2
842
def __init__ (self, bus, jobmanager):
844
self.getting_ready = 0
845
self.jobmanager = jobmanager
846
bus_name = dbus.service.BusName (PDS_OBJ, bus=bus)
847
dbus.service.Object.__init__ (self, bus_name, PDS_PATH)
848
#self.jobmanager.notify_new_printer ("", i18n("New Printer"), i18n("Configuring New Printer"))
852
global waitloop, runloop, jobmanager
854
if jobmanager == None:
856
runloop = gobject.MainLoop ()
857
jobmanager = JobManager(bus, runloop,
858
service_running=service_running,
859
trayicon=trayicon, suppress_icon_hide=True)
862
@dbus.service.method(PDS_IFACE, in_signature='', out_signature='')
864
"""hal-cups-utils is settings up a new printer"""
865
self.jobmanager.notify_new_printer ("", i18n("New Printer"), i18n("Configuring New Printer"))
868
if self.getting_ready == 0:
869
jobmanager.set_special_statusicon (SEARCHING_ICON)
871
self.getting_ready += 1
872
gobject.timeout_add (60 * 1000, self.timeout_ready)
874
def timeout_ready (self):
876
if self.getting_ready > 0:
877
self.getting_ready -= 1
878
if self.getting_ready == 0:
879
jobmanager.unset_special_statusicon ()
884
# When I plug in my printer HAL calls this with these args:
886
#name: PSC_1400_series
888
#mdl: PSC 1400 series
890
#cmd: LDL,MLC,PML,DYN
891
@dbus.service.method(PDS_IFACE, in_signature='isssss', out_signature='')
892
def NewPrinter (self, status, name, mfg, mdl, des, cmd):
893
"""hal-cups-utils has set up a new printer"""
895
print "status: " + str(status)
896
print "name: " + name
903
c = cups.Connection ()
905
printer = c.getPrinters ()[name]
910
sys.path.append (APPDIR)
911
from ppds import ppdMakeModelSplit
912
(make, model) = ppdMakeModelSplit (printer['printer-make-and-model'])
913
driver = make + " " + model
914
if status < self.STATUS_GENERIC_DRIVER:
915
title = i18n("Printer added")
917
title = i18n("Missing printer driver")
919
if status == self.STATUS_SUCCESS:
920
text = i18n("`%s' is ready for printing.") % name
921
else: # Model mismatch
922
text = i18n("`%s' has been added, using the `%s' driver.") % \
925
self.jobmanager.notify_new_printer (name, title, text)
928
if __name__ == "__main__":
929
"""start the application. TODO, gtk frontend does clever things here to not start the GUI until it has to"""
930
#Oxygen doesn't work with python-dbus (it loads QtDbus which clashes) so force to plastique until that is fixed
931
args = sys.argv + ["-style=plastique"]
932
#KApplication also loads QtDbus which clashes with python-dbus, so stick with QApplication for now
933
app = QApplication(args)
934
app.setWindowIcon(QIcon(KDEDIR + "/share/icons/oxygen/128x128/devices/printer.png"))
935
if app.isSessionRestored():
937
applet = JobManager()
938
if "--show" in sys.argv:
939
applet.mainWindow.show()
940
applet.sysTray.show()
941
sys.exit(app.exec_())