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