2 # -*- coding: utf-8 -*-
4 # (c) Copyright 2003-2008 Hewlett-Packard Development Company, L.P.
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.
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.
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
20 # Authors: Don Welch, Torsten Marek
33 from base import device, utils
34 from ui_utils import load_pixmap
40 log.error("Python bindings for Qt3 not found. Exiting!")
46 import ctypes.util as cu
48 log.error("Qt3 version of hp-systray requires python-ctypes module. Exiting!")
54 from dbus import SessionBus, lowlevel
56 log.error("Python bindings for dbus not found. Exiting!")
70 TrayIcon_Information = 2
73 UPGRADE_CHECK_DELAY=24*60*60*1000 #1 day
76 class BalloonTip(QDialog):
77 def __init__(self, msg_icon, title, msg, tray_icon):
78 QDialog.__init__(self, tray_icon, "BalloonTip", False,
79 Qt.WStyle_StaysOnTop | Qt.WStyle_Customize | Qt.WStyle_NoBorder | Qt.WStyle_Tool | Qt.WX11BypassWM)
82 self.bubbleActive = False
84 QObject.connect(tray_icon, SIGNAL("destroyed()"), self.close)
86 self.titleLabel = QLabel(self)
87 self.titleLabel.installEventFilter(self)
88 self.titleLabel.setText(title)
89 f = self.titleLabel.font()
91 self.titleLabel.setFont(f)
92 self.titleLabel.setTextFormat(Qt.PlainText) # to maintain compat with windows
94 self.closeButton = QPushButton(self)
95 self.closeButton.setPixmap(load_pixmap('close', '16x16'))
96 self.closeButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
97 self.closeButton.setFixedSize(18, 18)
98 QObject.connect(self.closeButton, SIGNAL("clicked()"), self.close)
100 self.msgLabel = QLabel(self)
101 self.msgLabel.installEventFilter(self)
102 self.msgLabel.setText(msg)
103 self.msgLabel.setTextFormat(Qt.PlainText)
104 self.msgLabel.setAlignment(Qt.AlignTop | Qt.AlignLeft)
106 layout = QGridLayout(self)
107 if msg_icon is not None:
108 self.iconLabel = QLabel(self)
109 self.iconLabel.setPixmap(msg_icon)
110 self.iconLabel.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
111 self.iconLabel.setMargin(2)
112 layout.addWidget(self.iconLabel, 0, 0)
113 layout.addWidget(self.titleLabel, 0, 1)
115 layout.addMultiCellWidget(self.titleLabel, 0, 1, 0, 2)
117 layout.addWidget(self.closeButton, 0, 3)
118 layout.addMultiCellWidget(self.msgLabel, 1, 1, 0, 3)
120 self.setPaletteBackgroundColor(QColor(255, 255, 224))
123 def resizeEvent(self, e):
124 QWidget.resizeEvent(self, e)
127 def mousePressEvent(self, e):
129 if e.button() == Qt.LeftButton:
133 def timerEvent(self, e):
134 if e.timerId() == self.timerId:
135 self.killTimer(self.timerId)
141 QWidget.timerEvent(self, e)
144 def closeEvent(self, event):
145 self.bubbleActive = False
149 def balloon(self, pos, msecs, showArrow):
150 if self.bubbleActive:
153 self.bubbleActive = True
155 scr = QApplication.desktop().screenGeometry(pos)
158 if pos.y() + ao > scr.bottom():
159 self.move(pos.x()-sh.width(), pos.y()-sh.height()-ao)
161 self.move(pos.x()-sh.width(), pos.y()+ao)
164 self.timerId = self.startTimer(msecs)
169 def showBalloon(msg_icon, title, msg, tray_icon, pos, timeout, showArrow=True):
173 theBalloonTip = BalloonTip(msg_icon, msg, title, tray_icon)
178 theBalloonTip.balloon(pos, timeout, showArrow)
183 if theBalloonTip is None:
192 class SystrayIcon(QLabel):
193 """ On construction, you have to supply a QPixmap instance holding the
194 application icon. The pixmap should not be bigger than 32x32,
195 preferably 22x22. Currently, no check is made.
197 The class can emits two signals:
198 Leftclick on icon: activated()
199 Rightclick on icon: contextMenuRequested(const QPoint&)
201 Based on code: (C) 2004 Torsten Marek
202 License: Public domain
205 def __init__(self, icon, parent=None, name=""):
206 QLabel.__init__(self, parent, name, Qt.WMouseNoMask | Qt.WRepaintNoErase |
207 Qt.WType_TopLevel | Qt.WStyle_Customize |
208 Qt.WStyle_NoBorder | Qt.WStyle_StaysOnTop)
210 self.setMinimumSize(22, 22)
211 self.setBackgroundMode(Qt.X11ParentRelative)
212 self.setBackgroundOrigin(QWidget.WindowOrigin)
214 self.libX11 = c.cdll.LoadLibrary(cu.find_library('X11'))
216 # get all functions, set arguments + return types
217 self.XternAtom = self.libX11.XInternAtom
218 self.XternAtom.argtypes = [c.c_void_p, c.c_char_p, c.c_int]
220 XSelectInput = self.libX11.XSelectInput
221 XSelectInput.argtypes = [c.c_void_p, c.c_int, c.c_long]
223 XUngrabServer = self.libX11.XUngrabServer
224 XUngrabServer.argtypes = [c.c_void_p]
226 XFlush = self.libX11.XFlush
227 XFlush.argtypes = [c.c_void_p]
230 _fields_ = [("b", c.c_char * 20),
231 ("s", c.c_short * 10),
234 class XClientMessageEvent(c.Structure):
235 _fields_ = [("type", c.c_int),
236 ("serial", c.c_ulong),
237 ("send_event", c.c_int),
238 ("display", c.c_void_p),
240 ("message_type", c.c_int),
244 XSendEvent = self.libX11.XSendEvent
245 XSendEvent.argtypes = [c.c_void_p, c.c_int, c.c_int, c.c_long, c.c_void_p]
247 XSync = self.libX11.XSync
248 XSync.argtypes = [c.c_void_p, c.c_int]
250 XChangeProperty = self.libX11.XChangeProperty
251 XChangeProperty.argtypes = [c.c_void_p, c.c_long, c.c_int, c.c_int,
252 c.c_int, c.c_int, c.c_char_p, c.c_int]
254 dpy = int(qt_xdisplay())
255 trayWin = self.winId()
259 managerWin = self.locateTray(dpy)
266 # Make sure KDE puts the icon in the system tray
267 class data2(c.Union):
268 _fields_ = [("i", c.c_int, 32),
273 pk = c.cast(c.pointer(k), c.c_char_p)
275 r = self.XternAtom(dpy, "KWM_DOCKWINDOW", 0)
276 XChangeProperty(dpy, trayWin, r, r, 32, 0, pk, 1)
278 r = self.XternAtom(dpy, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR", 0)
279 XChangeProperty(dpy, trayWin, r, 33, 32, 0, pk, 1)
282 # set StructureNotifyMask (1L << 17)
283 XSelectInput(dpy, managerWin, 1L << 17)
289 # send "SYSTEM_TRAY_OPCODE_REQUEST_DOCK to managerWin
291 k.l = (0, # CurrentTime
296 ev = XClientMessageEvent(33, #type: ClientMessage
300 managerWin, # systray manager
301 self.XternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE", 0), # message type
304 XSendEvent(dpy, managerWin, 0, 0, c.addressof(ev))
308 self.setAlignment(Qt.AlignHCenter)
311 QToolTip.add(self, parent.caption())
315 def locateTray(self, dpy):
316 # get systray window (holds _NET_SYSTEM_TRAY_S<screen> atom)
317 self.XScreenNumberOfScreen = self.libX11.XScreenNumberOfScreen
318 self.XScreenNumberOfScreen.argtypes = [c.c_void_p]
320 XDefaultScreenOfDisplay = self.libX11.XDefaultScreenOfDisplay
321 XDefaultScreenOfDisplay.argtypes = [c.c_void_p]
322 XDefaultScreenOfDisplay.restype = c.c_void_p
324 XGetSelectionOwner = self.libX11.XGetSelectionOwner
325 XGetSelectionOwner.argtypes = [c.c_void_p, c.c_int]
327 XGrabServer = self.libX11.XGrabServer
328 XGrabServer.argtypes = [c.c_void_p]
330 iscreen = self.XScreenNumberOfScreen(XDefaultScreenOfDisplay(dpy))
331 selectionAtom = self.XternAtom(dpy, "_NET_SYSTEM_TRAY_S%i" % iscreen, 0)
334 managerWin = XGetSelectionOwner(dpy, selectionAtom)
338 def setTooltipText(self, text):
339 QToolTip.add(self, text)
342 def mousePressEvent(self, e):
343 if e.button() == Qt.RightButton:
344 self.emit(PYSIGNAL("contextMenuRequested(const QPoint&)"), (e.globalPos(),))
346 elif e.button() == Qt.LeftButton:
347 self.emit(PYSIGNAL("activated()"), ())
350 def supportsMessages(self):
354 def showMessage(self, title, msg, icon, msecs):
355 if have_pynotify and pynotify.init("hplip"):
356 n = pynotify.Notification(title, msg, icon)
360 g = self.mapToGlobal(QPoint(0, 0))
361 showBalloon(icon, msg, title, self,
362 QPoint(g.x() + self.width()/2, g.y() + self.height()/2), msecs)
366 class TitleItem(QCustomMenuItem):
367 def __init__(self, icon, text):
368 QCustomMenuItem.__init__(self)
370 self.font.setBold(True)
371 self.pen = QPen(Qt.black)
372 self.bg_color = qApp.palette().color(QPalette.Active, QColorGroup.Background)
376 def paint(self, painter, cg, act, enabled, x, y, w, h):
377 painter.setPen(self.pen)
378 painter.setFont(self.font)
379 painter.setBackgroundColor(self.bg_color)
380 painter.eraseRect(x, y, w, h)
381 painter.drawPixmap(2, 2, self.icon, 0, 0, -1, -1)
382 painter.drawText(x, y, w, h, Qt.AlignLeft | Qt.AlignVCenter | Qt.ShowPrefix | Qt.DontClip, self.text)
385 return QFontMetrics(self.font).size(Qt.AlignLeft | Qt.AlignVCenter | Qt.ShowPrefix | Qt.DontClip, self.text)
389 class SystemTrayApp(QApplication):
390 def __init__(self, args, read_pipe):
391 QApplication.__init__(self, args)
393 self.read_pipe = read_pipe
394 self.fmt = "80s80sI32sI80sf"
395 self.fmt_size = struct.calcsize(self.fmt)
397 self.user_settings = utils.UserSettings()
398 self.user_settings.load()
399 self.user_settings.debug()
401 self.tray_icon = SystrayIcon(load_pixmap("hp_logo", "32x32", (22, 22)))
402 self.menu = QPopupMenu()
404 title_item = TitleItem(load_pixmap('hp_logo', '16x16', (16, 16)), "HP Status Service")
405 i = self.menu.insertItem(title_item)
406 self.menu.setItemEnabled(i, False)
408 self.menu.insertSeparator()
410 self.menu.insertItem(self.tr("HP Device Manager..."), self.toolbox_triggered)
413 #icon2 = QIconSet(load_pixmap('settings', '16x16'))
414 #self.menu.insertItem(icon2, self.tr("Options..."), self.preferences_triggered)
416 self.menu.insertSeparator()
418 icon3 = QIconSet(load_pixmap('quit', '16x16'))
419 self.menu.insertItem(icon3, self.tr("Quit"), self.quit_triggered)
421 self.tray_icon.show()
423 notifier = QSocketNotifier(self.read_pipe, QSocketNotifier.Read)
424 QObject.connect(notifier, SIGNAL("activated(int)"), self.notifier_activated)
426 QObject.connect(self.tray_icon, PYSIGNAL("contextMenuRequested(const QPoint&)"), self.menu_requested)
428 self.icon_info = load_pixmap('info', '16x16')
429 self.icon_warn = load_pixmap('warning', '16x16')
430 self.icon_error = load_pixmap('error', '16x16')
432 self.handle_hplip_updation()
433 self.timer = QTimer()
434 self.timer.connect(self.timer,SIGNAL("timeout()"),self.handle_hplip_updation)
435 self.timer.start(UPGRADE_CHECK_DELAY)
437 self.ERROR_STATE_TO_ICON = {
438 ERROR_STATE_CLEAR: self.icon_info,
439 ERROR_STATE_OK: self.icon_info,
440 ERROR_STATE_WARNING: self.icon_warn,
441 ERROR_STATE_ERROR: self.icon_error,
442 ERROR_STATE_LOW_SUPPLIES: self.icon_warn,
443 ERROR_STATE_BUSY: self.icon_warn,
444 ERROR_STATE_LOW_PAPER: self.icon_warn,
445 ERROR_STATE_PRINTING: self.icon_info,
446 ERROR_STATE_SCANNING: self.icon_info,
447 ERROR_STATE_PHOTOCARD: self.icon_info,
448 ERROR_STATE_FAXING: self.icon_info,
449 ERROR_STATE_COPYING: self.icon_info,
453 def menu_requested(self, pos):
457 def quit_triggered(self):
458 device.Event('', '', EVENT_SYSTEMTRAY_EXIT).send_via_dbus(SessionBus())
462 def toolbox_triggered(self):
464 os.waitpid(-1, os.WNOHANG)
468 # See if it is already running...
469 ok, lock_file = utils.lock_app('hp-toolbox', True)
471 if ok: # able to lock, not running...
472 utils.unlock(lock_file)
474 path = utils.which('hp-toolbox')
476 path = os.path.join(path, 'hp-toolbox')
478 log.error("Unable to find hp-toolbox on PATH.")
480 self.tray_icon.showMessage("HPLIP Status Service",
481 self.__tr("Unable to locate hp-toolbox on system PATH."),
482 self.icon_error, 5000)
487 os.spawnlp(os.P_NOWAIT, path, 'hp-toolbox')
489 else: # ...already running, raise it
490 device.Event('', '', EVENT_RAISE_DEVICE_MANAGER).send_via_dbus(SessionBus(), 'com.hplip.Toolbox')
493 def preferences_triggered(self):
494 #print "\nPARENT: prefs!"
498 def notifier_activated(self, s):
501 ready = select.select([self.read_pipe], [], [], 1.0)
504 m = ''.join([m, os.read(self.read_pipe, self.fmt_size)])
505 if len(m) == self.fmt_size:
506 event = device.Event(*struct.unpack(self.fmt, m))
508 if event.event_code > EVENT_MAX_USER_EVENT:
511 desc = device.queryString(event.event_code)
512 #print "BUBBLE:", event.device_uri, event.event_code, event.username
513 error_state = STATUS_TO_ERROR_STATE_MAP.get(event.event_code, ERROR_STATE_CLEAR)
514 icon = self.ERROR_STATE_TO_ICON.get(error_state, self.icon_info)
516 if self.tray_icon.supportsMessages():
517 if event.job_id and event.title:
518 self.tray_icon.showMessage("HPLIP Device Status",
519 QString("%1\n%2\n%3\n(%4/%5/%6)").\
520 arg(event.device_uri).arg(event.event_code).\
521 arg(desc).arg(event.username).arg(event.job_id).arg(event.title),
524 self.tray_icon.showMessage("HPLIP Device Status",
525 QString("%1\n%2\n%3").arg(event.device_uri).\
526 arg(event.event_code).arg(desc),
532 def handle_hplip_updation(self):
533 log.debug("handle_hplip_updation upgrade_notify =%d"%(self.user_settings.upgrade_notify))
534 path = utils.which('hp-upgrade')
535 if self.user_settings.upgrade_notify is False:
536 log.debug("upgrade notification is disabled in systray ")
538 path = os.path.join(path, 'hp-upgrade')
539 log.debug("Running hp-upgrade: %s " % (path))
540 # this just updates the available version in conf file. But won't notify
541 os.spawnlp(os.P_NOWAIT, path, 'hp-upgrade', '--check')
545 current_time = time.time()
547 if int(current_time) > self.user_settings.upgrade_pending_update_time:
548 path = utils.which('hp-upgrade')
550 path = os.path.join(path, 'hp-upgrade')
551 log.debug("Running hp-upgrade: %s " % (path))
552 os.spawnlp(os.P_NOWAIT, path, 'hp-upgrade', '--notify')
555 log.error("Unable to find hp-upgrade --notify on PATH.")
557 log.debug("upgrade schedule time is not yet completed. schedule time =%d current time =%d " %(self.user_settings.upgrade_pending_update_time, current_time))
561 def __tr(self,s,c = None):
562 return qApp.translate("SystemTrayApp",s,c)
568 log.set_module("hp-systray(qt3)")
570 app = SystemTrayApp(sys.argv, read_pipe)
572 notifier = QSocketNotifier(read_pipe, QSocketNotifier.Read)
573 QObject.connect(notifier, SIGNAL("activated(int)"), app.notifier_activated)
577 except KeyboardInterrupt:
578 log.debug("Ctrl-C: Exiting...")