Public Access
Quick connect: type user@host into Ctrl+K to open a terminal on a machine without saving it
The desktop had no way to reach a machine that was not in the keychain; the phone has had a manual box on its terminal screen all along. The palette's query now reads as an address when it has an @ in it: a "not saved" row offers to dial it, Enter asks for the password in place, and a second Enter connects. Escape on the password step goes back to the address. It goes through the phone's own path, now split out as VaultViewModel.ConnectToEndpointAsync, so the tab, the host-key question and the connection-log entry are the same on both heads and nothing is written to the keychain. Password only, as on the phone: a key is the reason to save the host. The typed row takes the highlight unless a saved host dials exactly that address, so typing a new address cannot land on a saved machine whose address merely starts the same way. Covered by eleven new QuickConnectTests; manual check 7.10a covers the real dial.
This commit is contained in:
1 parent
902761d37b
commit
988248fa57
8 files changed
+840
-99
No files matched your search
@@ -39,11 +39,25 @@ internal sealed partial class QuickConnect : UserControl
|
||||
// drag across the list — and the ListBox has moved its own selection by the time a tap completes,
|
||||
// which is what makes the handler below a matter of reading the selection rather than hit-testing.
|
||||
Results.Tapped += OnResultTapped;
|
||||
AdHocRow.Tapped += OnAdHocRowTapped;
|
||||
|
||||
// The two steps swap in place, and whichever one has just appeared takes the caret: typing an address
|
||||
// and pressing Enter should leave somebody typing a password, not looking for the box to click into.
|
||||
// Watched on the steps themselves rather than on the view model, for the reason TakeKeyboard gives —
|
||||
// the view model's change arrives ahead of the binding that reveals the step.
|
||||
QueryStep.PropertyChanged += OnStepChanged;
|
||||
PasswordStep.PropertyChanged += OnStepChanged;
|
||||
}
|
||||
|
||||
/// <summary>The box, so the caret can be put in it the moment the palette opens.</summary>
|
||||
internal TextBox QueryBox => Query;
|
||||
|
||||
/// <summary>The typed address's password box, which takes the caret when the palette asks for it.</summary>
|
||||
internal TextBox PasswordBox => Password;
|
||||
|
||||
/// <summary>The row offering to dial what was typed, when it reads as an address.</summary>
|
||||
internal Control AdHocTargetRow => AdHocRow;
|
||||
|
||||
private MainWindowViewModel? Shell => DataContext as MainWindowViewModel;
|
||||
|
||||
/// <summary>
|
||||
@@ -63,6 +77,29 @@ internal sealed partial class QuickConnect : UserControl
|
||||
return;
|
||||
}
|
||||
|
||||
// The password step answers only Enter and Escape, and Escape there goes back to the address: a
|
||||
// mistyped address is the likeliest reason to want out of it, and closing would throw that away too.
|
||||
if (shell.IsAskingForAdHocPassword)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Escape:
|
||||
shell.BackToSearchQueryCommand.Execute(null);
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
case Key.Enter:
|
||||
_ = shell.ConnectToAdHocTargetCommand.ExecuteAsync(null);
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Escape:
|
||||
@@ -76,12 +113,12 @@ internal sealed partial class QuickConnect : UserControl
|
||||
break;
|
||||
|
||||
case Key.Down:
|
||||
Move(shell, 1);
|
||||
shell.MoveSearchSelection(1);
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
case Key.Up:
|
||||
Move(shell, -1);
|
||||
shell.MoveSearchSelection(-1);
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
@@ -129,23 +166,38 @@ internal sealed partial class QuickConnect : UserControl
|
||||
NativeKeyboardFocus.ReturnTo(window);
|
||||
}
|
||||
|
||||
Query.Focus();
|
||||
if (PasswordStep.IsVisible)
|
||||
{
|
||||
Password.Focus();
|
||||
}
|
||||
else
|
||||
{
|
||||
Query.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
/// <remarks>Clamped rather than wrapped: a list that jumps from the last row to the first loses people.</remarks>
|
||||
private static void Move(MainWindowViewModel shell, int delta)
|
||||
/// <summary>Hands the caret to whichever step has just been revealed.</summary>
|
||||
/// <remarks>Posted, for the same reason the palette's own opening is: see <see cref="TakeKeyboard"/>.</remarks>
|
||||
private void OnStepChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (shell.SearchResults.Count == 0)
|
||||
if (e.Property == IsVisibleProperty && e.GetNewValue<bool>() && IsVisible)
|
||||
{
|
||||
Dispatcher.UIThread.Post(TakeKeyboard, DispatcherPriority.Loaded);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes the typed address, for a click on its row: the pointer's version of Enter on it.
|
||||
/// </summary>
|
||||
private void OnAdHocRowTapped(object? sender, TappedEventArgs e)
|
||||
{
|
||||
if (Shell is not { IsSearching: true, HasAdHocTarget: true } shell)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var current = shell.SelectedSearchResult is { } selected
|
||||
? shell.SearchResults.IndexOf(selected)
|
||||
: -1;
|
||||
|
||||
shell.SelectedSearchResult =
|
||||
shell.SearchResults[Math.Clamp(current + delta, 0, shell.SearchResults.Count - 1)];
|
||||
e.Handled = true;
|
||||
shell.ChooseAdHocTargetCommand.Execute(null);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
Reference in new issue
Block a user