## src/systraywin32.py ## ## Contributors for this file: ## - Yann Le Boulanger ## - Nikos Kouremenos ## - Dimitur Kirov ## ## code initially based on ## http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/334779 ## with some ideas/help from pysystray.sf.net ## ## Copyright (C) 2003-2004 Yann Le Boulanger ## Vincent Hanquez ## Copyright (C) 2005 Yann Le Boulanger ## Vincent Hanquez ## Nikos Kouremenos ## Dimitur Kirov ## Travis Shirk ## Norman Rasmussen ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published ## by the Free Software Foundation; version 2 only. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## import win32gui import pywintypes import win32con # winapi constants import systray import gtk import os WM_TASKBARCREATED = win32gui.RegisterWindowMessage('TaskbarCreated') WM_TRAYMESSAGE = win32con.WM_USER + 20 from common import gajim from common import i18n _ = i18n._ APP = i18n.APP gtk.glade.bindtextdomain(APP, i18n.DIR) gtk.glade.textdomain(APP) GTKGUI_GLADE = 'gtkgui.glade' class SystrayWINAPI: def __init__(self, gtk_window): self._window = gtk_window self._hwnd = gtk_window.window.handle self._message_map = {} self.notify_icon = None # Sublass the window and inject a WNDPROC to process messages. self._oldwndproc = win32gui.SetWindowLong(self._hwnd, win32con.GWL_WNDPROC, self._wndproc) def add_notify_icon(self, menu, hicon=None, tooltip=None): """ Creates a notify icon for the gtk window. """ if not self.notify_icon: if not hicon: hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION) self.notify_icon = NotifyIcon(self._hwnd, hicon, tooltip) # Makes redraw if the taskbar is restarted. self.message_map({WM_TASKBARCREATED: self.notify_icon._redraw}) def message_map(self, msg_map={}): """ Maps message processing to callback functions ala win32gui. """ if msg_map: if self._message_map: duplicatekeys = [key for key in msg_map.keys() if self._message_map.has_key(key)] for key in duplicatekeys: new_value = msg_map[key] if isinstance(new_value, list): raise TypeError('Dict cannot have list values') value = self._message_map[key] if new_value != value: new_value = [new_value] if isinstance(value, list): value += new_value else: value = [value] + new_value msg_map[key] = value self._message_map.update(msg_map) def message_unmap(self, msg, callback=None): if self._message_map.has_key(msg): if callback: cblist = self._message_map[key] if isinstance(cblist, list): if not len(cblist) < 2: for i in xrange(len(cblist)): if cblist[i] == callback: del self._message_map[key][i] return del self._message_map[key] def remove_notify_icon(self): """ Removes the notify icon. """ if self.notify_icon: self.notify_icon.remove() self.notify_icon = None def remove(self, *args): """ Unloads the extensions. """ self._message_map = {} self.remove_notify_icon() self = None def show_balloon_tooltip(self, title, text, timeout=10, icon=win32gui.NIIF_NONE): """ Shows a baloon tooltip. """ if not self.notify_icon: self.add_notifyicon() self.notify_icon.show_balloon(title, text, timeout, icon) def _wndproc(self, hwnd, msg, wparam, lparam): """ A WINDPROC to process window messages. """ if self._message_map.has_key(msg): callback = self._message_map[msg] if isinstance(callback, list): for cb in callback: cb(hwnd, msg, wparam, lparam) else: callback(hwnd, msg, wparam, lparam) return win32gui.CallWindowProc(self._oldwndproc, hwnd, msg, wparam, lparam) class NotifyIcon: def __init__(self, hwnd, hicon, tooltip=None): self._hwnd = hwnd self._id = 0 self._flags = win32gui.NIF_MESSAGE | win32gui.NIF_ICON self._callbackmessage = WM_TRAYMESSAGE self._hicon = hicon try: win32gui.Shell_NotifyIcon(win32gui.NIM_ADD, self._get_nid()) except pywintypes.error: pass if tooltip: self.set_tooltip(tooltip) def _get_nid(self): """ Function to initialise & retrieve the NOTIFYICONDATA Structure. """ nid = [self._hwnd, self._id, self._flags, self._callbackmessage, self._hicon] if not hasattr(self, '_tip'): self._tip = '' nid.append(self._tip) if not hasattr(self, '_info'): self._info = '' nid.append(self._info) if not hasattr(self, '_timeout'): self._timeout = 0 nid.append(self._timeout) if not hasattr(self, '_infotitle'): self._infotitle = '' nid.append(self._infotitle) if not hasattr(self, '_infoflags'):self._infoflags = win32gui.NIIF_NONE nid.append(self._infoflags) return tuple(nid) def remove(self): """ Removes the tray icon. """ try: win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, self._get_nid()) except pywintypes.error: pass def set_tooltip(self, tooltip): """ Sets the tray icon tooltip. """ self._flags = self._flags | win32gui.NIF_TIP self._tip = tooltip try: win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, self._get_nid()) except pywintypes.error: pass def show_balloon(self, title, text, timeout=10, icon=win32gui.NIIF_NONE): """ Shows a balloon tooltip from the tray icon. """ self._flags = self._flags | win32gui.NIF_INFO self._infotitle = title self._info = text self._timeout = timeout * 1000 self._infoflags = icon try: win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, self._get_nid()) except pywintypes.error: pass def _redraw(self, *args): """ Redraws the tray icon. """ self.remove() try: win32gui.Shell_NotifyIcon(win32gui.NIM_ADD, self._get_nid()) except pywintypes.error: pass class SystrayWin32(systray.Systray): def __init__(self): # Note: gtk window must be realized before installing extensions. systray.Systray.__init__(self) self.jids = [] self.status = 'offline' self.xml = gtk.glade.XML(GTKGUI_GLADE, 'systray_context_menu', APP) self.systray_context_menu = self.xml.get_widget('systray_context_menu') self.added_hide_menuitem = False # self.tray_ico_imgs = self.load_icos() w = gtk.Window() # just a window to pass w.realize() # realize it so gtk window exists self.systray_winapi = SystrayWINAPI(w) self.xml.signal_autoconnect(self) # Set up the callback messages self.systray_winapi.message_map({ WM_TRAYMESSAGE: self.on_clicked }) def show_icon(self): #self.systray_winapi.add_notify_icon(self.systray_context_menu, tooltip = 'Gajim') #self.systray_winapi.notify_icon.menu = self.systray_context_menu # do not remove set_img does both above. # maybe I can only change img without readding # the notify icon? HOW?? self.set_img() def hide_icon(self): self.systray_winapi.remove() def on_clicked(self, hwnd, message, wparam, lparam): if lparam == win32con.WM_RBUTTONUP: # Right click self.make_menu() self.systray_winapi.notify_icon.menu.popup(None, None, None, 0, 0) elif lparam == win32con.WM_MBUTTONUP: # Middle click self.on_middle_click() elif lparam == win32con.WM_LBUTTONUP: # Left click self.on_left_click() def add_jid(self, jid, account, typ): systray.Systray.add_jid(self, jid, account, typ) nb = gajim.interface.roster.nb_unread for acct in gajim.connections: # in chat / groupchat windows for kind in ('chats', 'gc'): jids = gajim.interface.instances[acct][kind] for jid in jids: if jid != 'tabbed': nb += jids[jid].nb_unread[jid] text = i18n.ngettext( 'Gajim - %d unread message', 'Gajim - %d unread messages', nb, nb, nb) self.systray_winapi.notify_icon.set_tooltip(text) def remove_jid(self, jid, account, typ): systray.Systray.remove_jid(self, jid, account, typ) nb = gajim.interface.roster.nb_unread for acct in gajim.connections: # in chat / groupchat windows for kind in ('chats', 'gc'): for jid in gajim.interface.instances[acct][kind]: if jid != 'tabbed': nb += gajim.interface.instances[acct][kind][jid].nb_unread[jid] if nb > 0: text = i18n.ngettext( 'Gajim - %d unread message', 'Gajim - %d unread messages', nb, nb, nb) else: text = 'Gajim' self.systray_winapi.notify_icon.set_tooltip(text) def set_img(self): self.tray_ico_imgs = self.load_icos() #FIXME: do not do this here # see gajim.interface.roster.reload_jabber_state_images() to merge self.systray_winapi.remove_notify_icon() if len(self.jids) > 0: state = 'message' else: state = self.status hicon = self.tray_ico_imgs[state] self.systray_winapi.add_notify_icon(self.systray_context_menu, hicon, 'Gajim') self.systray_winapi.notify_icon.menu = self.systray_context_menu def load_icos(self): '''load .ico files and return them to a dic of SHOW --> img_obj''' iconset = str(gajim.config.get('iconset')) if not iconset: iconset = 'dcraven' imgs = {} path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16', 'icos') # icon folder for missing icons path_dcraven_iconset = os.path.join(gajim.DATA_DIR, 'iconsets', 'dcraven', '16x16', 'icos') states_list = gajim.SHOW_LIST # trayicon apart from show holds message state too states_list.append('message') for state in states_list: path_to_ico = os.path.join(path, state + '.ico') if not os.path.isfile(path_to_ico): # fallback to dcraven iconset path_to_ico = os.path.join(path_dcraven_iconset, state + '.ico') if os.path.exists(path_to_ico): hinst = win32gui.GetModuleHandle(None) img_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE image = win32gui.LoadImage(hinst, path_to_ico, win32con.IMAGE_ICON, 0, 0, img_flags) imgs[state] = image return imgs