Public Access
Merge origin/main into claude/spelunk-tunnel
Port forwarding took host schema version 8 first, so the Spelunk route moves to 9 and is written after the forwards. A host may carry both.
This commit is contained in:
commit
689ba6c1bf
44 files changed
+3187
-75
No files matched your search
@@ -1241,6 +1241,18 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
|
||||
/// </remarks>
|
||||
internal event EventHandler? TerminalSessionOpened;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when any tab's forward starts, stops or fails, so a head can re-count what is listening.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The phone's keep-alive notification is the one listener: it says how many forwards are listening
|
||||
/// alongside how many shells are connected. On the UI thread, since every state change is.
|
||||
/// </remarks>
|
||||
internal event EventHandler? ForwardsChanged;
|
||||
|
||||
/// <summary>How many forwards are listening, across every tab.</summary>
|
||||
internal int ListeningForwardCount => Tabs.Sum(tab => tab.ListeningForwardCount);
|
||||
|
||||
/// <summary>
|
||||
/// Raised when something this shell did belongs in the terminal the user is already looking at, so the
|
||||
/// view can put the keyboard back there.
|
||||
@@ -1487,6 +1499,7 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
|
||||
internal bool IsTerminalShowing =>
|
||||
IsTerminalSurface
|
||||
&& !IsConnectSheetOpen
|
||||
&& !IsForwardsSheetOpen
|
||||
&& !IsHostKeyDecisionShowing
|
||||
&& SelectedTab is { HasSession: true };
|
||||
|
||||
@@ -2232,6 +2245,45 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
|
||||
[RelayCommand]
|
||||
private void CloseConnectSheet() => IsConnectSheetOpen = false;
|
||||
|
||||
// ---- The phone's forwards sheet ----
|
||||
|
||||
/// <summary>
|
||||
/// Whether the phone's list of the selected tab's forwards is open over the terminal.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The phone's answer to the desktop sidebar's FORWARDS section, over the same rows —
|
||||
/// <see cref="TerminalTabViewModel.Forwards"/> — with the same state word, reason and switch. A sheet
|
||||
/// rather than something always drawn because a phone gives a shell the whole screen, so it is raised
|
||||
/// from a button in the terminal's bar, which draws only for a tab that has forwards and turns the danger
|
||||
/// colour when one of them failed.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Shell state for the reason <see cref="IsConnectSheetOpen"/> is: it collapses the renderer, which is a
|
||||
/// native view nothing can be drawn over. Lowered with the connect sheet whenever the terminal surface is
|
||||
/// left — see <see cref="OnSurfaceChanged"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[ObservableProperty]
|
||||
private bool isForwardsSheetOpen;
|
||||
|
||||
/// <summary>Raises the forwards sheet over the terminal.</summary>
|
||||
/// <remarks>Gated as <see cref="OpenConnectSheet"/> is, and on there being a tab to list.</remarks>
|
||||
[RelayCommand]
|
||||
private void OpenForwardsSheet()
|
||||
{
|
||||
if (!IsTerminalSurface || SelectedTab is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsForwardsSheetOpen = true;
|
||||
}
|
||||
|
||||
/// <summary>Lowers the forwards sheet, leaving the terminal where it was.</summary>
|
||||
[RelayCommand]
|
||||
private void CloseForwardsSheet() => IsForwardsSheetOpen = false;
|
||||
|
||||
// ---- Open terminals ----
|
||||
|
||||
/// <summary>
|
||||
@@ -3868,11 +3920,13 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
|
||||
IdentityLabel = e.IdentityLabel,
|
||||
};
|
||||
AdoptTab(adopted);
|
||||
StartForwards(adopted, e);
|
||||
RefreshConnectedHosts();
|
||||
return;
|
||||
}
|
||||
|
||||
tab.Opened(e.SessionId);
|
||||
StartForwards(tab, e);
|
||||
|
||||
// From this moment, not from when the tab first appeared — connecting is not open, and the session
|
||||
// shell's elapsed timer is about a shell that is actually running.
|
||||
@@ -3894,6 +3948,54 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
|
||||
TerminalSessionOpened?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts the host's saved forwards on a tab whose session has just opened, and starts every one.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// All at once and none awaited: each row reports its own outcome, and the terminal is already usable
|
||||
/// while a remote forward waits for the server to answer. See <see cref="PortForwardViewModel"/> for why
|
||||
/// one failing stops nothing else.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The session id is captured into the start delegate rather than read off the tab when a row is
|
||||
/// toggled later. A reconnect puts a new id on the same tab, and a row from the old session that reached
|
||||
/// for the tab's current id would start its listener on a connection it was never part of.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// ◆ <b>On the phone too.</b> This used to return early on Android, because that head had nowhere to show
|
||||
/// what was listening. Its terminal bar now carries a FORWARDS button over the same rows the desktop's
|
||||
/// sidebar lists — see <see cref="IsForwardsSheetOpen"/> — so both heads start the same forwards.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private void StartForwards(TerminalTabViewModel tab, TerminalSessionEventArgs e)
|
||||
{
|
||||
var sessionId = e.SessionId;
|
||||
|
||||
tab.ReplaceForwards(e.Forwards.Select(rule => new PortForwardViewModel(
|
||||
rule,
|
||||
(spec, cancellationToken) => workspace.StartForwardAsync(sessionId, spec, cancellationToken),
|
||||
post)));
|
||||
|
||||
foreach (var forward in tab.Forwards)
|
||||
{
|
||||
// Never unsubscribed, and it need not be: the row holds the handler, not the other way round, so a
|
||||
// row replaced by a reconnect is collected with it — after its last change, to Stopped, is heard.
|
||||
forward.PropertyChanged += OnForwardChanged;
|
||||
_ = forward.StartAsync();
|
||||
}
|
||||
|
||||
ForwardsChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void OnForwardChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName is nameof(PortForwardViewModel.State))
|
||||
{
|
||||
ForwardsChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Answers a connection that did not become a session.
|
||||
/// </summary>
|
||||
@@ -4694,6 +4796,17 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
|
||||
vault.EditSelectedHostCommand.Execute(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The sidebar's FORWARDS affordance: opens the active tab's host for editing, where forwards are added.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The same route <see cref="PinFolderFromSidebar"/> takes, because a forward is saved on the host for
|
||||
/// the same reason a pin is. A forward added there starts with the next session to the host, not this
|
||||
/// one — the editor says so — since this session's forwards were started from the host as it was.
|
||||
/// </remarks>
|
||||
[RelayCommand]
|
||||
private void AddForwardFromSidebar() => PinFolderFromSidebar();
|
||||
|
||||
/// <summary>
|
||||
/// The SFTP tab row's click: makes one of the terminal's tabs the SFTP surface's browsed host.
|
||||
/// </summary>
|
||||
@@ -5169,6 +5282,7 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
|
||||
if (value is not ShellSurface.Terminal)
|
||||
{
|
||||
IsConnectSheetOpen = false;
|
||||
IsForwardsSheetOpen = false;
|
||||
}
|
||||
|
||||
RaiseSurfaceState();
|
||||
@@ -5353,6 +5467,9 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
|
||||
/// <inheritdoc cref="OnIsSearchingChanged" />
|
||||
partial void OnIsConnectSheetOpenChanged(bool value) => RaiseTerminalState();
|
||||
|
||||
/// <inheritdoc cref="OnIsSearchingChanged" />
|
||||
partial void OnIsForwardsSheetOpenChanged(bool value) => RaiseTerminalState();
|
||||
|
||||
/// <remarks>
|
||||
/// The unlock card and the confirmation swap, so arming one has to hide the other — see
|
||||
/// <see cref="IsAskingForThePassphrase"/>.
|
||||
|
||||
Reference in new issue
Block a user