diff --git a/src/openvpn3_indicator/dialogs/credentials.py b/src/openvpn3_indicator/dialogs/credentials.py index a68bc0b..2b7efa2 100644 --- a/src/openvpn3_indicator/dialogs/credentials.py +++ b/src/openvpn3_indicator/dialogs/credentials.py @@ -54,6 +54,7 @@ def construct_credentials_dialog(name, user_inputs, allow_store=True, on_connect entries = list() default_store = False can_store = False + focus_entry = None for user_input in user_inputs: label = Gtk.Label(label=user_input.name, hexpand=True, xalign=0, margin_right=10) grid.attach(label, 0, row, 1, 1) @@ -62,9 +63,11 @@ def construct_credentials_dialog(name, user_inputs, allow_store=True, on_connect entry.set_visibility(False) if user_input.can_store: can_store = True - if user_input.value is not None: + if user_input.value: entry.set_text(user_input.value) default_store = True + elif focus_entry is None: + focus_entry = entry entry.set_activates_default(True) grid.attach(entry, 1, row, 1, 1) entries.append(entry) @@ -108,6 +111,8 @@ def on_schedule(): default.set_can_default(True) default.grab_default() dialog.show_all() + if focus_entry is not None: + focus_entry.grab_focus() if remain_active is not None: GLib.timeout_add(1000, on_schedule) return dialog diff --git a/src/openvpn3_indicator/tests/credentials.py b/src/openvpn3_indicator/tests/credentials.py index 67f41e2..c6720a2 100755 --- a/src/openvpn3_indicator/tests/credentials.py +++ b/src/openvpn3_indicator/tests/credentials.py @@ -11,6 +11,58 @@ from openvpn3_indicator.about import APPLICATION_ID from openvpn3_indicator.dialogs.credentials import construct_credentials_dialog, CredentialsUserInput +# name, mask, value, can_store, and the field we expect to end up focused. +# '' means there's a saved value that's just empty. None means nothing was ever saved. +# OTP is never saved, so it's always None. +FOCUS_SCENARIOS = [ + ('nothing stored', [ + CredentialsUserInput('Username', False, None, True), + CredentialsUserInput('Password', True, None, True), + CredentialsUserInput('OTP', True, None, False), + ], 'Username'), + ('username stored only', [ + CredentialsUserInput('Username', False, 'user', True), + CredentialsUserInput('Password', True, '', True), + CredentialsUserInput('OTP', True, None, False), + ], 'Password'), + ('password stored only', [ + CredentialsUserInput('Username', False, '', True), + CredentialsUserInput('Password', True, 'secret', True), + CredentialsUserInput('OTP', True, None, False), + ], 'Username'), + ('username and password stored', [ + CredentialsUserInput('Username', False, 'user', True), + CredentialsUserInput('Password', True, 'secret', True), + CredentialsUserInput('OTP', True, None, False), + ], 'OTP'), +] + + +def focused_field_label(dialog): + focused = dialog.get_focus() + grid = next((child for child in dialog.get_content_area().get_children() + if isinstance(child, Gtk.Grid)), None) + if not isinstance(focused, Gtk.Entry) or grid is None: + return None + row = grid.child_get_property(focused, 'top-attach') + for child in grid.get_children(): + if (isinstance(child, Gtk.Label) + and grid.child_get_property(child, 'left-attach') == 0 + and grid.child_get_property(child, 'top-attach') == row): + return child.get_text() + return None + + +def verify_focus(): + for name, user_inputs, expected in FOCUS_SCENARIOS: + dialog = construct_credentials_dialog(name, user_inputs) + while Gtk.events_pending(): # realize widgets so the focus is applied + Gtk.main_iteration_do(False) + got = focused_field_label(dialog) + print(f"[{'PASS' if got == expected else 'FAIL'}] {name}: focus on {got!r} (expected {expected!r})") + dialog.destroy() + + class Test(Gtk.Application): def __init__(self): Gtk.Application.__init__(self, @@ -24,12 +76,14 @@ def on_activate(self, *args, **kwargs): def on_startup(self, *args, **kwargs): self.hold() + verify_focus() + user_inputs = [ CredentialsUserInput(name='Username', mask=False, value='user', can_store=True), CredentialsUserInput(name='Password', mask=True, value=None, can_store=True), CredentialsUserInput(name='Other', mask=True, value='rehto', can_store=True), ] - + dialog = construct_credentials_dialog('Test', user_inputs, on_connect=self.action_connect, on_cancel=self.action_quit) dialog.set_visible(True)