Tizen 2.1 base
[platform/upstream/hplip.git] / ui4 / systemtray.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # (c) Copyright 2003-2009 Hewlett-Packard Development Company, L.P.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 #
20 # Author: Don Welch
21
22 # Std Lib
23 import sys
24 import struct
25 import select
26 import os
27 import signal
28 import os.path
29 import time
30
31
32 # Local
33 from base.g import *
34 from base import device, utils, models
35 from base.codes import *
36 from ui_utils import *
37
38 # PyQt
39 try:
40     from PyQt4.QtCore import *
41     from PyQt4.QtGui import *
42 except ImportError:
43     log.error("Python bindings for Qt4 not found. Try using --qt3. Exiting!")
44     sys.exit(1)
45
46 from systrayframe import SystrayFrame
47
48 # dbus (required)
49 try:
50     import dbus
51     #import dbus.service
52     from dbus import SessionBus, lowlevel
53     #from dbus.mainloop.qt import DBusQtMainLoop
54 except ImportError:
55     log.error("Python bindings for dbus not found. Exiting!")
56     sys.exit(1)
57
58 import warnings
59 # Ignore: .../dbus/connection.py:242: DeprecationWarning: object.__init__() takes no parameters
60 # (occurring on Python 2.6/dBus 0.83/Ubuntu 9.04)
61 warnings.simplefilter("ignore", DeprecationWarning)
62
63
64 # pynotify (optional)
65 have_pynotify = True
66 try:
67     import pynotify
68 except ImportError:
69     have_pynotify = False
70
71
72 TRAY_MESSAGE_DELAY = 10000
73 HIDE_INACTIVE_DELAY = 5000
74 BLIP_DELAY = 2000
75 SET_MENU_DELAY = 1000
76 MAX_MENU_EVENTS = 10
77 UPGRADE_CHECK_DELAY=24*60*60*1000               #1 day
78
79 ERROR_STATE_TO_ICON = {
80     ERROR_STATE_CLEAR:        QSystemTrayIcon.Information,
81     ERROR_STATE_OK:           QSystemTrayIcon.Information,
82     ERROR_STATE_WARNING:      QSystemTrayIcon.Warning,
83     ERROR_STATE_ERROR:        QSystemTrayIcon.Critical,
84     ERROR_STATE_LOW_SUPPLIES: QSystemTrayIcon.Warning,
85     ERROR_STATE_BUSY:         QSystemTrayIcon.Warning,
86     ERROR_STATE_LOW_PAPER:    QSystemTrayIcon.Warning,
87     ERROR_STATE_PRINTING:     QSystemTrayIcon.Information,
88     ERROR_STATE_SCANNING:     QSystemTrayIcon.Information,
89     ERROR_STATE_PHOTOCARD:    QSystemTrayIcon.Information,
90     ERROR_STATE_FAXING:       QSystemTrayIcon.Information,
91     ERROR_STATE_COPYING:      QSystemTrayIcon.Information,
92 }
93
94 if have_pynotify:
95     info = getPynotifyIcon('info')
96     warn = getPynotifyIcon('warning')
97     err = getPynotifyIcon('error')
98     ERROR_STATE_TO_ICON_AND_URGENCY_PYNOTIFY = {
99         ERROR_STATE_CLEAR:        (info, pynotify.URGENCY_LOW),
100         ERROR_STATE_OK:           (info, pynotify.URGENCY_LOW),
101         ERROR_STATE_WARNING:      (warn, pynotify.URGENCY_NORMAL),
102         ERROR_STATE_ERROR:        (err, pynotify.URGENCY_CRITICAL),
103         ERROR_STATE_LOW_SUPPLIES: (warn, pynotify.URGENCY_NORMAL),
104         ERROR_STATE_BUSY:         (warn, pynotify.URGENCY_NORMAL),
105         ERROR_STATE_LOW_PAPER:    (warn, pynotify.URGENCY_NORMAL),
106         ERROR_STATE_PRINTING:     (info, pynotify.URGENCY_LOW),
107         ERROR_STATE_SCANNING:     (info, pynotify.URGENCY_LOW),
108         ERROR_STATE_PHOTOCARD:    (info, pynotify.URGENCY_LOW),
109         ERROR_STATE_FAXING:       (info, pynotify.URGENCY_LOW),
110         ERROR_STATE_COPYING:      (info, pynotify.URGENCY_LOW),
111     }
112
113 devices = {} # { <device_uri> : HistoryDevice(), ... }
114
115
116 class DeviceMenu(QMenu):
117     def __init__(self, title, parent, device_uri, device_hist, index):
118         QMenu.__init__(self, title, parent)
119         self.device_uri = device_uri
120         self.device_hist = device_hist
121         self.index = index
122
123
124     def update(self):
125         self.clear()
126
127         if self.device_hist:
128             first = True
129             for e in self.device_hist:
130                 error_state = STATUS_TO_ERROR_STATE_MAP.get(e.event_code, ERROR_STATE_CLEAR)
131                 ess = device.queryString(e.event_code, 0)
132
133                 a = QAction(QIcon(getStatusListIcon(error_state)[self.index]),
134                                     QString("%1 %2").arg(ess).arg(getTimeDeltaDesc(e.timedate)), self)
135
136                 if first:
137                     f = a.font()
138                     f.setBold(True)
139                     a.setFont(f)
140                     self.setIcon(QIcon(getStatusListIcon(error_state)[self.index]))
141                     first = False
142
143                 self.addAction(a)
144
145         else:
146             self.addAction(QIcon(load_pixmap("warning", "16x16")),
147                 QApplication.translate("SystemTray", "(No events)", None, QApplication.UnicodeUTF8))
148
149
150
151 class HistoryDevice(QObject):
152     def __init__(self, device_uri, needs_update=True):
153         self.needs_update = needs_update
154         self.device_uri = device_uri
155
156         back_end, is_hp, bus, model, serial, dev_file, host, zc, port = \
157                 device.parseDeviceURI(device_uri)
158
159         if bus == 'usb':
160             self.id = serial
161         elif bus == 'net':
162             self.id = host
163         elif bus == 'par':
164             self.id = dev_file
165         else:
166             self.id = 'unknown'
167
168         self.model = models.normalizeModelUIName(model)
169
170         if back_end == 'hp':
171             self.device_type = DEVICE_TYPE_PRINTER
172             self.menu_text = self.__tr("%1 Printer (%2)").arg(self.model).arg(self.id)
173
174         elif back_end == 'hpaio':
175             self.device_type = DEVICE_TYPE_SCANNER
176             self.menu_text = self.__tr("%1 Scanner (%2)").arg(self.model).arg(self.id)
177
178         elif back_end == 'hpfax':
179             self.device_type = DEVICE_TYPE_FAX
180             self.menu_text = self.__tr("%1 Fax (%2)").arg(self.model).arg(self.id)
181
182         else:
183             self.device_type = DEVICE_TYPE_UNKNOWN
184             self.menu_text = self.__tr("%1 (%2)").arg(self.model).arg(self.id)
185
186         self.mq = device.queryModelByURI(self.device_uri)
187         self.index = 0
188         if self.mq.get('tech-type', TECH_TYPE_NONE) in (TECH_TYPE_MONO_LASER, TECH_TYPE_COLOR_LASER):
189             self.index = 1
190         self.history = None
191
192
193     def getHistory(self, service):
194         if service is not None and self.needs_update:
195             device_uri, h = service.GetHistory(self.device_uri)
196             self.history = [device.Event(*tuple(e)) for e in list(h)[:-MAX_MENU_EVENTS:-1]]
197             self.needs_update = False
198
199
200     def __tr(self, s, c=None):
201         return QApplication.translate("SystemTray", s, c, QApplication.UnicodeUTF8)
202
203
204
205
206 class SystraySettingsDialog(QDialog):
207     def __init__(self, parent, systray_visible, polling,
208                  polling_interval, systray_messages,
209                  device_list=None,
210                  upgrade_notify=True,
211                  upgrade_pending_time=0,
212                  upgrade_last_update_time=0,
213                  upgrade_msg=""
214                  ):
215 #                 upgrade_pending_update_time=0,
216
217
218         QDialog.__init__(self, parent)
219
220         self.systray_visible = systray_visible
221         self.systray_messages = systray_messages
222
223         if device_list is not None:
224             self.device_list = device_list
225         else:
226             self.device_list = {}
227
228         self.polling = polling
229         self.polling_interval = polling_interval
230         self.upgrade_notify =upgrade_notify
231         self.upgrade_last_update_time=upgrade_last_update_time
232         self.upgrade_pending_time=upgrade_pending_time
233         self.upgrade_msg=upgrade_msg
234
235         self.initUi()
236         self.SystemTraySettings.updateUi()
237
238
239     def initUi(self):
240         self.setObjectName("SystraySettingsDialog")
241         self.resize(QSize(QRect(0,0,488,565).size()).expandedTo(self.minimumSizeHint()))
242
243         self.gridlayout = QGridLayout(self)
244         self.gridlayout.setObjectName("gridlayout")
245
246         self.SystemTraySettings = SystrayFrame(self)
247         self.SystemTraySettings.initUi(self.systray_visible,
248                                        self.polling, self.polling_interval,
249                                        self.device_list,
250                                        self.systray_messages,
251                                        self.upgrade_notify,
252                                        self.upgrade_pending_time,
253                                        self.upgrade_msg)
254
255         sizePolicy = QSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding)
256         sizePolicy.setHorizontalStretch(0)
257         sizePolicy.setVerticalStretch(0)
258         sizePolicy.setHeightForWidth(self.SystemTraySettings.sizePolicy().hasHeightForWidth())
259         self.SystemTraySettings.setSizePolicy(sizePolicy)
260         self.SystemTraySettings.setFrameShadow(QFrame.Raised)
261         self.SystemTraySettings.setObjectName("SystemTraySettings")
262         self.gridlayout.addWidget(self.SystemTraySettings,0,0,1,2)
263
264         spacerItem = QSpacerItem(301,20,QSizePolicy.Expanding,QSizePolicy.Minimum)
265         self.gridlayout.addItem(spacerItem,1,0,1,1)
266
267         self.StdButtons = QDialogButtonBox(self)
268         self.StdButtons.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.NoButton|QDialogButtonBox.Ok)
269         self.StdButtons.setCenterButtons(False)
270         self.StdButtons.setObjectName("StdButtons")
271         self.gridlayout.addWidget(self.StdButtons,1,1,1,1)
272
273         QObject.connect(self.StdButtons, SIGNAL("accepted()"), self.acceptClicked)
274         QObject.connect(self.StdButtons, SIGNAL("rejected()"), self.reject)
275         #QMetaObject.connectSlotsByName(self)
276
277         self.setWindowTitle(self.__tr("HP Device Manager - System Tray Settings"))
278         self.setWindowIcon(QIcon(load_pixmap('hp_logo', '128x128')))
279 #        pm = load_pixmap("hp_logo", "32x32")
280 #        self.prop_icon = QIcon(pm)
281
282
283     def acceptClicked(self):
284         self.systray_visible = self.SystemTraySettings.systray_visible
285         self.polling = self.SystemTraySettings.polling
286         self.polling_interval = self.SystemTraySettings.polling_interval
287         self.device_list = self.SystemTraySettings.device_list
288         self.systray_messages = self.SystemTraySettings.systray_messages
289         self.upgrade_notify =self.SystemTraySettings.upgrade_notify
290         self.accept()
291
292
293     def __tr(self, s, c=None):
294         return QApplication.translate("SystraySettingsDialog", s, c, QApplication.UnicodeUTF8)
295
296
297
298
299 class SystemTrayApp(QApplication):
300     def __init__(self, args, read_pipe):
301         QApplication.__init__(self, args)
302
303         self.menu = None
304         self.read_pipe = read_pipe
305         self.fmt = "80s80sI32sI80sf"
306         self.fmt_size = struct.calcsize(self.fmt)
307         self.timer_active = False
308         self.active_icon = False
309         self.user_settings = UserSettings()
310         self.user_settings.load()
311         self.user_settings.debug()
312
313         self.tray_icon = QSystemTrayIcon()
314
315         pm = load_pixmap("hp_logo", "32x32")
316         self.prop_icon = QIcon(pm)
317
318         a = load_pixmap('active', '16x16')
319         painter = QPainter(pm)
320         painter.drawPixmap(32, 0, a)
321         painter.end()
322
323         self.prop_active_icon = QIcon(pm)
324
325         self.tray_icon.setIcon(self.prop_icon)
326
327         self.session_bus = SessionBus()
328         self.service = None
329
330         for d in device.getSupportedCUPSDevices(back_end_filter=['hp', 'hpfax']):
331             self.addDevice(d)
332
333         self.tray_icon.setToolTip(self.__tr("HPLIP Status Service"))
334         QObject.connect(self.tray_icon, SIGNAL("messageClicked()"), self.messageClicked)
335         notifier = QSocketNotifier(self.read_pipe, QSocketNotifier.Read)
336         QObject.connect(notifier, SIGNAL("activated(int)"), self.notifierActivated)
337         QObject.connect(self.tray_icon, SIGNAL("activated(QSystemTrayIcon::ActivationReason)"), self.trayActivated)
338         self.tray_icon.show()
339
340         if self.user_settings.systray_visible == SYSTRAY_VISIBLE_SHOW_ALWAYS:
341             self.tray_icon.setVisible(True)
342         else:
343             QTimer.singleShot(HIDE_INACTIVE_DELAY, self.timeoutHideWhenInactive) # show icon for awhile @ startup
344
345         self.tray_icon.setIcon(self.prop_active_icon)
346         self.active_icon = True
347
348         self.handle_hplip_updation()
349         QTimer.singleShot(SET_MENU_DELAY, self.initDone)
350
351         self.timer = QTimer()
352         self.timer.connect(self.timer,SIGNAL("timeout()"),self.handle_hplip_updation)
353         self.timer.start(UPGRADE_CHECK_DELAY)
354
355
356
357     def initDone(self):
358         self.tray_icon.setIcon(self.prop_icon)
359         self.active_icon = False
360
361         self.setMenu()
362
363
364     def addDevice(self, device_uri):
365         try:
366             devices[device_uri]
367         except KeyError:
368             devices[device_uri] = HistoryDevice(device_uri)
369         else:
370             devices[device_uri].needs_update = True
371
372
373     def handle_hplip_updation(self):
374         log.debug("handle_hplip_updation upgrade_notify =%d"%(self.user_settings.upgrade_notify))
375         path = utils.which('hp-upgrade')
376         if self.user_settings.upgrade_notify is False:
377             log.debug("upgrade notification is disabled in systray ")
378             if path:
379                 path = os.path.join(path, 'hp-upgrade')
380                 log.debug("Running hp-upgrade: %s " % (path))
381                 # this just updates the available version in conf file. But won't notify
382                 os.spawnlp(os.P_NOWAIT, path, 'hp-upgrade', '--check')
383             return
384             
385             
386         current_time = time.time()
387     
388         if int(current_time) > self.user_settings.upgrade_pending_update_time:
389             path = utils.which('hp-upgrade')
390             if path:
391                 path = os.path.join(path, 'hp-upgrade')
392                 log.debug("Running hp-upgrade: %s " % (path))
393                 os.spawnlp(os.P_NOWAIT, path, 'hp-upgrade', '--notify')
394                 
395             else:
396                 log.error("Unable to find hp-upgrade --notify on PATH.")
397         else:
398             log.debug("upgrade schedule time is not yet completed. schedule time =%d current time =%d " %(self.user_settings.upgrade_pending_update_time, current_time))
399         
400
401
402
403
404
405     def setMenu(self):
406         self.menu = QMenu()
407
408         title = QWidgetAction(self.menu)
409         #title.setDisabled(True)
410
411         hbox = QFrame(self.menu)
412         layout = QHBoxLayout(hbox)
413         layout.setMargin(3)
414         layout.setSpacing(5)
415         pix_label = QLabel(hbox)
416
417         layout.insertWidget(-1, pix_label, 0)
418
419         icon_size = self.menu.style().pixelMetric(QStyle.PM_SmallIconSize)
420         pix_label.setPixmap(self.prop_icon.pixmap(icon_size))
421
422         label = QLabel(hbox)
423         layout.insertWidget(-1, label, 20)
424         title.setDefaultWidget(hbox)
425
426         label.setText(self.__tr("HPLIP Status Service"))
427
428         f = label.font()
429         f.setBold(True)
430         label.setFont(f)
431         self.menu.insertAction(None, title)
432
433         if devices:
434             if self.service is None:
435                 t = 0
436                 while t < 3:
437                     try:
438                         self.service = self.session_bus.get_object('com.hplip.StatusService',
439                                                                   "/com/hplip/StatusService")
440                     except DBusException:
441                         log.warn("Unable to connect to StatusService. Retrying...")
442
443                     t += 1
444                     time.sleep(0.5)
445
446             if self.service is not None:
447                 self.menu.addSeparator()
448
449                 for d in devices:
450                     devices[d].getHistory(self.service)
451
452                     menu = DeviceMenu(devices[d].menu_text, self.menu, d, devices[d].history, devices[d].index)
453                     self.menu.addMenu(menu)
454                     menu.update()
455
456
457         self.menu.addSeparator()
458         self.menu.addAction(self.__tr("HP Device Manager..."), self.toolboxTriggered)
459
460         self.menu.addSeparator()
461
462         self.settings_action = self.menu.addAction(QIcon(load_pixmap('settings', '16x16')),
463                                     self.__tr("Settings..."),  self.settingsTriggered)
464
465         self.menu.addSeparator()
466         self.menu.addAction(QIcon(load_pixmap('quit', '16x16')), "Quit", self.quitTriggered)
467         self.tray_icon.setContextMenu(self.menu)
468
469
470
471
472     def settingsTriggered(self):
473         if self.menu is None:
474             return
475
476         self.sendMessage('', '', EVENT_DEVICE_STOP_POLLING)
477 #        sys_conf
478         cur_vers = sys_conf.get('hplip', 'version')
479         self.user_settings.load()
480         installed_time =time.strftime("%d-%m-%Y", time.localtime(self.user_settings.upgrade_last_update_time))
481         if utils.Is_HPLIP_older_version(cur_vers, self.user_settings.latest_available_version):
482             if int(time.time()) < self.user_settings.upgrade_pending_update_time :
483                 postponed_time =time.strftime("%d-%m-%Y", time.localtime(self.user_settings.upgrade_pending_update_time))
484                 upgrade_msg ="HPLIP-%s version was installed on %s.\n\nNew version of HPLIP-%s is available for upgrade. HPLIP upgrade is scheduled on %s." %(cur_vers,installed_time , self.user_settings.latest_available_version, postponed_time)
485             elif self.user_settings.upgrade_last_update_time:
486                 upgrade_msg ="HPLIP-%s version was installed on %s.\n\nNew version of HPLIP-%s is available for upgrade." %(cur_vers,installed_time , self.user_settings.latest_available_version)
487             else:
488                 upgrade_msg ="HPLIP-%s version was installed.\n\nNew version of HPLIP-%s is available for upgrade." %(cur_vers, self.user_settings.latest_available_version)
489         elif self.user_settings.upgrade_last_update_time:
490             upgrade_msg ="HPLIP-%s version was installed on %s."%(cur_vers, installed_time)
491         else: 
492             upgrade_msg ="HPLIP-%s version was installed."%(cur_vers)
493             
494         
495         try:
496             dlg = SystraySettingsDialog(self.menu, self.user_settings.systray_visible,
497                                         self.user_settings.polling, self.user_settings.polling_interval,
498                                         self.user_settings.systray_messages,
499                                         self.user_settings.polling_device_list,
500                                         self.user_settings.upgrade_notify,
501                                         self.user_settings.upgrade_pending_update_time,
502                                         self.user_settings.upgrade_last_update_time,
503                                         upgrade_msg)
504
505
506             if dlg.exec_() == QDialog.Accepted:
507                 self.user_settings.systray_visible = dlg.systray_visible
508                 self.user_settings.systray_messages = dlg.systray_messages
509                 self.user_settings.upgrade_notify = dlg.upgrade_notify
510         
511                 log.debug("HPLIP update  notification = %d"%(self.user_settings.upgrade_notify))
512                 self.user_settings.save()
513
514                 if self.user_settings.systray_visible == SYSTRAY_VISIBLE_SHOW_ALWAYS:
515                     log.debug("Showing...")
516                     self.tray_icon.setVisible(True)
517
518                 else:
519                     log.debug("Waiting to hide...")
520                     QTimer.singleShot(HIDE_INACTIVE_DELAY, self.timeoutHideWhenInactive)
521
522                 self.sendMessage('', '', EVENT_USER_CONFIGURATION_CHANGED)
523
524         finally:
525             self.sendMessage('', '', EVENT_DEVICE_START_POLLING)
526
527
528     def timeoutHideWhenInactive(self):
529         log.debug("Hiding...")
530         if self.user_settings.systray_visible in (SYSTRAY_VISIBLE_HIDE_WHEN_INACTIVE, SYSTRAY_VISIBLE_HIDE_ALWAYS):
531             self.tray_icon.setVisible(False)
532             log.debug("Hidden")
533
534
535     def updateMenu(self):
536         if self.menu is None:
537             return
538         for a in self.menu.actions():
539             try:
540                 a.menu().update()
541             except AttributeError:
542                 continue
543
544
545
546     def trayActivated(self, reason):
547         if reason == QSystemTrayIcon.Context:
548             self.updateMenu()
549
550
551         elif reason == QSystemTrayIcon.DoubleClick:
552             #print "double click"
553             self.toolboxTriggered()
554             pass
555
556         elif reason == QSystemTrayIcon.Trigger:
557             #print "single click"
558             pass
559
560         elif reason == QSystemTrayIcon.MiddleClick:
561             #print "middle click"
562             pass
563
564
565     def messageClicked(self):
566         #print "\nPARENT: message clicked"
567         pass
568
569
570     def quitTriggered(self):
571         log.debug("Exiting")
572         self.sendMessage('', '', EVENT_SYSTEMTRAY_EXIT)
573         self.quit()
574         del self.tray_icon
575
576     def toolboxTriggered(self):
577         try:
578             os.waitpid(-1, os.WNOHANG)
579         except OSError:
580             pass
581
582         # See if it is already running...
583         ok, lock_file = utils.lock_app('hp-toolbox', True)
584
585         if ok: # able to lock, not running...
586             utils.unlock(lock_file)
587
588             path = utils.which('hp-toolbox')
589             if path:
590                 path = os.path.join(path, 'hp-toolbox')
591             else:
592                 self.tray_icon.showMessage(self.__tr("HPLIP Status Service"),
593                                 self.__tr("Unable to locate hp-toolbox on system PATH."),
594                                 QSystemTrayIcon.Critical, TRAY_MESSAGE_DELAY)
595
596                 log.error("Unable to find hp-toolbox on PATH.")
597                 return
598
599             #log.debug(path)
600             log.debug("Running hp-toolbox: hp-toolbox")
601             os.spawnlp(os.P_NOWAIT, path, 'hp-toolbox')
602
603         else: # ...already running, raise it
604             self.sendMessage('', '', EVENT_RAISE_DEVICE_MANAGER, interface='com.hplip.Toolbox')
605
606
607     def sendMessage(self, device_uri, printer_name, event_code, username=prop.username,
608                     job_id=0, title='', pipe_name='', interface='com.hplip.StatusService'):
609         #device.Event(device_uri, printer_name, event_code, username, job_id, title).send_via_dbus(SessionBus(), interface)
610         device.Event(device_uri, printer_name, event_code, username, job_id, title).send_via_dbus(self.session_bus, interface)
611
612
613     def notifierActivated(self, s):
614         m = ''
615         while True:
616             try:
617                 r, w, e = select.select([self.read_pipe], [], [self.read_pipe], 1.0)
618             except select.error:
619                 log.debug("Error in select()")
620                 break
621
622             if e:
623                 log.error("Pipe error: %s" % e)
624                 break
625
626             if r:
627                 m = ''.join([m, os.read(self.read_pipe, self.fmt_size)])
628                 while len(m) >= self.fmt_size:
629                     event = device.Event(*struct.unpack(self.fmt, m[:self.fmt_size]))
630
631                     m = m[self.fmt_size:]
632
633                     if event.event_code == EVENT_USER_CONFIGURATION_CHANGED:
634                         log.debug("Re-reading configuration (EVENT_USER_CONFIGURATION_CHANGED)")
635                         self.user_settings.load()
636                         self.user_settings.debug()
637
638                     elif event.event_code == EVENT_SYSTEMTRAY_EXIT:
639                         self.quit()
640                         return
641
642                     if self.user_settings.systray_visible in \
643                         (SYSTRAY_VISIBLE_SHOW_ALWAYS, SYSTRAY_VISIBLE_HIDE_WHEN_INACTIVE):
644
645                         log.debug("Showing...")
646                         self.tray_icon.setVisible(True)
647
648                         if event.event_code == EVENT_DEVICE_UPDATE_ACTIVE:
649                             if not self.active_icon:
650                                 self.tray_icon.setIcon(self.prop_active_icon)
651                                 self.active_icon = True
652                             continue
653
654                         elif event.event_code == EVENT_DEVICE_UPDATE_INACTIVE:
655                             if self.active_icon:
656                                 self.tray_icon.setIcon(self.prop_icon)
657                                 self.active_icon = False
658                             continue
659
660                         elif event.event_code == EVENT_DEVICE_UPDATE_BLIP:
661                             if not self.active_icon:
662                                 self.tray_icon.setIcon(self.prop_active_icon)
663                                 self.active_icon = True
664                                 QTimer.singleShot(BLIP_DELAY, self.blipTimeout)
665                             continue
666
667                     if self.user_settings.systray_visible in (SYSTRAY_VISIBLE_HIDE_WHEN_INACTIVE, SYSTRAY_VISIBLE_HIDE_ALWAYS):
668                         log.debug("Waiting to hide...")
669                         QTimer.singleShot(HIDE_INACTIVE_DELAY, self.timeoutHideWhenInactive)
670
671                     if event.event_code <= EVENT_MAX_USER_EVENT:
672                         self.addDevice(event.device_uri)
673                         self.setMenu()
674
675                         if self.tray_icon.supportsMessages():
676
677                             log.debug("Tray icon message:")
678                             event.debug()
679
680                             error_state = STATUS_TO_ERROR_STATE_MAP.get(event.event_code, ERROR_STATE_CLEAR)
681                             desc = device.queryString(event.event_code)
682
683                             show_message = False
684                             if self.user_settings.systray_messages == SYSTRAY_MESSAGES_SHOW_ALL: # OK, Busy
685                                 show_message = True
686
687                             elif self.user_settings.systray_messages in (SYSTRAY_MESSAGES_SHOW_ERRORS_AND_WARNINGS, SYSTRAY_MESSAGES_SHOW_ERRORS_ONLY):
688                                 if error_state == ERROR_STATE_ERROR:
689                                     show_message = True
690
691                                 elif self.user_settings.systray_messages == SYSTRAY_MESSAGES_SHOW_ERRORS_AND_WARNINGS and \
692                                     error_state in (ERROR_STATE_WARNING, ERROR_STATE_LOW_SUPPLIES, ERROR_STATE_LOW_PAPER):
693
694                                     show_message = True
695
696                             if event.printer_name:
697                                 d = QString(event.printer_name)
698                             else:
699                                 back_end, is_hp, bus, model, serial, dev_file, host, zc, port = \
700                                                 device.parseDeviceURI(event.device_uri)
701
702                                 if bus == 'usb':
703                                     idd = serial
704                                 elif bus == 'net':
705                                     idd = host
706                                 elif bus == 'par':
707                                     idd = dev_file
708                                 else:
709                                     idd = 'unknown'
710
711                                 self.model = models.normalizeModelUIName(model)
712
713                                 if back_end == 'hp':
714                                     d = self.__tr("%1 Printer (%2)").arg(model).arg(idd)
715
716                                 elif back_end == 'hpaio':
717                                     d = self.__tr("%1 Scanner (%2)").arg(model).arg(idd)
718
719                                 elif back_end == 'hpfax':
720                                     d = self.__tr("%1 Fax (%2)").arg(model).arg(idd)
721
722                                 else:
723                                     d = self.__tr("%1 (%2)").arg(model).arg(idd)
724
725                             if show_message:
726                                 if have_pynotify and pynotify.init("hplip"): # Use libnotify/pynotify
727                                     icon, urgency = ERROR_STATE_TO_ICON_AND_URGENCY_PYNOTIFY.get(error_state,
728                                         (getPynotifyIcon('info'), pynotify.URGENCY_NORMAL))
729
730                                     if event.job_id and event.title:
731                                         msg = "%s\n%s: %s\n(%s/%s)" % (unicode(d), desc, event.title, event.username, event.job_id)
732                                         log.debug("Notify: uri=%s desc=%s title=%s user=%s job_id=%d code=%d" %
733                                                 (event.device_uri, desc, event.title, event.username, event.job_id, event.event_code))
734                                     else:
735                                         msg = "%s\n%s (%s)" % (unicode(d), desc, event.event_code)
736                                         log.debug("Notify: uri=%s desc=%s code=%d" % (event.device_uri, desc, event.event_code))
737
738                                     n = pynotify.Notification("HPLIP Device Status", msg, icon)
739                                     n.set_urgency(urgency)
740
741                                     if error_state == ERROR_STATE_ERROR:
742                                         n.set_timeout(pynotify.EXPIRES_NEVER)
743                                     else:
744                                         n.set_timeout(TRAY_MESSAGE_DELAY)
745
746                                     n.show()
747
748                                 else: # Use "standard" message bubbles
749                                     icon = ERROR_STATE_TO_ICON.get(error_state, QSystemTrayIcon.Information)
750                                     if event.job_id and event.title:
751                                         log.debug("Bubble: uri=%s desc=%s title=%s user=%s job_id=%d code=%d" %
752                                                 (event.device_uri, desc, event.title, event.username, event.job_id, event.event_code))
753                                         self.tray_icon.showMessage(self.__tr("HPLIP Device Status"),
754                                             QString("%1\n%2: %3\n(%4/%5)").\
755                                             arg(d).\
756                                             arg(desc).arg(event.title).\
757                                             arg(event.username).arg(event.job_id),
758                                             icon, TRAY_MESSAGE_DELAY)
759
760                                     else:
761                                         log.debug("Bubble: uri=%s desc=%s code=%d" % (event.device_uri, desc, event.event_code))
762                                         self.tray_icon.showMessage(self.__tr("HPLIP Device Status"),
763                                             QString("%1\n%2 (%3)").arg(d).\
764                                             arg(desc).arg(event.event_code),
765                                             icon, TRAY_MESSAGE_DELAY)
766
767             else:
768                 break
769
770
771     def blipTimeout(self):
772         if self.active_icon:
773             self.tray_icon.setIcon(self.prop_icon)
774             self.active_icon = False
775
776
777
778     def __tr(self, s, c=None):
779         return QApplication.translate("SystemTray", s, c, QApplication.UnicodeUTF8)
780
781
782
783 def run(read_pipe):
784     log.set_module("hp-systray(qt4)")
785     log.debug("PID=%d" % os.getpid())
786
787     app = SystemTrayApp(sys.argv, read_pipe)
788     app.setQuitOnLastWindowClosed(False) # If not set, settings dlg closes app
789
790     i = 0
791     while i < 60:
792         if QSystemTrayIcon.isSystemTrayAvailable():
793             break
794         time.sleep(1.0)
795         i += 1
796
797     if not QSystemTrayIcon.isSystemTrayAvailable():
798         FailureUI(None,
799             QApplication.translate("SystemTray",
800             "<b>No system tray detected on this system.</b><p>Unable to start, exiting.</p>",
801             None, QApplication.UnicodeUTF8),
802             QApplication.translate("SystemTray", "HPLIP Status Service",
803             None, QApplication.UnicodeUTF8))
804     else:
805         notifier = QSocketNotifier(read_pipe, QSocketNotifier.Read)
806         QObject.connect(notifier, SIGNAL("activated(int)"), app.notifierActivated)
807
808         app.exec_()
809
810