Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Devolutions.Terminal.Control/PACKAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ Content = terminal;
await terminal.StartAsync(new ProfileSettings(), columns: 120, rows: 30);
```

The `columns` and `rows` passed to `StartAsync` set both the engine and PTY
initially, even if the control was arranged at a different size before startup.
Subsequent layout passes resize both to fit the available space. You can use
`TermControl.MeasureCell(profile, displayScale)` to calculate an initial grid
for the profile and display; pending layout changes made while the connection
starts are applied once it is ready.

See the [`samples/Devolutions.Terminal.Control.Sample`](https://github.com/Devolutions/devolutions-terminal/tree/main/samples/Devolutions.Terminal.Control.Sample)
project in the [Devolutions Terminal repository](https://github.com/Devolutions/devolutions-terminal)
for a full working app, plus the full source and documentation.
80 changes: 68 additions & 12 deletions src/Devolutions.Terminal.Control/TermControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ public sealed class TermControl : Avalonia.Controls.Control
private int _pendingPtyRows;
private int _pendingPtyPixelWidth;
private int _pendingPtyPixelHeight;
private bool _hasPendingPtyResize;
private bool _connectionStarting;

// Throughput-harness diagnostics (Devolutions.Terminal.Bench): posts requested by
// the engine-invalidated handler vs UI drains actually executed.
Expand Down Expand Up @@ -275,6 +277,7 @@ public static CellSize MeasureCell(ProfileSettings profile, double scale = 1)

public async Task StartAsync(ProfileSettings profile, int columns, int rows)
{
ClearPendingPtyResize();
Profile = profile;
_padding = ParsePadding(profile.Padding);
_shaderEffectsEnabled = true;
Expand All @@ -291,7 +294,22 @@ public async Task StartAsync(ProfileSettings profile, int columns, int rows)
profile.AllowKittyKeyboardMode);
ResizeEngine(columns, rows);

await StartConnectionAsync(profile, columns, rows).ConfigureAwait(true);
_connectionStarting = true;
try
{
await StartConnectionAsync(profile, columns, rows).ConfigureAwait(true);
}
catch
{
ClearPendingPtyResize();
throw;
}
finally
{
_connectionStarting = false;
}

FlushPtyResize();
_blinkTimer.Start();
InvalidateVisual();
}
Expand Down Expand Up @@ -378,15 +396,40 @@ public async Task RestartAsync(CancellationToken cancellationToken = default)
{
var connection = _connection
?? throw new InvalidOperationException("The terminal connection has not been started.");
await connection.CloseAsync(cancellationToken).ConfigureAwait(true);
ResetTerminal();
await connection.RestartAsync(cancellationToken: cancellationToken).ConfigureAwait(true);
_connectionStarting = true;
try
{
await connection.CloseAsync(cancellationToken).ConfigureAwait(true);
ResetTerminal();
await connection.RestartAsync(cancellationToken: cancellationToken).ConfigureAwait(true);
}
catch
{
ClearPendingPtyResize();
throw;
}
finally
{
_connectionStarting = false;
}

if (!_hasPendingPtyResize)
{
SchedulePtyResize(
Engine.Columns,
Engine.Rows,
checked((int)Math.Max(1, Engine.Columns * _engineCellWidthPixels)),
checked((int)Math.Max(1, Engine.Rows * _engineCellHeightPixels)));
}

FlushPtyResize();
_blinkTimer.Start();
}

public async Task CloseAsync()
{
_blinkTimer.Stop();
ClearPendingPtyResize();
if (_connection is not null)
{
var connection = _connection;
Expand Down Expand Up @@ -1007,28 +1050,41 @@ private void SchedulePtyResize(int columns, int rows, int pixelWidth, int pixelH
_pendingPtyRows = rows;
_pendingPtyPixelWidth = pixelWidth;
_pendingPtyPixelHeight = pixelHeight;
_hasPendingPtyResize = true;
_ptyResizeTimer.Stop();
_ptyResizeTimer.Start();
}

private void ClearPendingPtyResize()
{
_ptyResizeTimer.Stop();
_hasPendingPtyResize = false;
}

private void FlushPtyResize()
{
_ptyResizeTimer.Stop();
if (!_hasPendingPtyResize || _connectionStarting || _connection?.IsRunning != true)
{
return;
}

try
{
if (_connection?.IsRunning == true)
{
_connection.Resize(
_pendingPtyColumns,
_pendingPtyRows,
_pendingPtyPixelWidth,
_pendingPtyPixelHeight);
}
_connection.Resize(
_pendingPtyColumns,
_pendingPtyRows,
_pendingPtyPixelWidth,
_pendingPtyPixelHeight);
}
catch (Exception ex) when (ex is IOException or InvalidOperationException or OverflowException)
{
ReportInteractionError("Terminal resize was not accepted; resize again after input drains", ex);
}
finally
{
_hasPendingPtyResize = false;
}
}

private void ResizeEngine(int columns, int rows)
Expand Down
Loading
Loading