Skip to content

Commit

Permalink
WIP: Implement native terminal drivers.
Browse files Browse the repository at this point in the history
  • Loading branch information
alexrp committed Jan 6, 2024
1 parent 69f1c65 commit 8f403b9
Show file tree
Hide file tree
Showing 16 changed files with 288 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/out
*.user
.clangd
.idea
.vs
node_modules
1 change: 1 addition & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"github.vscode-github-actions",
"github.vscode-pull-request-github",
"jock.svg",
"llvm-vs-code-extensions.vscode-clangd",
"ms-dotnettools.csharp",
"redhat.vscode-xml",
"redhat.vscode-yaml",
Expand Down
4 changes: 3 additions & 1 deletion Directory.Build.rsp
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
-err
# TODO: https://github.com/ziglang/zig/issues/13385
# TODO: https://github.com/ziglang/zig/issues/15398
#-err
-nr:false
-tl
2 changes: 1 addition & 1 deletion Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<Using Include="System.Text" />
</ItemGroup>

<ItemGroup>
<ItemGroup Condition="'$(UsingVezelZigSdk)' != 'true'">
<AdditionalFiles Include="$(MSBuildThisFileDirectory).stylecop.json" />
</ItemGroup>

Expand Down
3 changes: 3 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
<ItemGroup>
<GlobalPackageReference Include="DotNet.ReproducibleBuilds"
Version="1.1.1" />
</ItemGroup>

<ItemGroup Condition="'$(UsingVezelZigSdk)' != 'true'">
<GlobalPackageReference Include="Microsoft.VisualStudio.Threading.Analyzers"
Version="17.8.14" />
<GlobalPackageReference Include="Nerdbank.GitVersioning"
Expand Down
3 changes: 2 additions & 1 deletion global.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"allowPrelease": false
},
"msbuild-sdks": {
"Microsoft.Build.Traversal": "4.1.0"
"Microsoft.Build.Traversal": "4.1.0",
"Vezel.Zig.Sdk": "4.2.16"
}
}
112 changes: 112 additions & 0 deletions src/core/Native/TerminalInterop.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
namespace Vezel.Cathode.Native;

internal static unsafe partial class TerminalInterop
{
public enum TerminalException
{
None,
ArgumentOutOfRange,
PlatformNotSupported,
TerminalNotAttached,
TerminalConfiguration,
Terminal,
}

[StructLayout(LayoutKind.Sequential)]
public struct TerminalResult
{
public TerminalException Exception;

public sbyte* Message;

public int Error;
}

private const string Library = "Vezel.Cathode.Native";

static TerminalInterop()
{
NativeLibrary.SetDllImportResolver(
#pragma warning disable CS0436
typeof(ThisAssembly).Assembly,
#pragma warning restore CS0436
static (name, asm, paths) =>
{
// First try the normal search algorithm that takes into account the application's configuration and
// static dependency information.
if (NativeLibrary.TryLoad(name, asm, paths, out var handle))
return handle;

// If someone is trying to load some unknown library through our assembly, at this point, there is
// nothing more that we can do.
if (name != Library)
return 0;

// It is now likely that someone is trying to use Cathode without static dependency information, so the
// runtime has no idea how to find Vezel.Cathode.Native. In this case, it is likely to either sit right
// next to Vezel.Cathode.dll, or in runtimes/<rid>/native.

var directory = AppContext.BaseDirectory;
var fileName = OperatingSystem.IsWindows()
? $"{name}.dll"
: OperatingSystem.IsMacOS()
? $"lib{name}.dylib"
: $"lib{name}.so";

bool TryLoad(out nint handle, params string[] paths)
{
return NativeLibrary.TryLoad(Path.Combine([directory, .. paths, fileName]), out handle);
}

return TryLoad(out handle)
? handle
: TryLoad(out handle, "runtimes", RuntimeInformation.RuntimeIdentifier, "native")
? handle
: 0;
});
}

[LibraryImport(Library, EntryPoint = "cathode_initialize")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial void Initialize();

[LibraryImport(Library, EntryPoint = "cathode_driver_set_mode")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial TerminalResult DriverSetMode(
[MarshalAs(UnmanagedType.U1)] bool raw, [MarshalAs(UnmanagedType.U1)] bool flush);

[LibraryImport(Library, EntryPoint = "cathode_driver_query_size")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
[return: MarshalAs(UnmanagedType.U1)]
public static partial bool DriverQuerySize(int* width, int* height);

[LibraryImport(Library, EntryPoint = "cathode_driver_generate_signal")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial TerminalResult DriverGenerateSignal(TerminalSignal signal);

[LibraryImport(Library, EntryPoint = "cathode_driver_open_standard_io")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial void DriverOpenStandardIO(nuint* @in, nuint* @out, nuint* error);

[LibraryImport(Library, EntryPoint = "cathode_driver_open_terminal_io")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial void DriverOpenTerminalIO(nuint* @in, nuint* @out);

[LibraryImport(Library, EntryPoint = "cathode_driver_is_valid")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
[return: MarshalAs(UnmanagedType.U1)]
public static partial bool DriverIsValid(nuint handle, [MarshalAs(UnmanagedType.U1)] bool write);

[LibraryImport(Library, EntryPoint = "cathode_driver_is_interactive")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
[return: MarshalAs(UnmanagedType.U1)]
public static partial bool DriverIsInteractive(nuint handle);

[LibraryImport(Library, EntryPoint = "cathode_driver_read")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial TerminalResult DriverRead(nuint handle, byte* buffer, int length, int* progress);

[LibraryImport(Library, EntryPoint = "cathode_driver_write")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial TerminalResult DriverWrite(nuint handle, byte* buffer, int length, int* progress);
}
1 change: 1 addition & 0 deletions src/core/TerminalSignal.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace Vezel.Cathode;

// Keep in sync with src/native/driver.h.
public enum TerminalSignal
{
Close,
Expand Down
18 changes: 18 additions & 0 deletions src/core/core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ This package provides the core terminal API.</PackageDescription>
<ProjectReference Include="../analyzers/analyzers.csproj"
ReferenceOutputAssembly="false" />
<ProjectReference Include="../common/common.csproj" />
<ProjectReference Include="../native/native.cproj"
ReferenceOutputAssembly="false" />
</ItemGroup>

<ItemGroup>
Expand All @@ -46,6 +48,22 @@ This package provides the core terminal API.</PackageDescription>
<PackageReference Include="Wcwidth" />
</ItemGroup>

<Target Name="_AddNativeLibraries"
BeforeTargets="AssignTargetPaths; _GetPackageFiles">
<MSBuild Projects="../native/native.cproj"
Targets="_GetNativeLibraries">
<Output TaskParameter="TargetOutputs"
ItemName="_NativeLibrary" />
</MSBuild>

<ItemGroup>
<Content Include="@(_NativeLibrary)"
Link="runtimes/%(RuntimeIdentifier)/native/%(Filename)%(Extension)"
CopyToOutputDirectory="PreserveNewest"
PackagePath="runtimes/%(RuntimeIdentifier)/native" />
</ItemGroup>
</Target>

<Target Name="_PackAnalyzer">
<MSBuild Projects="../analyzers/analyzers.csproj"
Targets="GetTargetPath"
Expand Down
17 changes: 17 additions & 0 deletions src/native/cathode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

#define atomic _Atomic
#define nonnull _Nonnull
#define nullable _Nullable

#if defined(ZIG_OS_WINDOWS)
# define CATHODE_API [[gnu::dllexport]]
#else
# define CATHODE_API [[gnu::visibility("default")]]
#endif
6 changes: 6 additions & 0 deletions src/native/common.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "common.h"

void cathode_initialize(void)
{
// This is just used to ensure that the library is loaded.
}
3 changes: 3 additions & 0 deletions src/native/common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#pragma once

CATHODE_API void cathode_initialize(void);
13 changes: 13 additions & 0 deletions src/native/driver-unix.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#if !defined(ZIG_OS_WINDOWS)

#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>

#include "driver.h"

#endif
7 changes: 7 additions & 0 deletions src/native/driver-windows.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#if defined(ZIG_OS_WINDOWS)

#include <windows.h>

#include "driver.h"

#endif
47 changes: 47 additions & 0 deletions src/native/driver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#pragma once

typedef size_t TerminalHandle;

typedef enum {
TerminalException_None,
TerminalException_ArgumentOutOfRange,
TerminalException_PlatformNotSupported,
TerminalException_TerminalNotAttached,
TerminalException_TerminalConfiguration,
TerminalException_Terminal,
} TerminalException;

typedef struct {
TerminalException exception;
const char *nullable message;
int32_t error;
} TerminalResult;

// Keep in sync with src/core/TerminalSignal.cs (public API).
typedef enum {
TerminalSignal_Close,
TerminalSignal_Interrupt,
TerminalSignal_Quit,
TerminalSignal_Terminate,
} TerminalSignal;

CATHODE_API TerminalResult cathode_driver_set_mode(bool raw, bool flush);

CATHODE_API bool cathode_driver_query_size(int32_t *nonnull width, int32_t *nonnull height);

CATHODE_API TerminalResult cathode_driver_generate_signal(TerminalSignal signal);

CATHODE_API void cathode_driver_open_standard_io(
TerminalHandle *nonnull in, TerminalHandle *nonnull out, TerminalHandle *nonnull error);

CATHODE_API void cathode_driver_open_terminal_io(TerminalHandle *nonnull in, TerminalHandle *nonnull out);

CATHODE_API bool cathode_driver_is_valid(TerminalHandle handle, bool write);

CATHODE_API bool cathode_driver_is_interactive(TerminalHandle handle);

CATHODE_API TerminalResult cathode_driver_read(
TerminalHandle handle, uint8_t *nullable buffer, int32_t length, int32_t *nonnull progress);

CATHODE_API TerminalResult cathode_driver_write(
TerminalHandle handle, const uint8_t *nullable buffer, int32_t length, int32_t *nonnull progress);
53 changes: 53 additions & 0 deletions src/native/native.cproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<Project Sdk="Vezel.Zig.Sdk">
<PropertyGroup>
<AssemblyName>Vezel.Cathode.Native</AssemblyName>
<DefineConstants>
$(DefineConstants);
_GNU_SOURCE;
_UNICODE;
UNICODE;
WIN32_LEAN_AND_MEAN
</DefineConstants>
<!-- TODO: Remove when -err is re-enabled in Directory.Build.rsp. -->
<MSBuildTreatWarningsAsErrors>false</MSBuildTreatWarningsAsErrors>
<!-- TODO: https://github.com/vezel-dev/zig-sdk/issues/83 -->
<RuntimeIdentifiers>
linux-arm;
linux-arm64;
linux-x64;
osx-arm64;
osx-x64;
win-arm64;
win-x86;
win-x64
</RuntimeIdentifiers>
</PropertyGroup>

<ItemGroup>
<PreludeHeader Include="cathode.h" />
</ItemGroup>

<Target Name="_GetNativeLibrary"
Returns="@(_NativeLibrary)">
<ItemGroup>
<_NativeLibrary Include="$(TargetPath)"
RuntimeIdentifier="$(RuntimeIdentifier)" />
</ItemGroup>
</Target>

<Target Name="_GetNativeLibraries"
Returns="@(_NativeLibrary)">
<ItemGroup>
<_RuntimeIdentifiers Include="$(RuntimeIdentifiers)" />
<_Projects Include="$(MSBuildProjectFullPath)"
Properties="RuntimeIdentifier=%(_RuntimeIdentifiers.Identity)" />
</ItemGroup>

<MSBuild Projects="@(_Projects)"
Targets="_GetNativeLibrary"
BuildInParallel="$(BuildInParallel)">
<Output TaskParameter="TargetOutputs"
ItemName="_NativeLibrary" />
</MSBuild>
</Target>
</Project>

0 comments on commit 8f403b9

Please sign in to comment.