Skip to content

Commit 7eb23c7

Browse files
committed
Refactor the way terminal handles are managed so the native library fully abstracts them.
1 parent a7e8535 commit 7eb23c7

File tree

11 files changed

+237
-182
lines changed

11 files changed

+237
-182
lines changed

src/core/Native/TerminalInterop.cs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ namespace Vezel.Cathode.Native;
22

33
internal static unsafe partial class TerminalInterop
44
{
5+
[StructLayout(LayoutKind.Sequential)]
6+
public struct TerminalDescriptor
7+
{
8+
}
9+
510
public enum TerminalException
611
{
712
None,
@@ -98,19 +103,24 @@ bool TryLoad(out nint handle, params string[] paths)
98103
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
99104
public static partial void Initialize();
100105

101-
[LibraryImport(Library, EntryPoint = "cathode_get_handles")]
106+
[LibraryImport(Library, EntryPoint = "cathode_get_descriptors")]
102107
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
103-
public static partial void GetHandles(nuint* stdIn, nuint* stdOut, nuint* stdErr, nuint* ttyIn, nuint* ttyOut);
108+
public static partial void GetDescriptors(
109+
TerminalDescriptor** stdIn,
110+
TerminalDescriptor** stdOut,
111+
TerminalDescriptor** stdErr,
112+
TerminalDescriptor** ttyIn,
113+
TerminalDescriptor** ttyOut);
104114

105115
[LibraryImport(Library, EntryPoint = "cathode_is_valid")]
106116
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
107117
[return: MarshalAs(UnmanagedType.U1)]
108-
public static partial bool IsValid(nuint handle, [MarshalAs(UnmanagedType.U1)] bool write);
118+
public static partial bool IsValid(TerminalDescriptor* descriptor, [MarshalAs(UnmanagedType.U1)] bool write);
109119

110120
[LibraryImport(Library, EntryPoint = "cathode_is_interactive")]
111121
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
112122
[return: MarshalAs(UnmanagedType.U1)]
113-
public static partial bool IsInteractive(nuint handle);
123+
public static partial bool IsInteractive(TerminalDescriptor* descriptor);
114124

115125
[LibraryImport(Library, EntryPoint = "cathode_query_size")]
116126
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
@@ -133,13 +143,13 @@ public static partial TerminalResult SetMode(
133143

134144
[LibraryImport(Library, EntryPoint = "cathode_read")]
135145
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
136-
public static partial TerminalResult Read(nuint handle, byte* buffer, int length, int* progress);
146+
public static partial TerminalResult Read(TerminalDescriptor* descriptor, byte* buffer, int length, int* progress);
137147

138148
[LibraryImport(Library, EntryPoint = "cathode_write")]
139149
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
140-
public static partial TerminalResult Write(nuint handle, byte* buffer, int length, int* progress);
150+
public static partial TerminalResult Write(TerminalDescriptor* descriptor, byte* buffer, int length, int* progress);
141151

142152
[LibraryImport(Library, EntryPoint = "cathode_poll")]
143153
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
144-
public static partial void Poll([MarshalAs(UnmanagedType.U1)] bool write, nuint* handles, bool* results, int count);
154+
public static partial void Poll([MarshalAs(UnmanagedType.U1)] bool write, int* fds, bool* results, int count);
145155
}

src/core/Terminals/NativeTerminalReader.cs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,47 @@
22

33
namespace Vezel.Cathode.Terminals;
44

5-
internal sealed class NativeTerminalReader : TerminalReader
5+
internal sealed unsafe class NativeTerminalReader : TerminalReader
66
{
77
// Note that the buffer size used affects how many characters the Windows console host will allow the user to type
88
// in a single line (in cooked mode).
99
private const int ReadBufferSize = 4096;
1010

1111
public NativeVirtualTerminal Terminal { get; }
1212

13-
public nuint Handle { get; }
13+
public TerminalInterop.TerminalDescriptor* Descriptor { get; }
1414

15-
public override sealed Stream Stream { get; }
15+
public override Stream Stream { get; }
1616

17-
public override sealed TextReader TextReader { get; }
17+
public override TextReader TextReader { get; }
1818

19-
public override sealed bool IsValid { get; }
19+
public override bool IsValid { get; }
2020

21-
public override sealed bool IsInteractive { get; }
21+
public override bool IsInteractive { get; }
2222

2323
private readonly SemaphoreSlim _semaphore;
2424

2525
private readonly Action<nuint, CancellationToken>? _cancellationHook;
2626

2727
public NativeTerminalReader(
2828
NativeVirtualTerminal terminal,
29-
nuint handle,
29+
TerminalInterop.TerminalDescriptor* descriptor,
3030
SemaphoreSlim semaphore,
3131
Action<nuint, CancellationToken>? cancellationHook)
3232
{
3333
Terminal = terminal;
34-
Handle = handle;
34+
Descriptor = descriptor;
3535
_semaphore = semaphore;
3636
_cancellationHook = cancellationHook;
3737
Stream = new SynchronizedStream(new TerminalInputStream(this));
3838
TextReader =
3939
new SynchronizedTextReader(
4040
new StreamReader(Stream, Cathode.Terminal.Encoding, false, ReadBufferSize, true));
41-
IsValid = TerminalInterop.IsValid(handle, write: false);
42-
IsInteractive = TerminalInterop.IsInteractive(handle);
41+
IsValid = TerminalInterop.IsValid(descriptor, write: false);
42+
IsInteractive = TerminalInterop.IsInteractive(descriptor);
4343
}
4444

45-
private unsafe int ReadPartialNative(scoped Span<byte> buffer, CancellationToken cancellationToken)
45+
private int ReadPartialNative(scoped Span<byte> buffer, CancellationToken cancellationToken)
4646
{
4747
using var guard = Terminal.Control.Guard();
4848

@@ -53,12 +53,12 @@ private unsafe int ReadPartialNative(scoped Span<byte> buffer, CancellationToken
5353

5454
using (_semaphore.Enter(cancellationToken))
5555
{
56-
_cancellationHook?.Invoke(Handle, cancellationToken);
56+
_cancellationHook?.Invoke((nuint)Descriptor, cancellationToken);
5757

5858
int progress;
5959

6060
fixed (byte* p = buffer)
61-
TerminalInterop.Read(Handle, p, buffer.Length, &progress).ThrowIfError();
61+
TerminalInterop.Read(Descriptor, p, buffer.Length, &progress).ThrowIfError();
6262

6363
return progress;
6464
}

src/core/Terminals/NativeTerminalWriter.cs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,35 @@
22

33
namespace Vezel.Cathode.Terminals;
44

5-
internal sealed class NativeTerminalWriter : TerminalWriter
5+
internal sealed unsafe class NativeTerminalWriter : TerminalWriter
66
{
77
// Unlike NativeTerminalReader, the buffer size here is arbitrary and only has performance implications.
88
private const int WriteBufferSize = 256;
99

1010
public NativeVirtualTerminal Terminal { get; }
1111

12-
public nuint Handle { get; }
12+
public TerminalInterop.TerminalDescriptor* Descriptor { get; }
1313

14-
public override sealed Stream Stream { get; }
14+
public override Stream Stream { get; }
1515

16-
public override sealed TextWriter TextWriter { get; }
16+
public override TextWriter TextWriter { get; }
1717

18-
public override sealed bool IsValid { get; }
18+
public override bool IsValid { get; }
1919

20-
public override sealed bool IsInteractive { get; }
20+
public override bool IsInteractive { get; }
2121

2222
private readonly SemaphoreSlim _semaphore;
2323

2424
private readonly Action<nuint, CancellationToken>? _cancellationHook;
2525

2626
public NativeTerminalWriter(
2727
NativeVirtualTerminal terminal,
28-
nuint handle,
28+
TerminalInterop.TerminalDescriptor* descriptor,
2929
SemaphoreSlim semaphore,
3030
Action<nuint, CancellationToken>? cancellationHook)
3131
{
3232
Terminal = terminal;
33-
Handle = handle;
33+
Descriptor = descriptor;
3434
_semaphore = semaphore;
3535
_cancellationHook = cancellationHook;
3636
Stream = new SynchronizedStream(new TerminalOutputStream(this));
@@ -39,11 +39,11 @@ public NativeTerminalWriter(
3939
{
4040
AutoFlush = true,
4141
});
42-
IsValid = TerminalInterop.IsValid(handle, write: true);
43-
IsInteractive = TerminalInterop.IsInteractive(handle);
42+
IsValid = TerminalInterop.IsValid(descriptor, write: true);
43+
IsInteractive = TerminalInterop.IsInteractive(descriptor);
4444
}
4545

46-
private unsafe int WritePartialNative(scoped ReadOnlySpan<byte> buffer, CancellationToken cancellationToken)
46+
private int WritePartialNative(scoped ReadOnlySpan<byte> buffer, CancellationToken cancellationToken)
4747
{
4848
using var guard = Terminal.Control.Guard();
4949

@@ -54,12 +54,12 @@ private unsafe int WritePartialNative(scoped ReadOnlySpan<byte> buffer, Cancella
5454

5555
using (_semaphore.Enter(cancellationToken))
5656
{
57-
_cancellationHook?.Invoke(Handle, cancellationToken);
57+
_cancellationHook?.Invoke((nuint)Descriptor, cancellationToken);
5858

5959
int progress;
6060

6161
fixed (byte* p = buffer)
62-
TerminalInterop.Write(Handle, p, buffer.Length, &progress).ThrowIfError();
62+
TerminalInterop.Write(Descriptor, p, buffer.Length, &progress).ThrowIfError();
6363

6464
return progress;
6565
}

src/core/Terminals/NativeVirtualTerminal.cs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ internal abstract class NativeVirtualTerminal : SystemVirtualTerminal
1414

1515
public override sealed NativeTerminalWriter TerminalOut { get; }
1616

17-
[SuppressMessage("", "CA2000")]
18-
[SuppressMessage("", "CA2214")]
1917
private protected unsafe NativeVirtualTerminal()
2018
{
2119
// Ensure that the native library is fully loaded and initialized before we do anything terminal-related.
@@ -24,13 +22,23 @@ private protected unsafe NativeVirtualTerminal()
2422
var inLock = new SemaphoreSlim(1, 1);
2523
var outLock = new SemaphoreSlim(1, 1);
2624

27-
nuint stdIn;
28-
nuint stdOut;
29-
nuint stdErr;
30-
nuint ttyIn;
31-
nuint ttyOut;
25+
TerminalInterop.TerminalDescriptor* stdIn;
26+
TerminalInterop.TerminalDescriptor* stdOut;
27+
TerminalInterop.TerminalDescriptor* stdErr;
28+
TerminalInterop.TerminalDescriptor* ttyIn;
29+
TerminalInterop.TerminalDescriptor* ttyOut;
3230

33-
TerminalInterop.GetHandles(&stdIn, &stdOut, &stdErr, &ttyIn, &ttyOut);
31+
TerminalInterop.GetDescriptors(&stdIn, &stdOut, &stdErr, &ttyIn, &ttyOut);
32+
33+
NativeTerminalReader CreateReader(TerminalInterop.TerminalDescriptor* descriptor, SemaphoreSlim semaphore)
34+
{
35+
return new(this, descriptor, semaphore, CreateCancellationHook(write: false));
36+
}
37+
38+
NativeTerminalWriter CreateWriter(TerminalInterop.TerminalDescriptor* descriptor, SemaphoreSlim semaphore)
39+
{
40+
return new(this, descriptor, semaphore, CreateCancellationHook(write: true));
41+
}
3442

3543
StandardIn = CreateReader(stdIn, inLock);
3644
StandardOut = CreateWriter(stdOut, outLock);
@@ -39,9 +47,7 @@ private protected unsafe NativeVirtualTerminal()
3947
TerminalOut = CreateWriter(ttyOut, outLock);
4048
}
4149

42-
protected abstract NativeTerminalReader CreateReader(nuint handle, SemaphoreSlim semaphore);
43-
44-
protected abstract NativeTerminalWriter CreateWriter(nuint handle, SemaphoreSlim semaphore);
50+
protected abstract Action<nuint, CancellationToken>? CreateCancellationHook(bool write);
4551

4652
private protected override sealed unsafe Size? QuerySize()
4753
{

src/core/Terminals/UnixCancellationPipe.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ namespace Vezel.Cathode.Terminals;
55
[SuppressMessage("", "CA1001")]
66
internal sealed class UnixCancellationPipe
77
{
8+
// TODO: Move this logic to src/native/driver-unix.c.
9+
810
private readonly AnonymousPipeServerStream _server;
911

1012
private readonly AnonymousPipeClientStream _client;
@@ -18,7 +20,7 @@ public UnixCancellationPipe(bool write)
1820
_write = write;
1921
}
2022

21-
public unsafe void PollWithCancellation(nuint handle, CancellationToken cancellationToken)
23+
public unsafe void PollWithCancellation(int fd, CancellationToken cancellationToken)
2224
{
2325
// Note that the runtime sets up a SIGPIPE handler for us.
2426

@@ -31,8 +33,8 @@ public unsafe void PollWithCancellation(nuint handle, CancellationToken cancella
3133
{
3234
var handles = stackalloc[]
3335
{
34-
(nuint)pipeHandle.DangerousGetHandle(),
35-
handle,
36+
(int)pipeHandle.DangerousGetHandle(),
37+
fd,
3638
};
3739
var results = stackalloc bool[2];
3840

src/core/Terminals/UnixVirtualTerminal.cs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,10 @@ internal sealed class UnixVirtualTerminal : NativeVirtualTerminal
66

77
public static UnixVirtualTerminal Instance { get; } = new();
88

9-
[SuppressMessage("", "IDE0052")]
109
private readonly PosixSignalRegistration _sigWinch;
1110

12-
[SuppressMessage("", "IDE0052")]
1311
private readonly PosixSignalRegistration _sigCont;
1412

15-
[SuppressMessage("", "IDE0052")]
1613
private readonly PosixSignalRegistration _sigChld;
1714

1815
public unsafe UnixVirtualTerminal()
@@ -57,17 +54,14 @@ void HandleSignal(PosixSignalContext context)
5754
_sigChld = PosixSignalRegistration.Create(PosixSignal.SIGCHLD, HandleSignal);
5855
}
5956

60-
protected override NativeTerminalReader CreateReader(nuint handle, SemaphoreSlim semaphore)
57+
protected override unsafe Action<nuint, CancellationToken> CreateCancellationHook(bool write)
6158
{
62-
var pipe = new UnixCancellationPipe(write: false);
59+
var pipe = new UnixCancellationPipe(write);
6360

64-
return new(this, handle, semaphore, cancellationHook: pipe.PollWithCancellation);
65-
}
66-
67-
protected override NativeTerminalWriter CreateWriter(nuint handle, SemaphoreSlim semaphore)
68-
{
69-
var pipe = new UnixCancellationPipe(write: true);
70-
71-
return new(this, handle, semaphore, cancellationHook: pipe.PollWithCancellation);
61+
return (descriptor, cancellationToken) =>
62+
{
63+
if (cancellationToken.CanBeCanceled)
64+
pipe.PollWithCancellation(*(int*)descriptor, cancellationToken);
65+
};
7266
}
7367
}

src/core/Terminals/WindowsVirtualTerminal.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,8 @@ private WindowsVirtualTerminal()
2020
{
2121
}
2222

23-
protected override NativeTerminalReader CreateReader(nuint handle, SemaphoreSlim semaphore)
23+
protected override unsafe Action<nuint, CancellationToken>? CreateCancellationHook(bool write)
2424
{
25-
return new(this, handle, semaphore, cancellationHook: null);
26-
}
27-
28-
protected override NativeTerminalWriter CreateWriter(nuint handle, SemaphoreSlim semaphore)
29-
{
30-
return new(this, handle, semaphore, cancellationHook: null);
25+
return null;
3126
}
3227
}

0 commit comments

Comments
 (0)