1 # -*- coding: utf-8 -*-
3 ## plugins/triggers/triggers.py
5 ## Copyright (C) 2011 Yann Leboulanger <asterix AT lagaule.org>
7 ## This file is part of Gajim.
9 ## Gajim is free software; you can redistribute it and/or modify
10 ## it under the terms of the GNU General Public License as published
11 ## by the Free Software Foundation; version 3 only.
13 ## Gajim is distributed in the hope that it will be useful,
14 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ## GNU General Public License for more details.
18 ## You should have received a copy of the GNU General Public License
19 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
25 from common
import gajim
26 from plugins
import GajimPlugin
27 from plugins
.helpers
import log_calls
, log
28 from plugins
.gui
import GajimPluginConfigDialog
29 from common
import i18n
30 from common
import ged
31 from common
import helpers
34 class Triggers(GajimPlugin
):
36 @log_calls('TriggersPlugin')
38 self
.config_dialog
= TriggersPluginConfigDialog(self
)
39 self
.config_default_values
= {}
41 self
.events_handlers
= {'notification' : (ged
.PREGUI
, self
._nec
_notif
),
42 'decrypted-message-received': (ged
.PREGUI2
,
43 self
._nec
_decrypted
_message
_received
)}
45 def _check_rule_recipients(self
, obj
, rule
):
46 rule_recipients
= [t
.strip() for t
in rule
['recipients'].split(',')]
47 if rule
['recipient_type'] == 'contact' and obj
.jid
not in \
50 contact_groups
= gajim
.contacts
.get_first_contact_from_jid(
51 obj
.conn
.name
, obj
.jid
).groups
53 for group
in contact_groups
:
54 if group
in rule_recipients
:
57 if rule
['recipient_type'] == 'group' and not group_found
:
62 def _check_rule_status(self
, obj
, rule
):
63 rule_statuses
= rule
['status'].split()
64 our_status
= gajim
.SHOW_LIST
[obj
.conn
.connected
]
65 if rule
['status'] != 'all' and our_status
not in rule_statuses
:
70 def _check_rule_tab_opened(self
, obj
, rule
):
71 if rule
['tab_opened'] == 'both':
74 if gajim
.interface
.msg_win_mgr
.get_control(obj
.jid
, obj
.conn
.name
):
76 if tab_opened
and rule
['tab_opened'] == 'no':
78 elif not tab_opened
and rule
['tab_opened'] == 'yes':
83 def check_rule_apply_notif(self
, obj
, rule
):
84 # Check notification type
86 if obj
.notif_type
== 'msg':
87 notif_type
= 'message_received'
88 if notif_type
!= rule
['event']:
91 # notification type is ok. Now check recipient
92 if not self
._check
_rule
_recipients
(obj
, rule
):
95 # recipient is ok. Now check our status
96 if not self
._check
_rule
_status
(obj
, rule
):
99 # our_status is ok. Now check opened chat window
100 if not self
._check
_rule
_tab
_opened
(obj
, rule
):
106 def check_rule_apply_decrypted_msg(self
, obj
, rule
):
107 # Check notification type
108 if rule
['event'] != 'message_received':
111 # notification type is ok. Now check recipient
112 if not self
._check
_rule
_recipients
(obj
, rule
):
115 # recipient is ok. Now check our status
116 if not self
._check
_rule
_status
(obj
, rule
):
119 # our_status is ok. Now check opened chat window
120 if not self
._check
_rule
_tab
_opened
(obj
, rule
):
126 def apply_rule(self
, obj
, rule
):
127 if rule
['sound'] == 'no':
129 elif rule
['sound'] == 'yes':
132 obj
.sound_file
= rule
['sound_file']
134 if rule
['popup'] == 'no':
136 elif rule
['popup'] == 'yes':
139 if rule
['run_command']:
140 obj
.do_command
= True
141 obj
.command
= rule
['command']
143 obj
.do_command
= False
145 if rule
['systray'] == 'no':
146 obj
.show_in_notification_area
= False
147 elif rule
['systray'] == 'yes':
148 obj
.show_in_notification_area
= True
150 if rule
['roster'] == 'no':
151 obj
.show_in_roster
= False
152 elif rule
['roster'] == 'yes':
153 obj
.show_in_roster
= True
155 # if rule['urgency_hint'] == 'no':
156 # ?? not in obj actions
157 # elif rule['urgency_hint'] == 'yes':
159 def _nec_notif(self
, obj
):
160 # check rules in order
161 rules_num
= [int(i
) for i
in self
.config
.keys()]
163 for num
in rules_num
:
164 if self
.check_rule_apply_notif(obj
, self
.config
[str(num
)]):
165 self
.apply_rule(obj
, self
.config
[str(num
)])
166 # Should we stop after first valid rule ?
169 def _nec_decrypted_message_received(self
, obj
):
170 rules_num
= [int(i
) for i
in self
.config
.keys()]
172 for num
in rules_num
:
173 rule
= self
.config
[str(num
)]
174 if self
.check_rule_apply_decrypted_msg(obj
, rule
):
175 if rule
['auto_open'] == 'no':
177 elif rule
['auto_open'] == 'yes':
181 class TriggersPluginConfigDialog(GajimPluginConfigDialog
):
182 events_list
= ['message_received']#, 'contact_connected',
183 #'contact_disconnected', 'contact_change_status', 'gc_msg_highlight',
185 recipient_types_list
= ['contact', 'group', 'all']
186 config_options
= ['event', 'recipient_type', 'recipients', 'status',
187 'tab_opened', 'sound', 'sound_file', 'popup', 'auto_open',
188 'run_command', 'command', 'systray', 'roster', 'urgency_hint']
191 self
.GTK_BUILDER_FILE_PATH
= self
.plugin
.local_file_path(
193 self
.xml
= gtk
.Builder()
194 self
.xml
.add_objects_from_file(self
.GTK_BUILDER_FILE_PATH
,
195 ['vbox', 'liststore1', 'liststore2'])
196 vbox
= self
.xml
.get_object('vbox')
197 self
.child
.pack_start(vbox
)
199 self
.xml
.connect_signals(self
)
200 self
.connect('hide', self
.on_hide
)
204 for w
in ('conditions_treeview', 'config_vbox', 'event_combobox',
205 'recipient_type_combobox', 'recipient_list_entry', 'delete_button',
206 'status_hbox', 'use_sound_cb', 'disable_sound_cb', 'use_popup_cb',
207 'disable_popup_cb', 'use_auto_open_cb', 'disable_auto_open_cb',
208 'use_systray_cb', 'disable_systray_cb', 'use_roster_cb',
209 'disable_roster_cb', 'tab_opened_cb', 'not_tab_opened_cb',
210 'sound_entry', 'sound_file_hbox', 'up_button', 'down_button',
211 'run_command_cb', 'command_entry', 'use_urgency_hint_cb',
212 'disable_urgency_hint_cb'):
213 self
.__dict
__[w
] = self
.xml
.get_object(w
)
216 for n
in self
.plugin
.config
:
217 self
.config
[int(n
)] = self
.plugin
.config
[n
]
219 # Contains status checkboxes
220 childs
= self
.status_hbox
.get_children()
222 self
.all_status_rb
= childs
[0]
223 self
.special_status_rb
= childs
[1]
224 self
.online_cb
= childs
[2]
225 self
.away_cb
= childs
[3]
226 self
.xa_cb
= childs
[4]
227 self
.dnd_cb
= childs
[5]
228 self
.invisible_cb
= childs
[6]
230 if not self
.conditions_treeview
.get_column(0):
231 # window never opened
232 model
= gtk
.ListStore(int, str)
233 model
.set_sort_column_id(0, gtk
.SORT_ASCENDING
)
234 self
.conditions_treeview
.set_model(model
)
237 col
= gtk
.TreeViewColumn(_('#'))
238 self
.conditions_treeview
.append_column(col
)
239 renderer
= gtk
.CellRendererText()
240 col
.pack_start(renderer
, expand
=False)
241 col
.set_attributes(renderer
, text
=0)
243 col
= gtk
.TreeViewColumn(_('Condition'))
244 self
.conditions_treeview
.append_column(col
)
245 renderer
= gtk
.CellRendererText()
246 col
.pack_start(renderer
, expand
=True)
247 col
.set_attributes(renderer
, text
=1)
249 model
= self
.conditions_treeview
.get_model()
253 # Fill conditions_treeview
255 while num
in self
.config
:
256 iter_
= model
.append((num
, ''))
257 path
= model
.get_path(iter_
)
258 self
.conditions_treeview
.set_cursor(path
)
259 self
.active_num
= num
260 self
.initiate_rule_state()
261 self
.set_treeview_string()
264 # No rule selected at init time
265 self
.conditions_treeview
.get_selection().unselect_all()
267 self
.config_vbox
.set_sensitive(False)
268 self
.delete_button
.set_sensitive(False)
269 self
.down_button
.set_sensitive(False)
270 self
.up_button
.set_sensitive(False)
272 def initiate_rule_state(self
):
274 Set values for all widgets
276 if self
.active_num
< 0:
279 value
= self
.config
[self
.active_num
]['event']
281 self
.event_combobox
.set_active(self
.events_list
.index(value
))
283 self
.event_combobox
.set_active(-1)
285 value
= self
.config
[self
.active_num
]['recipient_type']
287 self
.recipient_type_combobox
.set_active(
288 self
.recipient_types_list
.index(value
))
290 self
.recipient_type_combobox
.set_active(-1)
292 value
= self
.config
[self
.active_num
]['recipients']
295 self
.recipient_list_entry
.set_text(value
)
297 value
= self
.config
[self
.active_num
]['status']
299 self
.all_status_rb
.set_active(True)
301 self
.special_status_rb
.set_active(True)
302 values
= value
.split()
303 for v
in ('online', 'away', 'xa', 'dnd', 'invisible'):
305 self
.__dict
__[v
+ '_cb'].set_active(True)
307 self
.__dict
__[v
+ '_cb'].set_active(False)
308 self
.on_status_radiobutton_toggled(self
.all_status_rb
)
311 value
= self
.config
[self
.active_num
]['tab_opened']
312 self
.tab_opened_cb
.set_active(True)
313 self
.not_tab_opened_cb
.set_active(True)
315 self
.tab_opened_cb
.set_active(False)
317 self
.not_tab_opened_cb
.set_active(False)
320 value
= self
.config
[self
.active_num
]['sound_file']
321 self
.sound_entry
.set_text(value
)
323 # sound, popup, auto_open, systray, roster
324 for option
in ('sound', 'popup', 'auto_open', 'systray', 'roster',
326 value
= self
.config
[self
.active_num
][option
]
328 self
.__dict
__['use_' + option
+ '_cb'].set_active(True)
330 self
.__dict
__['use_' + option
+ '_cb'].set_active(False)
332 self
.__dict
__['disable_' + option
+ '_cb'].set_active(True)
334 self
.__dict
__['disable_' + option
+ '_cb'].set_active(False)
337 value
= self
.config
[self
.active_num
]['run_command']
338 self
.run_command_cb
.set_active(value
)
341 value
= self
.config
[self
.active_num
]['command']
342 self
.command_entry
.set_text(value
)
344 def set_treeview_string(self
):
345 (model
, iter_
) = self
.conditions_treeview
.get_selection().get_selected()
348 event
= self
.event_combobox
.get_active_text()
349 recipient_type
= self
.recipient_type_combobox
.get_active_text()
351 if recipient_type
!= 'everybody':
352 recipient
= self
.recipient_list_entry
.get_text()
353 if self
.all_status_rb
.get_active():
356 status
= _('when I am ')
357 for st
in ('online', 'away', 'xa', 'dnd', 'invisible'):
358 if self
.__dict
__[st
+ '_cb'].get_active():
359 status
+= helpers
.get_uf_show(st
) + ' '
360 model
[iter_
][1] = "When %s for %s %s %s" % (event
, recipient_type
,
363 def on_conditions_treeview_cursor_changed(self
, widget
):
364 (model
, iter_
) = widget
.get_selection().get_selected()
368 self
.active_num
= model
[iter_
][0]
369 if self
.active_num
== '0':
370 self
.up_button
.set_sensitive(False)
372 self
.up_button
.set_sensitive(True)
373 max = self
.conditions_treeview
.get_model().iter_n_children(None)
374 if self
.active_num
== max - 1:
375 self
.down_button
.set_sensitive(False)
377 self
.down_button
.set_sensitive(True)
378 self
.initiate_rule_state()
379 self
.config_vbox
.set_sensitive(True)
380 self
.delete_button
.set_sensitive(True)
382 def on_new_button_clicked(self
, widget
):
383 model
= self
.conditions_treeview
.get_model()
384 num
= self
.conditions_treeview
.get_model().iter_n_children(None)
385 self
.config
[num
] = {'event': '', 'recipient_type': 'all',
386 'recipients': '', 'status': 'all', 'tab_opened': 'both',
387 'sound': '', 'sound_file': '', 'popup': '', 'auto_open': '',
388 'run_command': False, 'command': '', 'systray': '', 'roster': '',
389 'urgency_hint': False}
390 iter_
= model
.append((num
, ''))
391 path
= model
.get_path(iter_
)
392 self
.conditions_treeview
.set_cursor(path
)
393 self
.active_num
= num
394 self
.set_treeview_string()
395 self
.config_vbox
.set_sensitive(True)
397 def on_delete_button_clicked(self
, widget
):
398 (model
, iter_
) = self
.conditions_treeview
.get_selection().get_selected()
402 iter2
= model
.iter_next(iter_
)
403 num
= self
.active_num
405 num
= model
[iter2
][0]
406 model
[iter2
][0] = num
- 1
407 self
.config
[num
-1] = self
.config
[num
].copy()
408 iter2
= model
.iter_next(iter2
)
412 self
.config_vbox
.set_sensitive(False)
413 self
.delete_button
.set_sensitive(False)
414 self
.up_button
.set_sensitive(False)
415 self
.down_button
.set_sensitive(False)
417 def on_up_button_clicked(self
, widget
):
418 (model
, iter_
) = self
.conditions_treeview
.get_selection().get_selected()
421 conf
= self
.config
[self
.active_num
].copy()
422 self
.config
[self
.active_num
] = self
.config
[self
.active_num
- 1]
423 self
.config
[self
.active_num
- 1] = conf
425 model
[iter_
][0] =self
.active_num
- 1
427 path
= model
.get_path(iter_
)
428 iter_
= model
.get_iter((path
[0] - 1,))
429 model
[iter_
][0] = self
.active_num
430 self
.on_conditions_treeview_cursor_changed(self
.conditions_treeview
)
432 def on_down_button_clicked(self
, widget
):
433 (model
, iter_
) = self
.conditions_treeview
.get_selection().get_selected()
436 conf
= self
.config
[self
.active_num
].copy()
437 self
.config
[self
.active_num
] = self
.config
[self
.active_num
+ 1]
438 self
.config
[self
.active_num
+ 1] = conf
440 model
[iter_
][0] = self
.active_num
+ 1
441 iter_
= model
.iter_next(iter_
)
442 model
[iter_
][0] = self
.active_num
443 self
.on_conditions_treeview_cursor_changed(self
.conditions_treeview
)
445 def on_event_combobox_changed(self
, widget
):
446 if self
.active_num
< 0:
448 active
= self
.event_combobox
.get_active()
452 event
= self
.events_list
[active
]
453 self
.config
[self
.active_num
]['event'] = event
454 self
.set_treeview_string()
456 def on_recipient_type_combobox_changed(self
, widget
):
457 if self
.active_num
< 0:
459 recipient_type
= self
.recipient_types_list
[
460 self
.recipient_type_combobox
.get_active()]
461 self
.config
[self
.active_num
]['recipient_type'] = recipient_type
462 if recipient_type
== 'all':
463 self
.recipient_list_entry
.hide()
465 self
.recipient_list_entry
.show()
466 self
.set_treeview_string()
468 def on_recipient_list_entry_changed(self
, widget
):
469 if self
.active_num
< 0:
471 recipients
= widget
.get_text().decode('utf-8')
473 self
.config
[self
.active_num
]['recipients'] = recipients
474 self
.set_treeview_string()
476 def set_status_config(self
):
477 if self
.active_num
< 0:
480 for st
in ('online', 'away', 'xa', 'dnd', 'invisible'):
481 if self
.__dict
__[st
+ '_cb'].get_active():
485 self
.config
[self
.active_num
]['status'] = status
486 self
.set_treeview_string()
488 def on_status_radiobutton_toggled(self
, widget
):
489 if self
.active_num
< 0:
491 if self
.all_status_rb
.get_active():
492 self
.config
[self
.active_num
]['status'] = 'all'
493 # 'All status' clicked
494 for st
in ('online', 'away', 'xa', 'dnd', 'invisible'):
495 self
.__dict
__[st
+ '_cb'].hide()
497 self
.special_status_rb
.show()
499 self
.set_status_config()
500 # 'special status' clicked
501 for st
in ('online', 'away', 'xa', 'dnd', 'invisible'):
502 self
.__dict
__[st
+ '_cb'].show()
504 self
.special_status_rb
.hide()
505 self
.set_treeview_string()
507 def on_status_cb_toggled(self
, widget
):
508 if self
.active_num
< 0:
510 self
.set_status_config()
512 # tab_opened OR (not xor) not_tab_opened must be active
513 def on_tab_opened_cb_toggled(self
, widget
):
514 if self
.active_num
< 0:
516 if self
.tab_opened_cb
.get_active():
517 if self
.not_tab_opened_cb
.get_active():
518 self
.config
[self
.active_num
]['tab_opened'] = 'both'
520 self
.config
[self
.active_num
]['tab_opened'] = 'yes'
522 self
.not_tab_opened_cb
.set_active(True)
523 self
.config
[self
.active_num
]['tab_opened'] = 'no'
525 def on_not_tab_opened_cb_toggled(self
, widget
):
526 if self
.active_num
< 0:
528 if self
.not_tab_opened_cb
.get_active():
529 if self
.tab_opened_cb
.get_active():
530 self
.config
[self
.active_num
]['tab_opened'] = 'both'
532 self
.config
[self
.active_num
]['tab_opened'] = 'no'
534 self
.tab_opened_cb
.set_active(True)
535 self
.config
[self
.active_num
]['tab_opened'] = 'yes'
537 def on_use_it_toggled(self
, widget
, oposite_widget
, option
):
538 if widget
.get_active():
539 if oposite_widget
.get_active():
540 oposite_widget
.set_active(False)
541 self
.config
[self
.active_num
][option
] = 'yes'
542 elif oposite_widget
.get_active():
543 self
.config
[self
.active_num
][option
] = 'no'
545 self
.config
[self
.active_num
][option
] = ''
547 def on_disable_it_toggled(self
, widget
, oposite_widget
, option
):
548 if widget
.get_active():
549 if oposite_widget
.get_active():
550 oposite_widget
.set_active(False)
551 self
.config
[self
.active_num
][option
] = 'no'
552 elif oposite_widget
.get_active():
553 self
.config
[self
.active_num
][option
] = 'yes'
555 self
.config
[self
.active_num
][option
] = ''
557 def on_use_sound_cb_toggled(self
, widget
):
558 self
.on_use_it_toggled(widget
, self
.disable_sound_cb
, 'sound')
559 if widget
.get_active():
560 self
.sound_file_hbox
.set_sensitive(True)
562 self
.sound_file_hbox
.set_sensitive(False)
564 def on_browse_for_sounds_button_clicked(self
, widget
, data
=None):
565 if self
.active_num
< 0:
568 def on_ok(widget
, path_to_snd_file
):
570 if not path_to_snd_file
:
571 path_to_snd_file
= ''
572 self
.config
[self
.active_num
]['sound_file'] = path_to_snd_file
573 self
.sound_entry
.set_text(path_to_snd_file
)
575 path_to_snd_file
= self
.sound_entry
.get_text().decode('utf-8')
576 path_to_snd_file
= os
.path
.join(os
.getcwd(), path_to_snd_file
)
577 dialog
= SoundChooserDialog(path_to_snd_file
, on_ok
)
579 def on_play_button_clicked(self
, widget
):
580 helpers
.play_sound_file(self
.sound_entry
.get_text().decode('utf-8'))
582 def on_disable_sound_cb_toggled(self
, widget
):
583 self
.on_disable_it_toggled(widget
, self
.use_sound_cb
, 'sound')
585 def on_sound_entry_changed(self
, widget
):
586 self
.config
[self
.active_num
]['sound_file'] = widget
.get_text().\
589 def on_use_popup_cb_toggled(self
, widget
):
590 self
.on_use_it_toggled(widget
, self
.disable_popup_cb
, 'popup')
592 def on_disable_popup_cb_toggled(self
, widget
):
593 self
.on_disable_it_toggled(widget
, self
.use_popup_cb
, 'popup')
595 def on_use_auto_open_cb_toggled(self
, widget
):
596 self
.on_use_it_toggled(widget
, self
.disable_auto_open_cb
, 'auto_open')
598 def on_disable_auto_open_cb_toggled(self
, widget
):
599 self
.on_disable_it_toggled(widget
, self
.use_auto_open_cb
, 'auto_open')
601 def on_run_command_cb_toggled(self
, widget
):
602 self
.config
[self
.active_num
]['run_command'] = widget
.get_active()
603 if widget
.get_active():
604 self
.command_entry
.set_sensitive(True)
606 self
.command_entry
.set_sensitive(False)
608 def on_command_entry_changed(self
, widget
):
609 self
.config
[self
.active_num
]['command'] = widget
.get_text().\
612 def on_use_systray_cb_toggled(self
, widget
):
613 self
.on_use_it_toggled(widget
, self
.disable_systray_cb
, 'systray')
615 def on_disable_systray_cb_toggled(self
, widget
):
616 self
.on_disable_it_toggled(widget
, self
.use_systray_cb
, 'systray')
618 def on_use_roster_cb_toggled(self
, widget
):
619 self
.on_use_it_toggled(widget
, self
.disable_roster_cb
, 'roster')
621 def on_disable_roster_cb_toggled(self
, widget
):
622 self
.on_disable_it_toggled(widget
, self
.use_roster_cb
, 'roster')
624 def on_use_urgency_hint_cb_toggled(self
, widget
):
625 self
.on_use_it_toggled(widget
, self
.disable_urgency_hint_cb
,
628 def on_disable_urgency_hint_cb_toggled(self
, widget
):
629 self
.on_disable_it_toggled(widget
, self
.use_urgency_hint_cb
,
632 def on_hide(self
, widget
):
634 for n
in self
.plugin
.config
:
635 del self
.plugin
.config
[n
]
636 for n
in self
.config
:
637 self
.plugin
.config
[str(n
)] = self
.config
[n
]