1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
|
## src/systraywin32.py
##
## Gajim Team:
## - Yann Le Boulanger <asterix@lagaule.org>
## - Nikos Kouremenos <kourem@gmail.com>
## - Dimitur Kirov <dkirov@gmail.com>
##
## code initially based on
## http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/334779
## with some ideas/help from pysystray.sf.net
##
## Copyright (C) 2003-2005 Gajim Team
##
## 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 = 'sun'
imgs = {}
path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16', 'icos')
# icon folder for missing icons
path_sun_iconset = os.path.join(gajim.DATA_DIR, 'iconsets', 'sun', '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 sun iconset
path_to_ico = os.path.join(path_sun_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
|