diff --git a/data/gui/chat_control.ui b/data/gui/chat_control.ui index 7301c02a3..323998e5b 100644 --- a/data/gui/chat_control.ui +++ b/data/gui/chat_control.ui @@ -939,6 +939,26 @@ 11 + + + True + True + True + none + + + True + False + channel-secure-symbolic.symbolic + + + + + False + True + 12 + + False @@ -1039,7 +1059,7 @@ audio-mic-volume-low False True - 12 + 13 @@ -1050,12 +1070,9 @@ audio-mic-volume-low True True - 13 + 14 - - - _Send @@ -1077,6 +1094,9 @@ audio-mic-volume-low 15 + + + False diff --git a/data/gui/contact_context_menu.ui b/data/gui/contact_context_menu.ui index 85f1c0387..98d846d40 100644 --- a/data/gui/contact_context_menu.ui +++ b/data/gui/contact_context_menu.ui @@ -45,28 +45,6 @@ True - - - True - False - - - - - True - False - Toggle Open_PGP Encryption - True - - - - - True - False - Toggle End to End Encryption - True - - True diff --git a/data/gui/groupchat_control.ui b/data/gui/groupchat_control.ui index 7ab14ecca..7a0d7e8f4 100644 --- a/data/gui/groupchat_control.ui +++ b/data/gui/groupchat_control.ui @@ -1,5 +1,5 @@ - + @@ -127,19 +127,56 @@ - + True - True - never - never - in + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 3 - + + ChatControl-AuthenticationButton + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + none + + + True + False + gtk-dialog-authentication + 1 + + + + + False + False + 0 + + + + + True + True + never + never + in + + + + + + True + True + 1 + False - True + False 1 @@ -214,12 +251,12 @@ True True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True Show a list of formattings none - False True @@ -364,12 +401,12 @@ True True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True Show advanced functions (Alt+D) none - False True @@ -397,7 +434,24 @@ - + + True + True + True + none + + + True + False + channel-secure-symbolic.symbolic + + + + + False + True + 10 + @@ -418,6 +472,9 @@ 11 + + + False diff --git a/data/gui/message_window.ui b/data/gui/message_window.ui index abc5613f1..0aab2b026 100644 --- a/data/gui/message_window.ui +++ b/data/gui/message_window.ui @@ -64,7 +64,7 @@ - + False 480 440 diff --git a/src/chat_control.py b/src/chat_control.py index 83eecc57f..2b1e8fa05 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -90,10 +90,10 @@ class ChatControl(ChatControlBase): ChatControlBase.__init__(self, self.TYPE_ID, parent_win, 'chat_control', contact, acct, resource) - self.gpg_is_active = False self.last_recv_message_id = None self.last_recv_message_marks = None self.last_message_timestamp = None + # for muc use: # widget = self.xml.get_object('muc_window_actions_button') self.actions_button = self.xml.get_object('message_window_actions_button') @@ -280,28 +280,13 @@ class ChatControl(ChatControlBase): # Enable encryption if needed self.no_autonegotiation = False - e2e_is_active = self.session and self.session.enable_encryption - gpg_pref = gajim.config.get_per('contacts', contact.jid, 'gpg_enabled') - - # try GPG first - if not e2e_is_active and gpg_pref and \ - gajim.config.get_per('accounts', self.account, 'keyid') and \ - gajim.connections[self.account].USE_GPG: - self.gpg_is_active = True - gajim.encrypted_chats[self.account].append(contact.jid) - msg = _('OpenPGP encryption enabled') - ChatControlBase.print_conversation_line(self, msg, 'status', '', - None) - - if self.session: - self.session.loggable = gajim.config.get_per('accounts', - self.account, 'log_encrypted_sessions') - # GPG is always authenticated as we use GPG's WoT - self._show_lock_image(self.gpg_is_active, 'OpenPGP', - self.gpg_is_active, self.session and self.session.is_loggable(), - True) self.update_ui() + self.set_lock_image() + + self.encryption_menu = self.xml.get_object('encryption_menu') + self.encryption_menu.set_menu_model( + gui_menu_builder.get_encryption_menu(self.contact, self.type_id)) # restore previous conversation self.restore_conversation() self.msg_textview.grab_focus() @@ -344,7 +329,8 @@ class ChatControl(ChatControlBase): send_button = self.xml.get_object('send_button') send_button.set_sensitive(True) # Formatting - if self.contact.supports(NS_XHTML_IM) and not self.gpg_is_active: + # TODO: find out what encryption allows for xhtml and which not + if self.contact.supports(NS_XHTML_IM): self._formattings_button.set_sensitive(True) self._formattings_button.set_tooltip_text(_( 'Show a list of formattings')) @@ -856,114 +842,64 @@ class ChatControl(ChatControlBase): def on_video_button_toggled(self, widget): self.on_jingle_button_toggled(widget, 'video') - def _toggle_gpg(self): - if not self.gpg_is_active and not self.contact.keyID: - dialogs.ErrorDialog(_('No OpenPGP key assigned'), - _('No OpenPGP key is assigned to this contact. So you cannot ' - 'encrypt messages with OpenPGP.')) - return - ec = gajim.encrypted_chats[self.account] - if self.gpg_is_active: - # Disable encryption - ec.remove(self.contact.jid) - self.gpg_is_active = False - loggable = False - msg = _('OpenPGP encryption disabled') - ChatControlBase.print_conversation_line(self, msg, 'status', '', - None) - if self.session: - self.session.loggable = True + def set_lock_image(self): + visible = self.encryption != 'disabled' + loggable = self.session and self.session.is_loggable() - else: - # Enable encryption - ec.append(self.contact.jid) - self.gpg_is_active = True - msg = _('OpenPGP encryption enabled') - ChatControlBase.print_conversation_line(self, msg, 'status', '', - None) + encryption_state = {'visible': visible, + 'enc_type': self.encryption, + 'authenticated': False} - loggable = gajim.config.get_per('accounts', self.account, - 'log_encrypted_sessions') + gajim.plugin_manager.gui_extension_point( + 'encryption_state' + self.encryption, self, encryption_state) - if self.session: - self.session.loggable = loggable + self._show_lock_image(**encryption_state) - loggable = self.session.is_loggable() - else: - loggable = loggable and gajim.config.should_log(self.account, - self.contact.jid) - - if loggable: - msg = _('Session WILL be logged') - else: - msg = _('Session WILL NOT be logged') - - ChatControlBase.print_conversation_line(self, msg, - 'status', '', None) - - gajim.config.set_per('contacts', self.contact.jid, - 'gpg_enabled', self.gpg_is_active) - - self._show_lock_image(self.gpg_is_active, 'OpenPGP', - self.gpg_is_active, loggable, True) - - def _show_lock_image(self, visible, enc_type='', enc_enabled=False, - chat_logged=False, authenticated=False): + def _show_lock_image(self, visible, enc_type='', + authenticated=False): """ Set lock icon visibility and create tooltip """ - #encryption %s active - status_string = enc_enabled and _('is') or _('is NOT') - #chat session %s be logged - logged_string = chat_logged and _('will') or _('will NOT') - if authenticated: - #About encrypted chat session authenticated_string = _('and authenticated') img_path = gtkgui_helpers.get_icon_path('security-high') else: - #About encrypted chat session authenticated_string = _('and NOT authenticated') img_path = gtkgui_helpers.get_icon_path('security-low') self.lock_image.set_from_file(img_path) - #status will become 'is' or 'is not', authentificaed will become - #'and authentificated' or 'and not authentificated', logged will become - #'will' or 'will not' - tooltip = _('%(type)s encryption %(status)s active %(authenticated)s.\n' - 'Your chat session %(logged)s be logged.') % {'type': enc_type, - 'status': status_string, 'authenticated': authenticated_string, - 'logged': logged_string} + tooltip = _('%(type)s encryption is active %(authenticated)s.') % {'type': enc_type, 'authenticated': authenticated_string} self.authentication_button.set_tooltip_text(tooltip) self.widget_set_visible(self.authentication_button, not visible) - self.lock_image.set_sensitive(enc_enabled) + self.lock_image.set_sensitive(visible) def _on_authentication_button_clicked(self, widget): - if self.gpg_is_active: - dialogs.GPGInfoWindow(self, self.parent_win.window) - elif self.session and self.session.enable_encryption: - dialogs.ESessionInfoWindow(self.session, self.parent_win.window) + gajim.plugin_manager.gui_extension_point( + 'encryption_dialog' + self.encryption, self) def send_message(self, message, keyID='', chatstate=None, xhtml=None, process_commands=True, attention=False): """ Send a message to contact """ + + if self.encryption: + self.sendmessage = True + gajim.plugin_manager.gui_extension_point( + 'send_message' + self.encryption, self) + if not self.sendmessage: + return + message = helpers.remove_invalid_xml_chars(message) if message in ('', None, '\n'): return None contact = self.contact + keyID = contact.keyID - encrypted = bool(self.session) and self.session.enable_encryption - - keyID = '' - if self.gpg_is_active: - keyID = contact.keyID - encrypted = True - if not keyID: - keyID = 'UNKNOWN' + chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \ + 'disabled' chatstate_to_send = None if contact is not None: @@ -999,7 +935,7 @@ class ChatControl(ChatControlBase): ChatControlBase.send_message(self, message, keyID, type_='chat', chatstate=chatstate_to_send, xhtml=xhtml, callback=_on_sent, - callback_args=[message, encrypted, xhtml, self.get_seclabel()], + callback_args=[message, self.encryption, xhtml, self.get_seclabel()], process_commands=process_commands, attention=attention) @@ -1040,8 +976,8 @@ class ChatControl(ChatControlBase): msg = _('E2E encryption disabled') ChatControlBase.print_conversation_line(self, msg, 'status', '', None) - self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \ - self.session.is_loggable(), self.session and self.session.verified_identity) + self._show_lock_image(e2e_is_active, 'E2E', + self.session and self.session.verified_identity) def print_session_details(self, old_session=None): if isinstance(self.session, EncryptedStanzaSession) or \ @@ -1092,20 +1028,6 @@ class ChatControl(ChatControlBase): msg = _('The following message was NOT encrypted') ChatControlBase.print_conversation_line(self, msg, 'status', '', tim) - else: - # GPG encryption - if encrypted and not self.gpg_is_active: - msg = _('The following message was encrypted') - ChatControlBase.print_conversation_line(self, msg, 'status', - '', tim) - # turn on OpenPGP if this was in fact a XEP-0027 encrypted - # message - if encrypted == 'xep27': - self._toggle_gpg() - elif not encrypted and self.gpg_is_active: - msg = _('The following message was NOT encrypted') - ChatControlBase.print_conversation_line(self, msg, 'status', - '', tim) if not frm: kind = 'incoming' name = contact.get_shown_name() @@ -1115,7 +1037,7 @@ class ChatControl(ChatControlBase): else: kind = 'outgoing' name = self.get_our_nick() - if not xhtml and not (encrypted and self.gpg_is_active) and \ + if not xhtml and not encrypted and \ gajim.config.get('rst_formatting_outgoing_messages'): from common.rst_xhtml_generator import create_xhtml xhtml = create_xhtml(text) @@ -1192,9 +1114,8 @@ class ChatControl(ChatControlBase): def prepare_context_menu(self, hide_buttonbar_items=False): """ Set compact view menuitem active state sets active and sensitivity state - for toggle_gpg_menuitem sets sensitivity for history_menuitem (False for - tranasports) and file_transfer_menuitem and hide()/show() for - add_to_roster_menuitem + for history_menuitem (False for tranasports) and file_transfer_menuitem + and hide()/show() for add_to_roster_menuitem """ if gajim.jid_is_transport(self.contact.jid): menu = gui_menu_builder.get_transport_menu(self.contact, @@ -1481,19 +1402,9 @@ class ChatControl(ChatControlBase): def _on_message_tv_buffer_changed(self, textbuffer): super()._on_message_tv_buffer_changed(textbuffer) if textbuffer.get_char_count(): - e2e_is_active = self.session and \ - self.session.enable_encryption - e2e_pref = gajim.config.get_per('accounts', self.account, - 'enable_esessions') and gajim.config.get_per('accounts', - self.account, 'autonegotiate_esessions') and gajim.config.get_per( - 'contacts', self.contact.jid, 'autonegotiate_esessions') - want_e2e = not e2e_is_active and not self.gpg_is_active \ - and e2e_pref - - if want_e2e and not self.no_autonegotiation \ - and gajim.HAVE_PYCRYPTO and self.contact.supports(NS_ESESSION): - self.begin_e2e_negotiation() - elif (not self.session or not self.session.status) and \ + gajim.plugin_manager.gui_extension_point( + 'typing' + self.encryption, self) + if (not self.session or not self.session.status) and \ gajim.connections[self.account].archiving_136_supported: self.begin_archiving_negotiation() @@ -1684,31 +1595,29 @@ class ChatControl(ChatControlBase): def _on_contact_information_menuitem_activate(self, widget): gajim.interface.roster.on_info(widget, self.contact, self.account) - def _on_toggle_gpg_menuitem_activate(self, widget): - self._toggle_gpg() - def _on_convert_to_gc_menuitem_activate(self, widget): """ User wants to invite some friends to chat """ dialogs.TransformChatToMUC(self.account, [self.contact.jid]) - def _on_toggle_e2e_menuitem_activate(self, widget): - if self.session and self.session.enable_encryption: - # e2e was enabled, disable it - jid = str(self.session.jid) - thread_id = self.session.thread_id - - self.session.terminate_e2e() - - gajim.connections[self.account].delete_session(jid, thread_id) - - # presumably the user had a good reason to shut it off, so - # disable autonegotiation too - self.no_autonegotiation = True - else: + def activate_esessions(self): + if not (self.session and self.session.enable_encryption): self.begin_e2e_negotiation() + def terminate_esessions(self): + # e2e was enabled, disable it + jid = str(self.session.jid) + thread_id = self.session.thread_id + + self.session.terminate_e2e() + + gajim.connections[self.account].delete_session(jid, thread_id) + + # presumably the user had a good reason to shut it off, so + # disable autonegotiation too + self.no_autonegotiation = True + def begin_negotiation(self): self.no_autonegotiation = True diff --git a/src/chat_control_base.py b/src/chat_control_base.py index a81533b6a..299dda9ca 100644 --- a/src/chat_control_base.py +++ b/src/chat_control_base.py @@ -34,6 +34,7 @@ from gi.repository import Gdk from gi.repository import Pango from gi.repository import GObject from gi.repository import GLib +from gi.repository import Gio import gtkgui_helpers import message_control import dialogs @@ -395,6 +396,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): self._on_window_motion_notify) self.handlers[id_] = parent_win.window + self.encryption = 'disabled' + self.set_encryption_state() + if self.parent_win: + self.add_window_actions() + # PluginSystem: adding GUI extension point for ChatControlBase # instance object (also subclasses, eg. ChatControl or GroupchatControl) gajim.plugin_manager.gui_extension_point('chat_control_base', self) @@ -412,6 +418,41 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): # to properly use the super, because of the old code. CommandTools.__init__(self) + def add_window_actions(self): + action = Gio.SimpleAction.new_stateful( + "%s-encryptiongroup" % self.contact.jid, + GLib.VariantType.new("s"), + GLib.Variant("s", self.encryption)) + action.connect("change-state", self.activate_encryption) + self.parent_win.window.add_action(action) + + def activate_encryption(self, action, param): + encryption = param.get_string() + if self.encryption == encryption: + return + + if encryption != 'disabled': + plugin = gajim.plugin_manager.encryption_plugins[encryption] + if not plugin.activate_encryption(self): + return + else: + if not self.widget_name == 'groupchat_control': + self.terminate_esessions() + action.set_state(param) + gajim.config.set_per( + 'contacts', self.contact.jid, 'encryption', encryption) + self.encryption = encryption + self.set_lock_image() + + def set_encryption_state(self): + enc = gajim.config.get_per('contacts', self.contact.jid, 'encryption') + if enc not in gajim.plugin_manager.encryption_plugins: + self.encryption = 'disabled' + gajim.config.set_per( + 'contacts', self.contact.jid, 'encryption', 'disabled') + else: + self.encryption = enc + def set_speller(self): # now set the one the user selected per_type = 'contacts' @@ -723,7 +764,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): keyID=keyID, type_=type_, chatstate=chatstate, msg_id=msg_id, resource=resource, user_nick=self.user_nick, xhtml=xhtml, label=label, callback=_cb, callback_args=[callback] + callback_args, - control=self, attention=attention, correction_msg=correction_msg, automatic_message=False)) + control=self, attention=attention, correction_msg=correction_msg, + automatic_message=False, encryption=self.encryption)) # Record the history of sent messages self.save_message(message, 'sent') diff --git a/src/command_system/implementation/standard.py b/src/command_system/implementation/standard.py index f7e1daa91..4caba25d7 100644 --- a/src/command_system/implementation/standard.py +++ b/src/command_system/implementation/standard.py @@ -179,11 +179,6 @@ class StandardCommonChatCommands(CommandContainer): def clear(self): self.conv_textview.clear() - @command - @doc(_("Toggle the OpenPGP encryption")) - def gpg(self): - self._toggle_gpg() - @command @doc(_("Send a ping to the contact")) def ping(self): diff --git a/src/common/config.py b/src/common/config.py index 3411bbb4c..9c8585fed 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -480,8 +480,7 @@ class Config: 'state_muc_directed_msg_color': [ opt_color, 'red2' ], }, {}), 'contacts': ({ - 'gpg_enabled': [ opt_bool, False, _('Is OpenPGP enabled for this contact?')], - 'autonegotiate_esessions': [opt_bool, False, _('Should Gajim automatically start an encrypted session with this contact when possible?')], + 'encryption': [ opt_str, '', _('Encryption used for this contact.')], 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')], }, {}), 'rooms': ({ diff --git a/src/common/connection.py b/src/common/connection.py index efc77bba9..094625bb5 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -296,65 +296,9 @@ class CommonConnection: if not obj.message and obj.chatstate is None and obj.form_node is None: return - if obj.keyID and self.USE_GPG: - self._encrypt_message(obj) - return - self._build_message_stanza(obj) - def _encrypt_message(self, obj): - obj.xhtml = None - if obj.keyID == 'UNKNOWN': - error = _('Neither the remote presence is signed, nor a key was ' - 'assigned.') - elif obj.keyID.endswith('MISMATCH'): - error = _('The contact\'s key (%s) does not match the key assigned ' - 'in Gajim.' % obj.keyID[:8]) - else: - myKeyID = gajim.config.get_per('accounts', self.name, 'keyid') - key_list = [obj.keyID, myKeyID] - def _on_encrypted(output): - msgenc, error = output - if error.startswith('NOT_TRUSTED'): - def _on_always_trust(answer): - if answer: - gajim.thread_interface( - self.gpg.encrypt, [obj.message, key_list, True], - _on_encrypted, []) - else: - self._finished_encrypt(obj, msgenc=msgenc, - error=error) - gajim.nec.push_incoming_event(GPGTrustKeyEvent(None, - conn=self, keyID=error.split(' ')[-1], - callback=_on_always_trust)) - else: - self._finished_encrypt(obj, msgenc=msgenc, error=error) - gajim.thread_interface( - self.gpg.encrypt, [obj.message, key_list, False], - _on_encrypted, []) - return - self._finished_encrypt(obj, error=error) - - def _finished_encrypt(self, obj, msgenc=None, error=None): - if error: - gajim.nec.push_incoming_event( - MessageNotSentEvent( - None, conn=self, jid=obj.jid, message=obj.message, - error=error, time_=time.time(), session=obj.session)) - return - self._build_message_stanza(obj, msgenc) - - def _build_message_stanza(self, obj, msgenc=None): - if msgenc: - msgtxt = '[This message is *encrypted* (See :XEP:`27`]' - lang = os.getenv('LANG') - if lang is not None and not lang.startswith('en'): - # we're not english: one in locale and one en - msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \ - ' (' + msgtxt + ')' - else: - msgtxt = obj.message - + def _build_message_stanza(self, obj): if obj.jid == gajim.get_jid_from_account(self.name): fjid = obj.jid else: @@ -368,44 +312,30 @@ class CommonConnection: namespace=nbxmpp.NS_CORRECT) id2 = self.connection.getAnID() obj.correction_msg.setID(id2) - obj.correction_msg.setBody(msgtxt) + obj.correction_msg.setBody(obj.message) if obj.xhtml: obj.correction_msg.setXHTML(obj.xhtml) - if msgenc: - encrypted_tag = obj.correction_msg.getTag( - 'x', namespace=nbxmpp.NS_ENCRYPTED) - obj.correction_msg.delChild(encrypted_tag) - obj.correction_msg.setTag( - 'x', namespace=nbxmpp.NS_ENCRYPTED).setData(msgenc) - if obj.session: obj.session.last_send = time.time() - # XEP-0200 - if obj.session.enable_encryption: - obj.correction_msg = obj.session.encrypt_stanza(obj.correction_msg) - self._push_stanza_message_outgoing(obj, obj.correction_msg) return if obj.type_ == 'chat': - msg_iq = nbxmpp.Message(body=msgtxt, typ=obj.type_, + msg_iq = nbxmpp.Message(body=obj.message, typ=obj.type_, xhtml=obj.xhtml) else: if obj.subject: - msg_iq = nbxmpp.Message(body=msgtxt, typ='normal', + msg_iq = nbxmpp.Message(body=obj.message, typ='normal', subject=obj.subject, xhtml=obj.xhtml) else: - msg_iq = nbxmpp.Message(body=msgtxt, typ='normal', + msg_iq = nbxmpp.Message(body=obj.message, typ='normal', xhtml=obj.xhtml) if obj.msg_id: msg_iq.setID(obj.msg_id) - if msgenc: - msg_iq.setTag('x', namespace=nbxmpp.NS_ENCRYPTED).setData(msgenc) - if obj.form_node: msg_iq.addChild(node=obj.form_node) if obj.label: @@ -460,7 +390,7 @@ class CommonConnection: if obj.chatstate and contact and contact.supports(nbxmpp.NS_CHATSTATES): msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES) only_chatste = False - if not msgtxt: + if not obj.message: only_chatste = True if only_chatste and not obj.session.enable_encryption: msg_iq.setTag('no-store', @@ -468,8 +398,9 @@ class CommonConnection: # XEP-0184 if obj.jid != gajim.get_jid_from_account(self.name): - if msgtxt and gajim.config.get_per('accounts', self.name, - 'request_receipt'): + request = gajim.config.get_per('accounts', self.name, + 'request_receipt') + if obj.message and request: msg_iq.setTag('request', namespace=nbxmpp.NS_RECEIPTS) if obj.forward_from: @@ -483,23 +414,9 @@ class CommonConnection: obj.session.last_send = time.time() msg_iq.setThread(obj.session.thread_id) - # XEP-0200 - if obj.session.enable_encryption: - msg_iq = obj.session.encrypt_stanza(msg_iq) - if self.carbons_enabled: - msg_iq.addChild(name='private', - namespace=nbxmpp.NS_CARBONS) - msg_iq.addChild(name='no-permanent-store', - namespace=nbxmpp.NS_MSG_HINTS) - msg_iq.addChild(name='no-copy', - namespace=nbxmpp.NS_MSG_HINTS) - if only_chatste: - msg_iq.addChild(name='no-store', - namespace=nbxmpp.NS_MSG_HINTS) - self._push_stanza_message_outgoing(obj, msg_iq) - def _push_stanza_message_outgoing(self, obj, msg_iq): + def _push_stanza_message_outgoing(self, obj, msg_iq): obj.conn = self if isinstance(msg_iq, list): for iq in msg_iq: @@ -2136,6 +2053,14 @@ class Connection(CommonConnection, ConnectionHandlers): def _nec_stanza_message_outgoing(self, obj): if obj.conn.name != self.name: return + encryption = gajim.config.get_per('contacts', obj.jid, 'encryption') + if encryption != 'disabled': + gajim.plugin_manager.gui_extension_point( + 'encrypt' + encryption, self, obj, self.send_message) + else: + self.send_message(obj) + + def send_message(self, obj): obj.msg_id = self.connection.send(obj.msg_iq, now=obj.now) gajim.nec.push_incoming_event(MessageSentEvent( @@ -2747,6 +2672,14 @@ class Connection(CommonConnection, ConnectionHandlers): def _nec_gc_stanza_message_outgoing(self, obj): if obj.conn.name != self.name: return + encryption = gajim.config.get_per('contacts', obj.jid, 'encryption') + if encryption != 'disabled': + gajim.plugin_manager.gui_extension_point( + 'gc_encrypt' + encryption, self, obj, self.send_gc_message) + else: + self.send_gc_message(obj) + + def send_gc_message(self, obj): if obj.correction_msg: obj.msg_id = self.connection.send(obj.correction_msg) else: diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index 7252f9046..80f4cf19d 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -897,7 +897,7 @@ class ConnectionHandlersBase: gajim.ged.register_event_handler('message-received', ged.CORE, self._nec_message_received) gajim.ged.register_event_handler('mam-message-received', ged.CORE, - self._nec_mam_message_received) + self._nec_message_received) gajim.ged.register_event_handler('decrypted-message-received', ged.CORE, self._nec_decrypted_message_received) @@ -911,7 +911,7 @@ class ConnectionHandlersBase: gajim.ged.remove_event_handler('message-received', ged.CORE, self._nec_message_received) gajim.ged.remove_event_handler('mam-message-received', ged.CORE, - self._nec_mam_message_received) + self._nec_message_received) gajim.ged.remove_event_handler('decrypted-message-received', ged.CORE, self._nec_decrypted_message_received) @@ -1080,78 +1080,22 @@ class ConnectionHandlersBase: if sess.enable_encryption: sess.terminate_e2e() - def decrypt_thread(self, encmsg, keyID, obj): - decmsg = self.gpg.decrypt(encmsg, keyID) - decmsg = self.connection.Dispatcher.replace_non_character(decmsg) - # \x00 chars are not allowed in C (so in GTK) - obj.msgtxt = decmsg.replace('\x00', '') - obj.encrypted = 'xep27' - self.gpg_messages_to_decrypt.remove([encmsg, keyID, obj]) - def _nec_message_received(self, obj): if obj.conn.name != self.name: return - if obj.encrypted == 'xep200': - try: - obj.stanza = obj.session.decrypt_stanza(obj.stanza) - obj.msgtxt = obj.stanza.getBody() - except Exception: - gajim.nec.push_incoming_event(FailedDecryptEvent(None, - conn=self, msg_obj=obj)) - return - if obj.enc_tag and self.USE_GPG: - encmsg = obj.enc_tag.getData() + gajim.plugin_manager.gui_extension_point( + 'decrypt', self, obj, self._on_message_received) + if not obj.encrypted: + self._on_message_received(obj) - keyID = gajim.config.get_per('accounts', self.name, 'keyid') - if keyID: - self.gpg_messages_to_decrypt.append([encmsg, keyID, obj]) - if len(self.gpg_messages_to_decrypt) == 1: - gajim.thread_interface(self.decrypt_thread, [encmsg, keyID, - obj], self._on_message_decrypted, [obj]) - return - gajim.nec.push_incoming_event(DecryptedMessageReceivedEvent(None, - conn=self, msg_obj=obj)) - - def _nec_mam_message_received(self, obj): - if obj.conn.name != self.name: - return - if obj.enc_tag and self.USE_GPG: - encmsg = obj.enc_tag.getData() - - keyID = gajim.config.get_per('accounts', self.name, 'keyid') - if keyID: - self.gpg_messages_to_decrypt.append([encmsg, keyID, obj]) - if len(self.gpg_messages_to_decrypt) == 1: - gajim.thread_interface(self.decrypt_thread, [encmsg, keyID, - obj], self._on_mam_message_decrypted, [obj]) - return - gajim.nec.push_incoming_event(MamDecryptedMessageReceivedEvent(None, - conn=self, msg_obj=obj)) - - def _on_message_decrypted(self, output, obj): - if len(self.gpg_messages_to_decrypt): - encmsg, keyID, obj2 = self.gpg_messages_to_decrypt[0] - if type(obj2) == MessageReceivedEvent: - cb = self._on_message_decrypted - else: - cb = self._on_mam_message_decrypted - gajim.thread_interface(self.decrypt_thread, [encmsg, keyID, obj2], - cb, [obj2]) - gajim.nec.push_incoming_event(DecryptedMessageReceivedEvent(None, - conn=self, msg_obj=obj)) - - def _on_mam_message_decrypted(self, output, obj): - if len(self.gpg_messages_to_decrypt): - encmsg, keyID, obj2 = self.gpg_messages_to_decrypt[0] - if type(obj2) == MessageReceivedEvent: - cb = self._on_message_decrypted - else: - cb = self._on_mam_message_decrypted - gajim.thread_interface(self.decrypt_thread, [encmsg, keyID, obj2], - cb, [obj2]) - gajim.nec.push_incoming_event(MamDecryptedMessageReceivedEvent(None, - conn=self, msg_obj=obj)) + def _on_message_received(self, obj): + if isinstance(obj, MessageReceivedEvent): + gajim.nec.push_incoming_event( + DecryptedMessageReceivedEvent(None, conn=self, msg_obj=obj)) + else: + gajim.nec.push_incoming_event( + MamDecryptedMessageReceivedEvent(None, conn=self, msg_obj=obj)) def _nec_decrypted_message_received(self, obj): if obj.conn.name != self.name: diff --git a/src/common/connection_handlers_events.py b/src/common/connection_handlers_events.py index 286f935fb..ed2a9dd41 100644 --- a/src/common/connection_handlers_events.py +++ b/src/common/connection_handlers_events.py @@ -1035,6 +1035,7 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): def init(self): self.additional_data = {} + self.encrypted = False def generate(self): if not self.stanza: @@ -1067,7 +1068,6 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): self.with_ = to_ self.direction = 'to' self.resource = gajim.get_resource_from_jid(self.msg_.getAttr('to')) - self.enc_tag = self.msg_.getTag('x', namespace=nbxmpp.NS_ENCRYPTED) return True class MamDecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): @@ -1122,6 +1122,7 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): self.get_id() self.forwarded = False self.sent = False + self.encrypted = False account = self.conn.name our_full_jid = gajim.get_jid_from_account(account, full=True) @@ -1216,33 +1217,31 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): conn=self.conn, stanza=forwarded)) return - self.enc_tag = self.stanza.getTag('x', namespace=nbxmpp.NS_ENCRYPTED) - if not self.enc_tag: - # Mediated invitation? - muc_user = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER) - if muc_user: - if muc_user.getTag('decline'): - gajim.nec.push_incoming_event( - GcDeclineReceivedEvent( - None, conn=self.conn, - room_jid=self.fjid, stanza=muc_user)) - return - if muc_user.getTag('invite'): - gajim.nec.push_incoming_event( - GcInvitationReceivedEvent( - None, conn=self.conn, jid_from=self.fjid, - mediated=True, stanza=muc_user)) - return - else: - # Direct invitation? - direct = self.stanza.getTag( - 'x', namespace=nbxmpp.NS_CONFERENCE) - if direct: - gajim.nec.push_incoming_event( - GcInvitationReceivedEvent( - None, conn=self.conn, jid_from=self.fjid, - mediated=False, stanza=direct)) - return + # Mediated invitation? + muc_user = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER) + if muc_user: + if muc_user.getTag('decline'): + gajim.nec.push_incoming_event( + GcDeclineReceivedEvent( + None, conn=self.conn, + room_jid=self.fjid, stanza=muc_user)) + return + if muc_user.getTag('invite'): + gajim.nec.push_incoming_event( + GcInvitationReceivedEvent( + None, conn=self.conn, jid_from=self.fjid, + mediated=True, stanza=muc_user)) + return + else: + # Direct invitation? + direct = self.stanza.getTag( + 'x', namespace=nbxmpp.NS_CONFERENCE) + if direct: + gajim.nec.push_incoming_event( + GcInvitationReceivedEvent( + None, conn=self.conn, jid_from=self.fjid, + mediated=False, stanza=direct)) + return self.thread_id = self.stanza.getThread() self.mtype = self.stanza.getType() @@ -1283,52 +1282,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): self.session.last_receive = time_time() - # check if the message is a XEP-0020 feature negotiation request - if not self.forwarded and self.stanza.getTag('feature', - namespace=nbxmpp.NS_FEATURE): - if gajim.HAVE_PYCRYPTO: - feature = self.stanza.getTag(name='feature', - namespace=nbxmpp.NS_FEATURE) - form = nbxmpp.DataForm(node=feature.getTag('x')) - if not form: - return - - if not form.getField('FORM_TYPE'): - return - - if form['FORM_TYPE'] == 'urn:xmpp:ssn': - self.session.handle_negotiation(form) - else: - reply = self.stanza.buildReply() - reply.setType('error') - reply.addChild(feature) - err = nbxmpp.ErrorNode('service-unavailable', typ='cancel') - reply.addChild(node=err) - self.conn.connection.send(reply) - return - - if not self.forwarded and self.stanza.getTag('init', - namespace=nbxmpp.NS_ESESSION_INIT): - init = self.stanza.getTag(name='init', - namespace=nbxmpp.NS_ESESSION_INIT) - form = nbxmpp.DataForm(node=init.getTag('x')) - - self.session.handle_negotiation(form) - - return - self._generate_timestamp(self.stanza.getTimestamp()) - - self.encrypted = False - xep_200_encrypted = self.stanza.getTag('c', - namespace=nbxmpp.NS_STANZA_CRYPTO) - if xep_200_encrypted: - if self.forwarded: - # Ignore E2E forwarded encrypted messages - return False - self.encrypted = 'xep200' - return True class ZeroconfMessageReceivedEvent(MessageReceivedEvent): @@ -2796,6 +2751,7 @@ class MessageOutgoingEvent(nec.NetworkOutgoingEvent): self.attention = False self.correction_msg = None self.automatic_message = True + self.encryption = '' def get_full_jid(self): if self.resource: diff --git a/src/dialogs.py b/src/dialogs.py index 549bb05e2..48203b566 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -5315,140 +5315,6 @@ class DataFormWindow(Dialog): self.df_response_ok(form) self.destroy() -class ESessionInfoWindow: - """ - Class for displaying information about a XEP-0116 encrypted session - """ - def __init__(self, session, transient_for=None): - self.session = session - - self.xml = gtkgui_helpers.get_gtk_builder('esession_info_window.ui') - self.xml.connect_signals(self) - - self.security_image = self.xml.get_object('security_image') - self.verify_now_button = self.xml.get_object('verify_now_button') - self.button_label = self.xml.get_object('verification_status_label') - self.window = self.xml.get_object('esession_info_window') - self.update_info() - self.window.set_transient_for(transient_for) - - self.window.show_all() - - def update_info(self): - labeltext = _('''Your chat session with %(jid)s is encrypted.\n\nThis session's Short Authentication String is %(sas)s.''') % {'jid': self.session.jid, 'sas': self.session.sas} - - if self.session.verified_identity: - labeltext += '\n\n' + _('''You have already verified this contact's identity.''') - security_image = 'security-high' - if self.session.control: - self.session.control._show_lock_image(True, 'E2E', True, - self.session.is_loggable(), True) - - verification_status = _('''Contact's identity verified''') - self.window.set_title(verification_status) - self.xml.get_object('verification_status_label').set_markup( - '%s' % verification_status) - - self.xml.get_object('dialog-action_area1').set_no_show_all(True) - self.button_label.set_text(_('Verify again…')) - else: - if self.session.control: - self.session.control._show_lock_image(True, 'E2E', True, - self.session.is_loggable(), False) - labeltext += '\n\n' + _('''To be certain that only the expected person can read your messages or send you messages, you need to verify their identity by clicking the button below.''') - security_image = 'security-low' - - verification_status = _('''Contact's identity NOT verified''') - self.window.set_title(verification_status) - self.xml.get_object('verification_status_label').set_markup( - '%s' % verification_status) - - self.button_label.set_text(_('Verify…')) - - path = gtkgui_helpers.get_icon_path(security_image, 32) - self.security_image.set_from_file(path) - - self.xml.get_object('info_display').set_markup(labeltext) - - def on_close_button_clicked(self, widget): - self.window.destroy() - - def on_verify_now_button_clicked(self, widget): - pritext = _('''Have you verified the contact's identity?''') - sectext = _('''To prevent talking to an unknown person, you should speak to %(jid)s directly (in person or on the phone) and verify that they see the same Short Authentication String (SAS) as you.\n\nThis session's Short Authentication String is %(sas)s.''') % {'jid': self.session.jid, 'sas': self.session.sas} - sectext += '\n\n' + _('Did you talk to the remote contact and verify the SAS?') - - def on_yes(checked): - self.session._verified_srs_cb() - self.session.verified_identity = True - self.update_info() - - def on_no(): - self.session._unverified_srs_cb() - self.session.verified_identity = False - self.update_info() - - YesNoDialog(pritext, sectext, on_response_yes=on_yes, - on_response_no=on_no, transient_for=self.window) - -class GPGInfoWindow: - """ - Class for displaying information about a XEP-0116 encrypted session - """ - def __init__(self, control, transient_for=None): - xml = gtkgui_helpers.get_gtk_builder('esession_info_window.ui') - security_image = xml.get_object('security_image') - status_label = xml.get_object('verification_status_label') - info_label = xml.get_object('info_display') - verify_now_button = xml.get_object('verify_now_button') - self.window = xml.get_object('esession_info_window') - account = control.account - keyID = control.contact.keyID - error = None - - verify_now_button.set_no_show_all(True) - verify_now_button.hide() - - if keyID.endswith('MISMATCH'): - verification_status = _('''Contact's identity NOT verified''') - info = _('The contact\'s key (%s) does not match the key ' - 'assigned in Gajim.') % keyID[:8] - image = 'security-low' - elif not keyID: - # No key assigned nor a key is used by remote contact - verification_status = _('No OpenPGP key assigned') - info = _('No OpenPGP key is assigned to this contact. So you cannot' - ' encrypt messages.') - image = 'security-low' - else: - error = gajim.connections[account].gpg.encrypt('test', [keyID])[1] - if error: - verification_status = _('''Contact's identity NOT verified''') - info = _('OpenPGP key is assigned to this contact, but you ' - 'do not trust their key, so message cannot be ' - 'encrypted. Use your OpenPGP client to trust their key.') - image = 'security-low' - else: - verification_status = _('''Contact's identity verified''') - info = _('OpenPGP Key is assigned to this contact, and you ' - 'trust their key, so messages will be encrypted.') - image = 'security-high' - - status_label.set_markup('%s' % \ - verification_status) - info_label.set_markup(info) - - path = gtkgui_helpers.get_icon_path(image, 32) - security_image.set_from_file(path) - - self.window.set_transient_for(transient_for) - xml.connect_signals(self) - self.window.show_all() - - def on_close_button_clicked(self, widget): - self.window.destroy() - - class ResourceConflictDialog(TimeoutDialog, InputDialog): def __init__(self, title, text, resource, ok_handler): diff --git a/src/groupchat_control.py b/src/groupchat_control.py index facef7ece..17f30a3c0 100644 --- a/src/groupchat_control.py +++ b/src/groupchat_control.py @@ -257,9 +257,8 @@ class PrivateChatControl(ChatControl): def prepare_context_menu(self, hide_buttonbar_items=False): """ Set compact view menuitem active state sets active and sensitivity state - for toggle_gpg_menuitem sets sensitivity for history_menuitem (False for - tranasports) and file_transfer_menuitem and hide()/show() for - add_to_roster_menuitem + for history_menuitem (False for tranasports) and file_transfer_menuitem + and hide()/show() for add_to_roster_menuitem """ menu = gui_menu_builder.get_contact_menu(self.contact, self.account, use_multiple_contacts=False, show_start_chat=False, @@ -482,6 +481,19 @@ class GroupchatControl(ChatControlBase): self.form_widget = None + # Encryption + self.lock_image = self.xml.get_object('lock_image') + self.authentication_button = self.xml.get_object( + 'authentication_button') + id_ = self.authentication_button.connect('clicked', + self._on_authentication_button_clicked) + self.handlers[id_] = self.authentication_button + self.set_lock_image() + + self.encryption_menu = self.xml.get_object('encryption_menu') + self.encryption_menu.set_menu_model( + gui_menu_builder.get_encryption_menu(self.contact, self.type_id)) + gajim.ged.register_event_handler('gc-presence-received', ged.GUI1, self._nec_gc_presence_received) gajim.ged.register_event_handler('gc-message-received', ged.GUI1, @@ -509,6 +521,11 @@ class GroupchatControl(ChatControlBase): # instance object gajim.plugin_manager.gui_extension_point('groupchat_control', self) + def on_groupchat_maximize(self): + self.set_tooltip() + self.add_window_actions() + self.set_lock_image() + def set_tooltip(self): widget = self.xml.get_object('list_treeview') if widget.get_tooltip_window(): @@ -752,6 +769,42 @@ class GroupchatControl(ChatControlBase): for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): self.draw_contact(nick) + def set_lock_image(self): + visible = self.encryption != 'disabled' + + encryption_state = {'visible': visible, + 'enc_type': self.encryption, + 'authenticated': False} + + gajim.plugin_manager.gui_extension_point( + 'encryption_state' + self.encryption, self, encryption_state) + + self._show_lock_image(**encryption_state) + + def _show_lock_image(self, visible, enc_type='', + authenticated=False): + """ + Set lock icon visibility and create tooltip + """ + if authenticated: + authenticated_string = _('and authenticated') + img_path = gtkgui_helpers.get_icon_path('security-high') + else: + authenticated_string = _('and NOT authenticated') + img_path = gtkgui_helpers.get_icon_path('security-low') + self.lock_image.set_from_file(img_path) + + tooltip = _('%(type)s encryption is active %(authenticated)s.') % { + 'type': enc_type, 'authenticated': authenticated_string} + + self.authentication_button.set_tooltip_text(tooltip) + self.widget_set_visible(self.authentication_button, not visible) + self.lock_image.set_sensitive(visible) + + def _on_authentication_button_clicked(self, widget): + gajim.plugin_manager.gui_extension_point( + 'encryption_dialog' + self.encryption, self) + def _change_style(self, model, path, iter_, option): model[iter_][Column.NICK] = model[iter_][Column.NICK] @@ -1967,6 +2020,13 @@ class GroupchatControl(ChatControlBase): if not message: return + if self.encryption: + self.sendmessage = True + gajim.plugin_manager.gui_extension_point( + 'send_message' + self.encryption, self) + if not self.sendmessage: + return + if process_commands and self.process_as_command(message): return diff --git a/src/gui_menu_builder.py b/src/gui_menu_builder.py index 835b78578..58f892533 100644 --- a/src/gui_menu_builder.py +++ b/src/gui_menu_builder.py @@ -245,9 +245,6 @@ control=None, gc_contact=None, is_anonymous=True): 'remove_from_roster_menuitem') manage_contact_menuitem = xml.get_object('manage_contact') convert_to_gc_menuitem = xml.get_object('convert_to_groupchat_menuitem') - encryption_separator = xml.get_object('encryption_separator') - toggle_gpg_menuitem = xml.get_object('toggle_gpg_menuitem') - toggle_e2e_menuitem = xml.get_object('toggle_e2e_menuitem') last_separator = xml.get_object('last_separator') items_to_hide = [] @@ -322,36 +319,6 @@ control=None, gc_contact=None, is_anonymous=True): if not show_start_chat: items_to_hide.append(start_chat_menuitem) - if not show_encryption or not control: - items_to_hide += [encryption_separator, toggle_gpg_menuitem, - toggle_e2e_menuitem] - else: - e2e_is_active = control.session is not None and \ - control.session.enable_encryption - - # check if we support and use gpg - if not gajim.config.get_per('accounts', account, 'keyid') or \ - not gajim.connections[account].USE_GPG or gajim.jid_is_transport( - contact.jid): - toggle_gpg_menuitem.set_sensitive(False) - else: - toggle_gpg_menuitem.set_sensitive(control.gpg_is_active or \ - not e2e_is_active) - toggle_gpg_menuitem.set_active(control.gpg_is_active) - toggle_gpg_menuitem.connect('activate', - control._on_toggle_gpg_menuitem_activate) - - # disable esessions if we or the other client don't support them - if not gajim.HAVE_PYCRYPTO or not contact.supports(NS_ESESSION) or \ - not gajim.config.get_per('accounts', account, 'enable_esessions'): - toggle_e2e_menuitem.set_sensitive(False) - else: - toggle_e2e_menuitem.set_active(e2e_is_active) - toggle_e2e_menuitem.set_sensitive(e2e_is_active or \ - not control.gpg_is_active) - toggle_e2e_menuitem.connect('activate', - control._on_toggle_e2e_menuitem_activate) - if not show_buttonbar_items: items_to_hide += [history_menuitem, send_file_menuitem, information_menuitem, convert_to_gc_menuitem, last_separator] @@ -781,3 +748,23 @@ def build_bookmark_menu(account): label = menu.get_item_attribute_value(1, 'label').get_string() menu.remove(1) menu.insert_submenu(1, label, bookmark_menu) + + +def get_encryption_menu(contact, type_id): + menu = Gio.Menu() + menu.append( + 'Disabled', 'win.{}-encryptiongroup::{}'.format(contact.jid, + 'disabled')) + for name, plugin in gajim.plugin_manager.encryption_plugins.items(): + if type_id == 'gc': + if not hasattr(plugin, 'allow_groupchat'): + continue + if type_id == 'pm': + if not hasattr(plugin, 'allow_privatchat'): + continue + menu_action = 'win.{}-encryptiongroup::{}'.format( + contact.jid, name) + menu.append(name, menu_action) + if menu.get_n_items() == 1: + return None + return menu diff --git a/src/plugins/gajimplugin.py b/src/plugins/gajimplugin.py index 2a78e4977..18a8a75b0 100644 --- a/src/plugins/gajimplugin.py +++ b/src/plugins/gajimplugin.py @@ -58,6 +58,16 @@ class GajimPlugin(object): :todo: decide whether we really need this one, because class name (with module name) can act as such short name + ''' + encryption_name = '' + ''' + Name of the encryption scheme. + + The name that Gajim displays in the encryption menu. + Leave empty if the plugin is not an encryption plugin. + + :type: str + ''' version = '' ''' diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index eeeafcb04..dc7e867eb 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -101,6 +101,12 @@ class PluginManager(metaclass=Singleton): ''' Registered handlers of GUI extension points. ''' + + self.encryption_plugins = {} + ''' + Registered names with instances of encryption Plugins. + ''' + for path in [gajim.PLUGINS_DIRS[1], gajim.PLUGINS_DIRS[0]]: pc = PluginManager.scan_dir_for_plugins(path) self.add_plugins(pc) @@ -291,6 +297,10 @@ class PluginManager(metaclass=Singleton): elif issubclass(event_class, nec.NetworkOutgoingEvent): gajim.nec.unregister_outgoing_event(event_class) + def _remove_name_from_encryption_plugins(self, plugin): + if plugin.encryption_name: + del self.encryption_plugins[plugin.encryption_name] + @log_calls('PluginManager') def activate_plugin(self, plugin): ''' @@ -300,6 +310,7 @@ class PluginManager(metaclass=Singleton): if not plugin.active and plugin.activatable: self._add_gui_extension_points_handlers_from_plugin(plugin) + self._add_encryption_name_from_plugin(plugin) self._handle_all_gui_extension_points_with_plugin(plugin) self._register_events_handlers_in_ged(plugin) self._register_network_events_in_nec(plugin) @@ -339,6 +350,7 @@ class PluginManager(metaclass=Singleton): self._remove_events_handler_from_ged(plugin) self._remove_network_events_from_nec(plugin) + self._remove_name_from_encryption_plugins(plugin) # removing plug-in from active plug-ins list plugin.deactivate() @@ -357,6 +369,10 @@ class PluginManager(metaclass=Singleton): self.gui_extension_points_handlers.setdefault(gui_extpoint_name, []).append(gui_extpoint_handlers) + def _add_encryption_name_from_plugin(self, plugin): + if plugin.encryption_name: + self.encryption_plugins[plugin.encryption_name] = plugin + @log_calls('PluginManager') def _handle_all_gui_extension_points_with_plugin(self, plugin): for gui_extpoint_name, gui_extpoint_handlers in \ diff --git a/src/roster_window.py b/src/roster_window.py index 81766ef94..7d33908e8 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -3220,7 +3220,7 @@ class RosterWindow: ctrl._on_window_motion_notify) ctrl.handlers[id_] = mw.window ctrl.parent_win = mw - ctrl.set_tooltip() + ctrl.on_groupchat_maximize() mw.new_tab(ctrl) mw.set_active_tab(ctrl) mw.window.get_window().focus(Gtk.get_current_event_time())