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