Welcome to mirror list, hosted at ThFree Co, Russian Federation.

systraywin32.py « src - dev.gajim.org/gajim/gajim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 383b28c7c67cb4bf4782094f0458b787bbaeea41 (plain)
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
##
## Contributors for this file:
##	- 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