comparison notification.py @ 0:dfe10c951e21

Initial revision
author Guido Berhoerster <guido+weechat@berhoerster.name>
date Tue, 10 Mar 2015 11:27:22 +0100
parents
children ba1005429c76
comparison
equal deleted inserted replaced
-1:000000000000 0:dfe10c951e21
1 #
2 # Copyright (C) 2014 Guido Berhoerster <guido+weechat@berhoerster.name>
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License version 3 as
6 # published by the Free Software Foundation.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
16
17 import os
18 import sys
19 import time
20 import re
21 import select
22 import signal
23 import errno
24 import fcntl
25 import cgi
26 import multiprocessing
27
28
29 SCRIPT_NAME = 'notification'
30 APPLICATION = 'Weechat'
31 VERSION = '1'
32 AUTHOR = 'Guido Berhoerster'
33 COPYRIGHT = '(C) 2014 Guido Berhoerster'
34 SUBTITLE = 'Notification Plugin for Weechat'
35 HOMEPAGE = 'https://code.guido-berhoerster.org/addons/weechat-scripts/weechat-notification-script/'
36 EMAIL = 'guido+weechat@berhoerster.name'
37 DESCRIPTION = 'Notifies of a number of events through desktop notifications ' \
38 'and an optional status icon'
39 DEFAULT_SETTINGS = {
40 'status_icon': ('weechat', 'path or name of the status icon'),
41 'notification_icon': ('weechat', 'path or name of the icon shown in '
42 'notifications'),
43 'preferred_toolkit': ('', 'preferred UI toolkit'),
44 'notify_on_displayed_only': ('on', 'only notify of messages that are '
45 'actually displayed'),
46 'notify_on_privmsg': ('on', 'notify when receiving a private message'),
47 'notify_on_highlight': ('on', 'notify when a messages is highlighted'),
48 'notify_on_dcc_request': ('on', 'notify on DCC requests')
49 }
50 BUFFER_SIZE = 1024
51
52
53 class NetstringParser(object):
54 """Netstring Stream Parser"""
55
56 IN_LENGTH = 0
57 IN_STRING = 1
58
59 def __init__(self, on_string_complete):
60 self.on_string_complete = on_string_complete
61 self.length = 0
62 self.input_buffer = ''
63 self.state = self.IN_LENGTH
64
65 def parse(self, data):
66 self.input_buffer += data
67 ret = True
68 while ret:
69 if self.state == self.IN_LENGTH:
70 ret = self.parse_length()
71 else:
72 ret = self.parse_string()
73
74 def parse_length(self):
75 length, delimiter, self.input_buffer = self.input_buffer.partition(':')
76 if not delimiter:
77 return False
78 try:
79 self.length = int(length)
80 except ValueError:
81 raise SyntaxError('Invalid length: %s' % length)
82 self.state = self.IN_STRING
83 return True
84
85 def parse_string(self):
86 input_buffer_len = len(self.input_buffer)
87 if input_buffer_len < self.length + 1:
88 return False
89 string = self.input_buffer[0:self.length]
90 if self.input_buffer[self.length] != ',':
91 raise SyntaxError('Missing delimiter')
92 self.input_buffer = self.input_buffer[self.length + 1:]
93 self.length = 0
94 self.state = self.IN_LENGTH
95 self.on_string_complete(string)
96 return True
97
98
99 def netstring_encode(*args):
100 return ''.join(['%d:%s,' % (len(element), element) for element in
101 args])
102
103 def netstring_decode(netstring):
104 result = []
105 def append_result(data):
106 result.append(data)
107 np = NetstringParser(append_result)
108 np.parse(netstring)
109 return result
110
111 def dispatch_weechat_callback(*args):
112 return weechat_callbacks[args[0]](*args)
113
114 def create_weechat_callback(method):
115 global weechat_callbacks
116
117 method_id = str(id(method))
118 weechat_callbacks[method_id] = method
119 return method_id
120
121
122 class Notifier(object):
123 """Simple notifier which discards all notifications, base class for all
124 other notifiers
125 """
126
127 def __init__(self, icon):
128 flags = fcntl.fcntl(sys.stdin, fcntl.F_GETFL)
129 fcntl.fcntl(sys.stdin, fcntl.F_SETFL, flags | os.O_NONBLOCK)
130
131 self.parser = NetstringParser(self.on_command_received)
132
133 def on_command_received(self, raw_command):
134 command_args = netstring_decode(raw_command)
135 if len(command_args) > 1:
136 command = command_args[0]
137 args = netstring_decode(command_args[1])
138 else:
139 command = command_args[0]
140 args = []
141 getattr(self, command)(*args)
142
143 def notify(self, summary, message, icon):
144 pass
145
146 def reset(self):
147 pass
148
149 def run(self):
150 poll = select.poll()
151 poll.register(sys.stdin, select.POLLIN | select.POLLPRI)
152
153 while True:
154 try:
155 events = poll.poll()
156 except select.error as e:
157 if e.args and e.args[0] == errno.EINTR:
158 continue
159 else:
160 raise e
161 for fd, event in events:
162 if event & (select.POLLIN | select.POLLPRI):
163 buffer_ = os.read(fd, BUFFER_SIZE)
164 if buffer_ != '':
165 self.parser.parse(buffer_)
166 if event & (select.POLLERR | select.POLLHUP | select.POLLNVAL):
167 sys.exit(1)
168
169
170 class Gtk2Notifier(Notifier):
171 """GTK 2 notifier based on pygtk and pynotify"""
172
173 def __init__(self, icon):
174 super(Gtk2Notifier, self).__init__(icon)
175
176 pynotify.init(APPLICATION)
177
178 gobject.io_add_watch(sys.stdin, gobject.IO_IN | gobject.IO_PRI,
179 self.on_input)
180
181 if not icon:
182 icon_name = None
183 icon_pixbuf = None
184 elif icon.startswith('/'):
185 icon_name = None
186 try:
187 icon_pixbuf = gtk.gdk.Pixbuf.new_from_file(icon)
188 except gobject.GError:
189 icon_pixbuf = None
190 else:
191 icon_name = icon
192 icon_pixbuf = None
193
194 if icon_name or icon_pixbuf:
195 self.status_icon = gtk.StatusIcon()
196 self.status_icon.set_title(APPLICATION)
197 self.status_icon.set_tooltip_text(APPLICATION)
198 self.status_icon.connect('activate', self.on_activate)
199 if icon_name:
200 self.status_icon.set_from_icon_name(icon_name)
201 elif icon_pixbuf:
202 self.status_icon.set_from_pixbuf(icon_pixbuf)
203 else:
204 self.status_icon = None
205
206 def on_input(self, fd, cond):
207 if cond & (gobject.IO_IN | gobject.IO_PRI):
208 try:
209 buffer_ = os.read(fd.fileno(), BUFFER_SIZE)
210 if buffer_ != '':
211 self.parser.parse(buffer_)
212 except EOFError:
213 gtk.main_quit()
214 return False
215
216 if cond & (gobject.IO_ERR | gobject.IO_HUP):
217 gtk.main_quit()
218 return False
219
220 return True
221
222 def on_activate(self, widget):
223 self.reset()
224
225 def notify(self, summary, message, icon):
226 if self.status_icon:
227 self.status_icon.set_tooltip_text('%s: %s' % (APPLICATION,
228 summary))
229 self.status_icon.set_blinking(True)
230
231 if icon and icon.startswith('/'):
232 icon_name = None
233 try:
234 icon_pixbuf = gtk.gdk.Pixbuf.new_from_file(icon)
235 except gobject.GError:
236 icon_pixbuf = None
237 else:
238 icon_name = icon
239 icon_pixbuf = None
240
241 if 'body-markup' in pynotify.get_server_caps():
242 body = cgi.escape(message)
243 else:
244 body = message
245
246 notification = pynotify.Notification(summary, body, icon_name)
247 if icon_pixbuf is not None:
248 notification.set_image_from_pixbuf(icon_pixbuf)
249 notification.show()
250
251 def reset(self):
252 if self.status_icon:
253 self.status_icon.set_tooltip_text(APPLICATION)
254 self.status_icon.set_blinking(False)
255
256 def run(self):
257 gtk.main()
258
259
260 class Gtk3Notifier(Notifier):
261 """GTK3 notifier based on GObject Introspection Bindings for GTK 3 and
262 libnotify
263 """
264
265 def __init__(self, icon):
266 super(Gtk3Notifier, self).__init__(icon)
267
268 Notify.init(APPLICATION)
269
270 GLib.io_add_watch(sys.stdin, GLib.IO_IN | GLib.IO_PRI, self.on_input)
271
272 if not icon:
273 self.icon_name = None
274 self.icon_pixbuf = None
275 elif icon.startswith('/'):
276 self.icon_name = None
277 try:
278 self.icon_pixbuf = GdkPixbuf.Pixbuf.new_from_file(icon)
279 except GLib.GError:
280 self.icon_pixbuf = None
281 else:
282 self.icon_name = icon
283 self.icon_pixbuf = None
284
285 if self.icon_name or self.icon_pixbuf:
286 # create blank, fully transparent pixbuf in order to simulate
287 # blinking
288 self.blank_pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB,
289 True, 8, 22, 22)
290 self.blank_pixbuf.fill(0x00)
291
292 self.blink_on = True
293 self.blink_timeout_id = None
294
295 self.status_icon = Gtk.StatusIcon.new()
296 self.status_icon.set_title(APPLICATION)
297 self.status_icon.set_tooltip_text(APPLICATION)
298 self.status_icon.connect('activate', self.on_activate)
299 self.update_icon()
300 else:
301 self.status_icon = None
302
303 def on_input(self, fd, cond):
304 if cond & (GLib.IO_IN | GLib.IO_PRI):
305 try:
306 self.parser.parse(os.read(fd.fileno(), BUFFER_SIZE))
307 except EOFError:
308 Gtk.main_quit()
309 return False
310
311 if cond & (GLib.IO_ERR | GLib.IO_HUP):
312 Gtk.main_quit()
313 return False
314
315 return True
316
317 def on_activate(self, widget):
318 self.reset()
319
320 def update_icon(self):
321 if not self.blink_on:
322 self.status_icon.set_from_pixbuf(self.blank_pixbuf)
323 elif self.icon_name:
324 self.status_icon.set_from_icon_name(self.icon_name)
325 elif self.icon_pixbuf:
326 self.status_icon.set_from_pixbuf(self.icon_pixbuf)
327
328 def on_blink_timeout(self):
329 self.blink_on = not self.blink_on
330 self.update_icon()
331 return True
332
333 def notify(self, summary, message, icon):
334 if self.status_icon:
335 self.status_icon.set_tooltip_text('%s: %s' % (APPLICATION,
336 summary))
337 if self.blink_timeout_id is None:
338 self.blink_timeout_id = GLib.timeout_add(500,
339 self.on_blink_timeout)
340
341 if icon and icon.startswith('/'):
342 icon_name = None
343 try:
344 icon_pixbuf = GdkPixbuf.Pixbuf.new_from_file(icon)
345 except GLib.GError:
346 icon_pixbuf = None
347 else:
348 icon_name = icon
349 icon_pixbuf = None
350
351 if 'body-markup' in Notify.get_server_caps():
352 body = cgi.escape(message)
353 else:
354 body = message
355
356 notification = Notify.Notification.new(summary, body, icon_name)
357 if icon_pixbuf is not None:
358 notification.set_image_from_pixbuf(icon_pixbuf)
359 notification.show()
360
361 def reset(self):
362 if self.status_icon:
363 self.status_icon.set_tooltip_text(APPLICATION)
364 if self.blink_timeout_id is not None:
365 GLib.source_remove(self.blink_timeout_id)
366 self.blink_timeout_id = None
367 self.blink_on = True
368 self.update_icon()
369
370 def run(self):
371 Gtk.main()
372
373
374 class Qt4Notifier(Notifier):
375 """Qt 4 notifier"""
376
377 def __init__(self, icon):
378 super(Qt4Notifier, self).__init__(icon)
379
380 signal.signal(signal.SIGINT, self.on_sigint)
381
382 self.qapplication = QtGui.QApplication([])
383
384 self.readable_notifier = QtCore.QSocketNotifier(sys.stdin.fileno(),
385 QtCore.QSocketNotifier.Read)
386 self.readable_notifier.activated.connect(self.on_input)
387 self.readable_notifier.setEnabled(True)
388
389 if not icon:
390 self.icon = None
391 elif icon.startswith('/'):
392 self.icon = QtGui.QIcon(icon)
393 else:
394 self.icon = QtGui.QIcon.fromTheme(icon)
395
396 if self.icon:
397 # create blank, fully transparent pixbuf in order to simulate
398 # blinking
399 self.blank_icon = QtGui.QIcon()
400
401 self.blink_on = True
402 self.blinking_timer = QtCore.QTimer()
403 self.blinking_timer.setInterval(500)
404 self.blinking_timer.timeout.connect(self.on_blink_timeout)
405
406 self.status_icon = QtGui.QSystemTrayIcon()
407 self.status_icon.setToolTip(APPLICATION)
408 self.update_icon()
409 self.status_icon.setVisible(True)
410 self.status_icon.activated.connect(self.on_activated)
411 else:
412 self.status_icon = None
413
414 def on_sigint(self, signo, frame):
415 self.qapplication.exit(0)
416
417 def on_input(self, fd):
418 try:
419 self.parser.parse(os.read(fd, BUFFER_SIZE))
420 except EOFError:
421 self.qapplication.exit(1)
422
423 def on_activated(self, reason):
424 self.reset()
425
426 def on_blink_timeout(self):
427 self.blink_on = not self.blink_on
428 self.update_icon()
429
430 def update_icon(self):
431 if not self.blink_on:
432 self.status_icon.setIcon(self.blank_icon)
433 else:
434 self.status_icon.setIcon(self.icon)
435
436 def notify(self, summary, message, icon):
437 if self.status_icon:
438 self.status_icon.setToolTip('%s: %s' % (APPLICATION,
439 cgi.escape(summary)))
440 self.blinking_timer.start()
441 if self.status_icon.supportsMessages():
442 self.status_icon.showMessage(summary, message,
443 QtGui.QSystemTrayIcon.NoIcon)
444
445 def reset(self):
446 if self.status_icon:
447 self.blinking_timer.stop()
448 self.blink_on = True
449 self.update_icon()
450 self.status_icon.setToolTip(APPLICATION)
451
452 def run(self):
453 sys.exit(self.qapplication.exec_())
454
455
456 class KDE4Notifier(Notifier):
457 """KDE 4 notifier based on PyKDE4"""
458
459 def __init__(self, icon):
460 super(KDE4Notifier, self).__init__(icon)
461
462 signal.signal(signal.SIGINT, self.on_sigint)
463
464 aboutData = kdecore.KAboutData(APPLICATION.lower(), '',
465 kdecore.ki18n(APPLICATION), VERSION, kdecore.ki18n(SUBTITLE),
466 kdecore.KAboutData.License_GPL_V3, kdecore.ki18n(COPYRIGHT),
467 kdecore.ki18n (''), HOMEPAGE, EMAIL)
468 kdecore.KCmdLineArgs.init(aboutData)
469 self.kapplication = kdeui.KApplication()
470
471 self.readable_notifier = QtCore.QSocketNotifier(sys.stdin.fileno(),
472 QtCore.QSocketNotifier.Read)
473 self.readable_notifier.activated.connect(self.on_input)
474 self.readable_notifier.setEnabled(True)
475
476 if not icon:
477 icon_qicon = None
478 icon_name = None
479 elif icon.startswith('/'):
480 icon_qicon = QtGui.QIcon(icon)
481 icon_name = None
482 else:
483 icon_qicon = None
484 icon_name = icon
485
486 if icon_name or icon_pixmap:
487 self.status_notifier = kdeui.KStatusNotifierItem(self.kapplication)
488 self.status_notifier.setCategory(
489 kdeui.KStatusNotifierItem.Communications)
490 if icon_name:
491 self.status_notifier.setIconByName(icon_name)
492 self.status_notifier.setToolTip(icon_name, APPLICATION,
493 SUBTITLE)
494 else:
495 self.status_notifier.setIconByPixmap(icon_qicon)
496 self.status_notifier.setToolTip(icon_qicon, APPLICATION,
497 SUBTITLE)
498 self.status_notifier.setStandardActionsEnabled(False)
499 self.status_notifier.setStatus(kdeui.KStatusNotifierItem.Active)
500 self.status_notifier.setTitle(APPLICATION)
501 self.status_notifier.activateRequested.connect(
502 self.on_activate_requested)
503 else:
504 self.status_notifier = None
505
506 def on_sigint(self, signo, frame):
507 self.kapplication.exit(0)
508
509 def on_input(self, fd):
510 try:
511 self.parser.parse(os.read(fd, BUFFER_SIZE))
512 except EOFError:
513 self.kapplication.exit(1)
514
515 def on_activate_requested(self, active, pos):
516 self.reset()
517
518 def notify(self, summary, message, icon):
519 if self.status_notifier:
520 self.status_notifier.setToolTipSubTitle(cgi.escape(summary))
521 self.status_notifier.setStatus(
522 kdeui.KStatusNotifierItem.NeedsAttention)
523
524 if icon:
525 if icon.startswith('/'):
526 pixmap = QtGui.QPixmap.load(icon)
527 else:
528 pixmap = kdeui.KIcon(icon).pixmap(kdeui.KIconLoader.SizeHuge,
529 kdeui.KIconLoader.SizeHuge)
530 else:
531 pixmap = QtGui.QPixmap()
532 kdeui.KNotification.event(kdeui.KNotification.Notification, summary,
533 cgi.escape(message), pixmap)
534
535 def reset(self):
536 if self.status_notifier:
537 self.status_notifier.setStatus(kdeui.KStatusNotifierItem.Active)
538 self.status_notifier.setToolTipTitle(APPLICATION)
539 self.status_notifier.setToolTipSubTitle(SUBTITLE)
540
541 def run(self):
542 sys.exit(self.kapplication.exec_())
543
544
545 class NotificationProxy(object):
546 """Proxy object for interfacing with the notifier process"""
547
548 def __init__(self, preferred_toolkit, status_icon):
549 self.script_file = os.path.realpath(__file__)
550 self._status_icon = status_icon
551 self._preferred_toolkit = preferred_toolkit
552 self.notifier_process_hook = None
553 self.spawn_timer_hook = None
554 self.next_spawn_time = 0.0
555
556 self.spawn_notifier_process()
557
558 @property
559 def status_icon(self):
560 return self._status_icon
561
562 @status_icon.setter
563 def status_icon(self, value):
564 self._status_icon = value
565 self.terminate_notifier_process()
566 self.spawn_notifier_process()
567
568 @property
569 def preferred_toolkit(self):
570 return self._preferred_toolkit
571
572 @preferred_toolkit.setter
573 def preferred_toolkit(self, value):
574 self._preferred_toolkit = value
575 self.terminate_notifier_process()
576 self.spawn_notifier_process()
577
578 def on_notifier_process_event(self, data, command, return_code, output,
579 error_output):
580 if return_code != weechat.WEECHAT_HOOK_PROCESS_RUNNING:
581 if return_code == weechat.WEECHAT_HOOK_PROCESS_ERROR:
582 error = '%sfailed to run notifier' % weechat.prefix("error")
583 else:
584 error = '%snotifier exited with exit status %d' % \
585 (weechat.prefix("error"), return_code)
586 if output:
587 error += '\nstdout:%s' % output
588 if error_output:
589 error += '\nstderr:%s' % error_output
590 weechat.prnt('', error)
591 self.notifier_process_hook = None
592 self.spawn_notifier_process()
593 return weechat.WEECHAT_RC_OK
594
595 def on_spawn_timer(self, data, remaining):
596 self.spawn_timer_hook = None
597 if not self.notifier_process_hook:
598 self.spawn_notifier_process()
599 return weechat.WEECHAT_RC_OK
600
601 def spawn_notifier_process(self):
602 if self.notifier_process_hook or self.spawn_timer_hook:
603 return
604
605 # do not try to respawn a notifier more than once every ten seconds
606 now = time.time()
607 if long(self.next_spawn_time - now) > 0:
608 self.spawn_timer_hook = \
609 weechat.hook_timer(long((self.next_spawn_time - now) *
610 1000), 0, 1, 'dispatch_weechat_callback',
611 create_weechat_callback(self.on_spawn_timer))
612 return
613
614 self.next_spawn_time = now + 10
615 self.notifier_process_hook = \
616 weechat.hook_process_hashtable(sys.executable, {'arg1':
617 self.script_file, 'arg2': self.preferred_toolkit, 'arg3':
618 self.status_icon, 'stdin': '1'}, 0,
619 'dispatch_weechat_callback',
620 create_weechat_callback(self.on_notifier_process_event))
621
622 def terminate_notifier_process(self):
623 if self.spawn_timer_hook:
624 weechat.unhook(self.spawn_timer_hook)
625 self.spawn_timer_hook = None
626 if self.notifier_process_hook:
627 weechat.unhook(self.notifier_process_hook)
628 self.notifier_process_hook = None
629 self.next_spawn_time = 0.0
630
631 def send(self, command, *args):
632 if self.notifier_process_hook:
633 if args:
634 weechat.hook_set(self.notifier_process_hook, 'stdin',
635 netstring_encode(netstring_encode(command,
636 netstring_encode(*args))))
637 else:
638 weechat.hook_set(self.notifier_process_hook, 'stdin',
639 netstring_encode(netstring_encode(command)))
640
641 def notify(self, summary, message, icon):
642 self.send('notify', summary, message, icon)
643
644 def reset(self):
645 self.send('reset')
646
647
648 class NotificationPlugin(object):
649 """Weechat plugin"""
650
651 def __init__(self):
652 self.DCC_SEND_RE = re.compile(r':(?P<sender>\S+) PRIVMSG \S+ :'
653 r'\x01DCC SEND (?P<filename>\S+) \d+ \d+ (?P<size>\d+)')
654 self.DCC_CHAT_RE = re.compile(r':(?P<sender>\S+) PRIVMSG \S+ :'
655 r'\x01DCC CHAT ')
656
657 weechat.register(SCRIPT_NAME, AUTHOR, VERSION, 'GPL3', DESCRIPTION, '',
658 '')
659
660 for option, (value, description) in DEFAULT_SETTINGS.iteritems():
661 if not weechat.config_is_set_plugin(option):
662 weechat.config_set_plugin(option, value)
663 weechat.config_set_desc_plugin(option, '%s (default: "%s")' %
664 (description, value))
665
666 self.notification_proxy = NotificationProxy(
667 weechat.config_get_plugin('preferred_toolkit'),
668 weechat.config_get_plugin('status_icon'))
669
670 weechat.hook_print('', 'irc_privmsg', '', 1,
671 'dispatch_weechat_callback',
672 create_weechat_callback(self.on_message))
673 weechat.hook_signal('key_pressed', 'dispatch_weechat_callback',
674 create_weechat_callback(self.on_key_pressed))
675 weechat.hook_signal('irc_dcc', 'dispatch_weechat_callback',
676 create_weechat_callback(self.on_dcc))
677 weechat.hook_config('plugins.var.python.%s.*' % SCRIPT_NAME,
678 'dispatch_weechat_callback',
679 create_weechat_callback(self.on_config_changed))
680
681 def on_message(self, data, buffer, date, tags, displayed, highlight,
682 prefix, message):
683 if weechat.config_get_plugin('notify_on_displayed_only') == 'on' and \
684 int(displayed) != 1:
685 return weechat.WEECHAT_RC_OK
686
687 formatted_date = time.strftime('%H:%M', time.localtime(float(date)))
688 if 'notify_private' in tags.split(',') and \
689 weechat.config_get_plugin('notify_on_privmsg') == 'on':
690 summary = 'Private message from %s at %s' % (prefix,
691 formatted_date)
692 self.notification_proxy.notify(summary, message,
693 weechat.config_get_plugin('notification_icon'))
694 elif int(highlight) == 1 and \
695 weechat.config_get_plugin('notify_on_highlight') == 'on':
696 summary = 'Highlighted message from %s at %s' % (prefix,
697 formatted_date)
698 self.notification_proxy.notify(summary, message,
699 weechat.config_get_plugin('notification_icon'))
700
701 return weechat.WEECHAT_RC_OK
702
703 def on_dcc(self, data, signal, signal_data):
704 if weechat.config_get_plugin('notify_on_dcc') != 'on':
705 return weechat.WEECHAT_RC_OK
706
707 matches = self.DCC_SEND_RE.match(signal_data)
708 if matches:
709 summary = 'DCC send request from %s' % matches.group('sender')
710 message = 'Filname: %s, Size: %d bytes' % \
711 (matches.group('filename'), int(matches.group('size')))
712 self.notification_proxy.notify(summary, message,
713 weechat.config_get_plugin('notification_icon'))
714 return weechat.WEECHAT_RC_OK
715
716 matches = self.DCC_CHAT_RE.match(signal_data)
717 if matches:
718 summary = 'DCC chat request from %s' % matches.group('sender')
719 message = ''
720 self.notification_proxy.notify(summary, message,
721 weechat.config_get_plugin('notification_icon'))
722 return weechat.WEECHAT_RC_OK
723
724 return weechat.WEECHAT_RC_OK
725
726 def on_key_pressed(self, data, signal, signal_data):
727 self.notification_proxy.reset()
728 return weechat.WEECHAT_RC_OK
729
730 def on_config_changed(self, data, option, value):
731 if option.endswith('.preferred_toolkit'):
732 self.notification_proxy.preferred_toolkit = value
733 elif option.endswith('.status_icon'):
734 self.notification_proxy.status_icon = value
735 return weechat.WEECHAT_RC_OK
736
737
738 def import_modules(modules):
739 for module_name, fromlist in modules:
740 if fromlist:
741 module = __import__(module_name, fromlist=fromlist)
742 for identifier in fromlist:
743 globals()[identifier] = getattr(module, identifier)
744 else:
745 globals()[module_name] = __import__(module_name)
746
747 def try_import_modules(modules):
748 try:
749 import_modules(modules)
750 except ImportError:
751 sys.exit(1)
752 sys.exit(0)
753
754
755 if __name__ == '__main__':
756 if sys.argv[0] == '__weechat_plugin__':
757 # running as Weechat plugin
758 import weechat
759
760 weechat_callbacks = {}
761
762 plugin = NotificationPlugin()
763 elif len(sys.argv) == 3:
764 # running as the notifier process
765 preferred_toolkit = sys.argv[1]
766 icon = sys.argv[2]
767
768 # required modules for each toolkit
769 toolkits_modules = {
770 'gtk3': [
771 ('gi.repository', [
772 'GLib',
773 'GdkPixbuf',
774 'Gtk',
775 'Notify'
776 ])
777 ],
778 'gtk2': [
779 ('pygtk', []),
780 ('gobject', []),
781 ('gtk', []),
782 ('pynotify', [])
783 ],
784 'qt4': [
785 ('PyQt4', [
786 'QtGui',
787 'QtCore'
788 ])
789 ],
790 'kde4': [
791 ('PyQt4', [
792 'QtGui',
793 'QtCore'
794 ]),
795 ('PyKDE4', [
796 'kdecore',
797 'kdeui'
798 ])
799 ],
800 '': []
801 }
802 available_toolkits = []
803 selected_toolkit = ''
804
805 # find available toolkits by spawning a process for each toolkit which
806 # tries to import all required modules and returns an exit status of 1
807 # in case of an import error
808 for toolkit in toolkits_modules:
809 process = multiprocessing.Process(target=try_import_modules,
810 args=(toolkits_modules[toolkit],))
811 process.start()
812 process.join(3)
813 if process.is_alive():
814 process.terminate()
815 process.join()
816 if process.exitcode == 0:
817 available_toolkits.append(toolkit)
818
819 # select toolkit based on either explicit preference or the
820 # availability of modules and the used desktop environment
821 if preferred_toolkit:
822 if preferred_toolkit in available_toolkits:
823 selected_toolkit = preferred_toolkit
824 else:
825 if 'KDE_FULL_SESSION' in os.environ:
826 # preferred order if running KDE4
827 toolkits = ['kde4', 'qt4', 'gtk3', 'gtk2']
828 else:
829 # preferred order for all other desktop environments
830 toolkits = ['gtk3', 'gtk2', 'qt4', 'kde4']
831 for toolkit in toolkits:
832 if toolkit in available_toolkits:
833 selected_toolkit = toolkit
834 break
835
836 # import required toolkit modules
837 import_modules(toolkits_modules[selected_toolkit])
838
839 # run selected notifier
840 if selected_toolkit == 'gtk3':
841 notifier = Gtk3Notifier(icon)
842 elif selected_toolkit == 'gtk2':
843 notifier = Gtk2Notifier(icon)
844 elif selected_toolkit == 'qt4':
845 notifier = Qt4Notifier(icon)
846 elif selected_toolkit == 'kde4':
847 notifier = KDE4Notifier(icon)
848 else:
849 notifier = Notifier(icon)
850 notifier.run()
851 else:
852 sys.exit(1)