root/branches/gajim_0.9/src/systraywin32.py

Revision 4747, 10.1 kB (checked in by nk, 3 years ago)

margin 80

  • Property svn:eol-style set to LF
Line 
1## src/systraywin32.py
2##
3## Contributors for this file:
4##      - Yann Le Boulanger <asterix@lagaule.org>
5##      - Nikos Kouremenos <kourem@gmail.com>
6##      - Dimitur Kirov <dkirov@gmail.com>
7##
8## code initially based on
9## http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/334779
10## with some ideas/help from pysystray.sf.net
11##
12## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
13##                         Vincent Hanquez <tab@snarc.org>
14## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
15##                    Vincent Hanquez <tab@snarc.org>
16##                    Nikos Kouremenos <nkour@jabber.org>
17##                    Dimitur Kirov <dkirov@gmail.com>
18##                    Travis Shirk <travis@pobox.com>
19##                    Norman Rasmussen <norman@rasmussen.co.za>
20##
21## This program is free software; you can redistribute it and/or modify
22## it under the terms of the GNU General Public License as published
23## by the Free Software Foundation; version 2 only.
24##
25## This program is distributed in the hope that it will be useful,
26## but WITHOUT ANY WARRANTY; without even the implied warranty of
27## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28## GNU General Public License for more details.
29##
30
31
32import win32gui
33import pywintypes
34import win32con # winapi constants
35import systray
36import gtk
37import os
38
39WM_TASKBARCREATED = win32gui.RegisterWindowMessage('TaskbarCreated')
40WM_TRAYMESSAGE = win32con.WM_USER + 20
41
42from common import gajim
43from common import i18n
44_ = i18n._
45APP = i18n.APP
46gtk.glade.bindtextdomain(APP, i18n.DIR)
47gtk.glade.textdomain(APP)
48
49GTKGUI_GLADE = 'gtkgui.glade'
50
51class SystrayWINAPI:
52        def __init__(self, gtk_window):
53                self._window = gtk_window
54                self._hwnd = gtk_window.window.handle
55                self._message_map = {}
56
57                self.notify_icon = None           
58
59                # Sublass the window and inject a WNDPROC to process messages.
60                self._oldwndproc = win32gui.SetWindowLong(self._hwnd,
61                        win32con.GWL_WNDPROC, self._wndproc)
62
63
64        def add_notify_icon(self, menu, hicon=None, tooltip=None):
65                """ Creates a notify icon for the gtk window. """
66                if not self.notify_icon:
67                        if not hicon:
68                                hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
69                        self.notify_icon = NotifyIcon(self._hwnd, hicon, tooltip)
70
71                        # Makes redraw if the taskbar is restarted.   
72                        self.message_map({WM_TASKBARCREATED: self.notify_icon._redraw})
73
74
75        def message_map(self, msg_map={}):
76                """ Maps message processing to callback functions ala win32gui. """
77                if msg_map:
78                        if self._message_map:
79                                duplicatekeys = [key for key in msg_map.keys()
80                                                                if self._message_map.has_key(key)]
81                               
82                                for key in duplicatekeys:
83                                        new_value = msg_map[key]
84                                       
85                                        if isinstance(new_value, list):
86                                                raise TypeError('Dict cannot have list values')
87                                       
88                                        value = self._message_map[key]
89                                       
90                                        if new_value != value:
91                                                new_value = [new_value]
92                                               
93                                                if isinstance(value, list):
94                                                        value += new_value
95                                                else:
96                                                        value = [value] + new_value
97                                               
98                                                msg_map[key] = value
99                        self._message_map.update(msg_map)
100
101        def message_unmap(self, msg, callback=None):
102                if self._message_map.has_key(msg):
103                        if callback:
104                                cblist = self._message_map[key]
105                                if isinstance(cblist, list):
106                                        if not len(cblist) < 2:
107                                                for i in xrange(len(cblist)):
108                                                        if cblist[i] == callback:
109                                                                del self._message_map[key][i]
110                                                                return
111                        del self._message_map[key]
112
113        def remove_notify_icon(self):
114                """ Removes the notify icon. """
115                if self.notify_icon:
116                        self.notify_icon.remove()
117                        self.notify_icon = None
118
119        def remove(self, *args):
120                """ Unloads the extensions. """
121                self._message_map = {}
122                self.remove_notify_icon()
123                self = None
124
125        def show_balloon_tooltip(self, title, text, timeout=10,
126                                                        icon=win32gui.NIIF_NONE):
127                """ Shows a baloon tooltip. """
128                if not self.notify_icon:
129                        self.add_notifyicon()
130                self.notify_icon.show_balloon(title, text, timeout, icon)
131
132        def _wndproc(self, hwnd, msg, wparam, lparam):
133                """ A WINDPROC to process window messages. """
134                if self._message_map.has_key(msg):
135                        callback = self._message_map[msg]
136                        if isinstance(callback, list):
137                                for cb in callback:
138                                        cb(hwnd, msg, wparam, lparam)
139                        else:
140                                callback(hwnd, msg, wparam, lparam)
141
142                return win32gui.CallWindowProc(self._oldwndproc, hwnd, msg, wparam,
143                                                                        lparam)
144                                                                       
145
146class NotifyIcon:
147
148        def __init__(self, hwnd, hicon, tooltip=None):
149                self._hwnd = hwnd
150                self._id = 0
151                self._flags = win32gui.NIF_MESSAGE | win32gui.NIF_ICON
152                self._callbackmessage = WM_TRAYMESSAGE
153                self._hicon = hicon
154
155                try:
156                        win32gui.Shell_NotifyIcon(win32gui.NIM_ADD, self._get_nid())
157                except pywintypes.error:
158                        pass
159                if tooltip: self.set_tooltip(tooltip)
160
161
162        def _get_nid(self):
163                """ Function to initialise & retrieve the NOTIFYICONDATA Structure. """
164                nid = [self._hwnd, self._id, self._flags, self._callbackmessage, self._hicon]
165
166                if not hasattr(self, '_tip'): self._tip = ''
167                nid.append(self._tip)
168
169                if not hasattr(self, '_info'): self._info = ''
170                nid.append(self._info)
171                       
172                if not hasattr(self, '_timeout'): self._timeout = 0
173                nid.append(self._timeout)
174
175                if not hasattr(self, '_infotitle'): self._infotitle = ''
176                nid.append(self._infotitle)
177                       
178                if not hasattr(self, '_infoflags'):self._infoflags = win32gui.NIIF_NONE
179                nid.append(self._infoflags)
180
181                return tuple(nid)
182       
183        def remove(self):
184                """ Removes the tray icon. """
185                try:
186                        win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, self._get_nid())
187                except pywintypes.error:
188                        pass
189
190
191        def set_tooltip(self, tooltip):
192                """ Sets the tray icon tooltip. """
193                self._flags = self._flags | win32gui.NIF_TIP
194                self._tip = tooltip
195                try:
196                        win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, self._get_nid())
197                except pywintypes.error:
198                        pass
199               
200               
201        def show_balloon(self, title, text, timeout=10, icon=win32gui.NIIF_NONE):
202                """ Shows a balloon tooltip from the tray icon. """
203                self._flags = self._flags | win32gui.NIF_INFO
204                self._infotitle = title
205                self._info = text
206                self._timeout = timeout * 1000
207                self._infoflags = icon
208                try:
209                        win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, self._get_nid())
210                except pywintypes.error:
211                        pass
212
213        def _redraw(self, *args):
214                """ Redraws the tray icon. """
215                self.remove()
216                try:
217                        win32gui.Shell_NotifyIcon(win32gui.NIM_ADD, self._get_nid())
218                except pywintypes.error:
219                        pass
220
221
222class SystrayWin32(systray.Systray):
223        def __init__(self):
224                # Note: gtk window must be realized before installing extensions.
225                systray.Systray.__init__(self)
226                self.jids = []
227                self.status = 'offline'
228                self.xml = gtk.glade.XML(GTKGUI_GLADE, 'systray_context_menu', APP)
229                self.systray_context_menu = self.xml.get_widget('systray_context_menu')
230                self.added_hide_menuitem = False
231               
232#               self.tray_ico_imgs = self.load_icos()
233               
234                w = gtk.Window() # just a window to pass
235                w.realize() # realize it so gtk window exists
236                self.systray_winapi = SystrayWINAPI(w)
237               
238                self.xml.signal_autoconnect(self)
239               
240                # Set up the callback messages
241                self.systray_winapi.message_map({
242                        WM_TRAYMESSAGE: self.on_clicked
243                        }) 
244
245        def show_icon(self):
246                #self.systray_winapi.add_notify_icon(self.systray_context_menu, tooltip = 'Gajim')
247                #self.systray_winapi.notify_icon.menu = self.systray_context_menu
248                # do not remove set_img does both above.
249                # maybe I can only change img without readding
250                # the notify icon? HOW??
251                self.set_img()
252
253        def hide_icon(self):
254                self.systray_winapi.remove()
255
256        def on_clicked(self, hwnd, message, wparam, lparam):
257                if lparam == win32con.WM_RBUTTONUP: # Right click
258                        self.make_menu()
259                        self.systray_winapi.notify_icon.menu.popup(None, None, None, 0, 0)
260                elif lparam == win32con.WM_MBUTTONUP: # Middle click
261                        self.on_middle_click()
262                elif lparam == win32con.WM_LBUTTONUP: # Left click
263                        self.on_left_click()
264
265        def add_jid(self, jid, account, typ):
266                systray.Systray.add_jid(self, jid, account, typ)
267
268                nb = gajim.interface.roster.nb_unread
269                for acct in gajim.connections:
270                        # in chat / groupchat windows
271                        for kind in ('chats', 'gc'):
272                                jids = gajim.interface.instances[acct][kind]
273                                for jid in jids:
274                                        if jid != 'tabbed':
275                                                nb += jids[jid].nb_unread[jid]
276               
277                text = i18n.ngettext(
278                                        'Gajim - %d unread message',
279                                        'Gajim - %d unread messages',
280                                        nb, nb, nb)
281
282                self.systray_winapi.notify_icon.set_tooltip(text)
283
284        def remove_jid(self, jid, account, typ):
285                systray.Systray.remove_jid(self, jid, account, typ)
286
287                nb = gajim.interface.roster.nb_unread
288                for acct in gajim.connections:
289                        # in chat / groupchat windows
290                        for kind in ('chats', 'gc'):
291                                for jid in gajim.interface.instances[acct][kind]:
292                                        if jid != 'tabbed':
293                                                nb += gajim.interface.instances[acct][kind][jid].nb_unread[jid]
294               
295                if nb > 0:
296                        text = i18n.ngettext(
297                                        'Gajim - %d unread message',
298                                        'Gajim - %d unread messages',
299                                        nb, nb, nb)
300                else:
301                        text = 'Gajim'
302                self.systray_winapi.notify_icon.set_tooltip(text)
303
304        def set_img(self):
305                self.tray_ico_imgs = self.load_icos() #FIXME: do not do this here
306                # see gajim.interface.roster.reload_jabber_state_images() to merge
307                self.systray_winapi.remove_notify_icon()
308                if len(self.jids) > 0:
309                        state = 'message'
310                else:
311                        state = self.status
312                hicon = self.tray_ico_imgs[state]
313               
314                self.systray_winapi.add_notify_icon(self.systray_context_menu, hicon,
315                        'Gajim')
316                self.systray_winapi.notify_icon.menu = self.systray_context_menu
317
318        def load_icos(self):
319                '''load .ico files and return them to a dic of SHOW --> img_obj'''
320                iconset = str(gajim.config.get('iconset'))
321                if not iconset:
322                        iconset = 'dcraven'
323               
324                imgs = {}
325                path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16', 'icos')
326                # icon folder for missing icons
327                path_dcraven_iconset = os.path.join(gajim.DATA_DIR, 'iconsets', 'dcraven',
328                        '16x16', 'icos')
329                states_list = gajim.SHOW_LIST
330                # trayicon apart from show holds message state too
331                states_list.append('message')
332                for state in states_list:
333                        path_to_ico = os.path.join(path, state + '.ico')
334                        if not os.path.isfile(path_to_ico): # fallback to dcraven iconset
335                                path_to_ico = os.path.join(path_dcraven_iconset, state + '.ico')
336                        if os.path.exists(path_to_ico):
337                                hinst = win32gui.GetModuleHandle(None)
338                                img_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
339                                image = win32gui.LoadImage(hinst, path_to_ico, win32con.IMAGE_ICON, 
340                                        0, 0, img_flags)
341                                imgs[state] = image
342               
343                return imgs
Note: See TracBrowser for help on using the browser.