Philipp Hörist pushed to branch master at gajim / gajim
Commits: faa1a2f0 by Philipp Hörist at 2019-08-01T10:34:55Z Groupchat: Add progress page when joining
- - - - - 8e02e326 by Philipp Hörist at 2019-08-01T10:34:55Z Groupchat: Refactor closing GroupchatControl
- - - - - 4d2c0e11 by Philipp Hörist at 2019-08-01T10:34:55Z MUC: Improve captcha error handling
- Add page to handle captcha errors
- - - - -
6 changed files:
- gajim/common/const.py - gajim/common/modules/muc.py - gajim/common/structs.py - gajim/data/gui/groupchat_control.ui - gajim/data/style/gajim.css - gajim/groupchat_control.py
Changes:
===================================== gajim/common/const.py ===================================== @@ -205,6 +205,8 @@ class MUCJoinedState(Enum): JOINED = 'joined' NOT_JOINED = 'not joined' JOINING = 'joining' + CAPTCHA_REQUEST = 'captcha in progress' + CAPTCHA_FAILED = 'captcha failed'
def __str__(self): return self.name
===================================== gajim/common/modules/muc.py ===================================== @@ -59,6 +59,7 @@ class MUC(BaseModule): 'set_subject', 'cancel_config', 'send_captcha', + 'cancel_captcha', 'decline', 'invite', 'request_config', @@ -326,6 +327,18 @@ class MUC(BaseModule): self._raise_muc_event('muc-join-failed', properties) self._set_muc_state(room_jid, MUCJoinedState.NOT_JOINED)
+ elif muc_data.state == MUCJoinedState.CAPTCHA_REQUEST: + app.nec.push_incoming_event( + NetworkEvent('muc-captcha-error', + account=self._account, + room_jid=room_jid, + error_text=properties.error.message)) + self._set_muc_state(room_jid, MUCJoinedState.CAPTCHA_FAILED) + self._set_muc_state(room_jid, MUCJoinedState.NOT_JOINED) + + elif muc_data.state == MUCJoinedState.CAPTCHA_FAILED: + self._set_muc_state(room_jid, MUCJoinedState.NOT_JOINED) + else: self._raise_muc_event('muc-presence-error', properties)
@@ -586,6 +599,15 @@ class MUC(BaseModule): if not properties.is_captcha_challenge: return
+ muc_data = self._muc_data.get(properties.jid) + if muc_data is None: + return + + if muc_data.state != MUCJoinedState.JOINING: + self._log.warning('Received captcha request but state != %s', + MUCJoinedState.JOINING) + return + contact = app.contacts.get_groupchat_contact(self._account, str(properties.jid)) if contact is None: @@ -593,6 +615,9 @@ class MUC(BaseModule):
self._log.info('Captcha challenge received from %s', properties.jid) store_bob_data(properties.captcha.bob_data) + muc_data.captcha_id = properties.id + + self._set_muc_state(properties.jid, MUCJoinedState.CAPTCHA_REQUEST)
app.nec.push_incoming_event( NetworkEvent('muc-captcha-challenge', @@ -601,6 +626,38 @@ class MUC(BaseModule): form=properties.captcha.form)) raise nbxmpp.NodeProcessed
+ def cancel_captcha(self, room_jid): + muc_data = self._muc_data.get(room_jid) + if muc_data is None: + return + + if muc_data.captcha_id is None: + self._log.warning('No captcha message id available') + return + self._nbxmpp('MUC').cancel_captcha(room_jid, muc_data.captcha_id) + self._set_muc_state(room_jid, MUCJoinedState.CAPTCHA_FAILED) + self._set_muc_state(room_jid, MUCJoinedState.NOT_JOINED) + + def send_captcha(self, room_jid, form_node): + self._set_muc_state(room_jid, MUCJoinedState.JOINING) + self._nbxmpp('MUC').send_captcha(room_jid, + form_node, + callback=self._on_captcha_result) + + def _on_captcha_result(self, result): + if not is_error_result(result): + return + + muc_data = self._muc_data.get(result.jid) + if muc_data is None: + return + self._set_muc_state(result.jid, MUCJoinedState.CAPTCHA_FAILED) + app.nec.push_incoming_event( + NetworkEvent('muc-captcha-error', + account=self._account, + room_jid=str(result.jid), + error_text=result.message)) + def _on_config_change(self, _con, _stanza, properties): if not properties.is_muc_config_change: return
===================================== gajim/common/structs.py ===================================== @@ -32,6 +32,8 @@ class MUCData: self.nick = nick self.password = password self.state = MUCJoinedState.NOT_JOINED + # Message id of the captcha challenge + self.captcha_id = None
@property def jid(self):
===================================== gajim/data/gui/groupchat_control.ui ===================================== @@ -458,12 +458,56 @@ <property name="can_focus">False</property> <property name="orientation">vertical</property> <child> - <object class="GtkSpinner" id="spinner"> + <object class="GtkGrid"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="halign">center</property> <property name="valign">center</property> <property name="vexpand">True</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkSpinner" id="progress_spinner"> + <property name="width_request">40</property> + <property name="height_request">40</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Joining…</property> + <style> + <class name="bold16"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="abort_button"> + <property name="label" translatable="yes">_Abort</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="_on_abort_button_clicked" swapped="no"/> + <style> + <class name="margin-top12"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> </object> <packing> <property name="expand">False</property> @@ -981,6 +1025,112 @@ <property name="position">6</property> </packing> </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="row_spacing">12</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="homogeneous">True</property> + <child> + <object class="GtkButton" id="captcha_close_button"> + <property name="label" translatable="yes">_Close</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">True</property> + <property name="halign">center</property> + <property name="valign">start</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="_on_page_close_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="captcha_try_again_button"> + <property name="label" translatable="yes">_Try again</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="_on_captcha_try_again_clicked" swapped="no"/> + <style> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">dialog-error-symbolic</property> + <property name="icon_size">6</property> + <style> + <class name="error-color"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="captcha_error_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="justify">center</property> + <property name="wrap">True</property> + <property name="max_width_chars">40</property> + <style> + <class name="bold16"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="name">captcha-error</property> + <property name="position">7</property> + </packing> + </child> </object> <packing> <property name="expand">True</property>
===================================== gajim/data/style/gajim.css ===================================== @@ -224,6 +224,7 @@ list.settings > row > box {
/* Padding/Margins */ .margin-top6 { margin-top: 6px; } +.margin-top12 { margin-top: 12px; } .margin-12 { margin: 12px; } .margin-18 { margin: 18px; }
===================================== gajim/groupchat_control.py ===================================== @@ -311,7 +311,7 @@ class GroupchatControl(ChatControlBase): settings_menu.set_menu_model(self.control_menu)
self._event_handlers = [ - ('muc-joined', ged.GUI1, self._on_joined), + ('muc-joined', ged.GUI1, self._on_muc_joined), ('muc-join-failed', ged.GUI1, self._on_muc_join_failed), ('muc-user-joined', ged.GUI1, self._on_user_joined), ('muc-user-left', ged.GUI1, self._on_user_left), @@ -327,6 +327,7 @@ class GroupchatControl(ChatControlBase): ('muc-config-changed', ged.GUI1, self._on_config_changed), ('muc-subject', ged.GUI1, self._on_subject), ('muc-captcha-challenge', ged.GUI1, self._on_captcha_challenge), + ('muc-captcha-error', ged.GUI1, self._on_captcha_error), ('muc-voice-approval', ged.GUI1, self._on_voice_approval), ('muc-disco-update', ged.GUI1, self._on_disco_update), ('muc-configuration-finished', ged.GUI1, self._on_configuration_finished), @@ -348,12 +349,12 @@ class GroupchatControl(ChatControlBase):
# Stack self.xml.stack.show_all() - self.xml.stack.set_visible_child_name('groupchat') + self.xml.stack.set_visible_child_name('progress') + self.xml.progress_spinner.start()
self.update_ui() self.widget.show_all()
- if app.config.get('hide_groupchat_occupants_list'): # Roster is shown by default, so toggle the roster button to hide it self.show_roster() @@ -631,9 +632,7 @@ class GroupchatControl(ChatControlBase):
def _on_disconnect(self, action, param): app.connections[self.account].get_module('MUC').leave(self.room_jid) - self.force_non_minimizable = True - self.parent_win.remove_tab(self, self.parent_win.CLOSE_COMMAND) - self.force_non_minimizable = False + self._close_control()
def _on_destroy_room(self, action, param): def _on_confirm(reason, jid): @@ -1898,8 +1897,9 @@ class GroupchatControl(ChatControlBase): self.draw_all_roles()
@event_filter(['account', 'room_jid']) - def _on_joined(self, _event): + def _on_muc_joined(self, _event): self.got_connected() + self._show_page('groupchat')
@event_filter(['account', 'room_jid']) def _on_user_joined(self, event): @@ -1994,9 +1994,7 @@ class GroupchatControl(ChatControlBase): con.get_module('Bookmarks').remove(self.room_jid)
if self._wait_for_destruction: - self.force_non_minimizable = True - self.parent_win.remove_tab(self, self.parent_win.CLOSE_COMMAND) - self.force_non_minimizable = False + self._close_control()
def get_role_iter(self, role: str) -> Optional[Gtk.TreeIter]: try: @@ -2228,6 +2226,9 @@ class GroupchatControl(ChatControlBase):
on_yes(self)
+ def _close_control(self): + self.parent_win.remove_tab(self, None, force=True) + def set_control_active(self, state): self.conv_textview.allow_focus_out_line = True self.attention_flag = False @@ -2418,7 +2419,7 @@ class GroupchatControl(ChatControlBase): self._on_password_cancel_clicked() elif self._get_current_page() == 'captcha': self._on_captcha_cancel_clicked() - elif self._get_current_page() == 'error': + elif self._get_current_page() in ('error', 'captcha-error'): self._on_page_close_clicked() else: self._show_page('groupchat') @@ -2874,12 +2875,18 @@ class GroupchatControl(ChatControlBase):
def _on_page_change(self, stack, _param): page_name = stack.get_visible_child_name() - if page_name == 'password': + if page_name == 'groupchat': + self.xml.progress_spinner.stop() + elif page_name == 'progress': + self.xml.progress_spinner.start() + elif page_name == 'password': self.xml.password_entry.set_text('') self.xml.password_entry.grab_focus() self.xml.password_set_button.grab_default() elif page_name == 'captcha': self.xml.captcha_set_button.grab_default() + elif page_name == 'captcha-error': + self.xml.captcha_try_again_button.grab_default() elif page_name == 'error': self.xml.close_button.grab_default()
@@ -2929,15 +2936,13 @@ class GroupchatControl(ChatControlBase): password = self.xml.password_entry.get_text() self._muc_data.password = password app.connections[self.account].get_module('MUC').join(self._muc_data) - self._show_page('groupchat') + self._show_page('progress')
def _on_password_changed(self, entry, _param): self.xml.password_set_button.set_sensitive(bool(entry.get_text()))
def _on_password_cancel_clicked(self, _button=None): - self.force_non_minimizable = True - self.parent_win.remove_tab(self, self.parent_win.CLOSE_COMMAND) - self.force_non_minimizable = False + self._close_control()
@event_filter(['account', 'room_jid']) def _on_captcha_challenge(self, event): @@ -2959,6 +2964,11 @@ class GroupchatControl(ChatControlBase): self._show_page('captcha') self._captcha_request.focus_first_entry()
+ @event_filter(['account', 'room_jid']) + def _on_captcha_error(self, event): + self.xml.captcha_error_label.set_text(event.error_text) + self._show_page('captcha-error') + def _remove_captcha_request(self): if self._captcha_request is None: return @@ -2975,21 +2985,27 @@ class GroupchatControl(ChatControlBase): con = app.connections[self.account] con.get_module('MUC').send_captcha(self.room_jid, form_node) self._remove_captcha_request() - self._show_page('groupchat') + self._show_page('progress')
def _on_captcha_cancel_clicked(self, _button=None): + con = app.connections[self.account] + con.get_module('MUC').cancel_captcha(self.room_jid) self._remove_captcha_request() - self.force_non_minimizable = True - self.parent_win.remove_tab(self, self.parent_win.CLOSE_COMMAND) - self.force_non_minimizable = False + self._close_control() + + def _on_captcha_try_again_clicked(self, _button=None): + app.connections[self.account].get_module('MUC').join(self._muc_data) + self._show_page('progress')
def _on_page_cancel_clicked(self, _button): self._show_page('groupchat')
def _on_page_close_clicked(self, _button=None): - self.force_non_minimizable = True - self.parent_win.remove_tab(self, self.parent_win.CLOSE_COMMAND) - self.force_non_minimizable = False + self._close_control() + + def _on_abort_button_clicked(self, _button): + self.parent_win.window.lookup_action( + 'disconnect-%s' % self.control_id).activate()
class SubjectPopover(Gtk.Popover):
View it on GitLab: https://dev.gajim.org/gajim/gajim/compare/6c2271ce56bf7ea34a6dd8115125514f2c...