Skip to content

Commit

Permalink
enable clipboard as input
Browse files Browse the repository at this point in the history
  • Loading branch information
IS4Code committed Jun 7, 2023
1 parent 44c3e87 commit 87bfe56
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 24 deletions.
118 changes: 118 additions & 0 deletions SFI.Application/Tools/MappedStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
using IS4.SFI.Tools;
using System;
using System.IO;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;

namespace IS4.SFI.Application.Tools
{
/// <summary>
/// Provides a custom stream with a buffer mapped to external storage.
/// </summary>
public abstract class MappedStream : MemoryStream
{
bool loaded;

/// <summary>
/// Pre-loads the data into the stream.
/// </summary>
/// <returns>A task representing the operation.</returns>
protected abstract ValueTask Load();

/// <summary>
/// Stores the data into the external storage.
/// </summary>
/// <param name="data">The data in the stream.</param>
/// <returns>A task representing the operation.</returns>
protected abstract ValueTask Store(ArraySegment<byte> data);

/// <inheritdoc/>
public override void Close()
{
if(!loaded)
{
var valueTask = Store(this.GetData());
if(valueTask.IsCompletedSuccessfully)
{
return;
}else if(valueTask.IsFaulted)
{
var task = valueTask.AsTask();
if(task.Exception.InnerExceptions.Count == 1)
{
ExceptionDispatchInfo.Capture(task.Exception.InnerException).Throw();
}
throw task.Exception;
}else try{
valueTask.AsTask().Wait();
}catch(SynchronizationLockException)
{

}catch(PlatformNotSupportedException)
{

}
}

base.Close();
}

private async ValueTask Initialize()
{
if(!loaded)
{
loaded = true;
await Load();
Position = 0;
}
}

private void InitializeSync()
{
var valueTask = Initialize();
if(valueTask.IsCompletedSuccessfully)
{
return;
}else if(valueTask.IsFaulted)
{
var task = valueTask.AsTask();
if(task.Exception.InnerExceptions.Count == 1)
{
ExceptionDispatchInfo.Capture(task.Exception.InnerException).Throw();
}
throw task.Exception;
}else{
valueTask.AsTask().Wait();
}
}

/// <inheritdoc/>
public override int Read(byte[] buffer, int offset, int count)
{
InitializeSync();
return base.Read(buffer, offset, count);
}

/// <inheritdoc/>
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state)
{
InitializeSync();
return base.BeginRead(buffer, offset, count, callback, state);
}

/// <inheritdoc/>
public async override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
await Initialize();
return await base.ReadAsync(buffer, offset, count, cancellationToken);
}

/// <inheritdoc/>
public override int ReadByte()
{
InitializeSync();
return base.ReadByte();
}
}
}
49 changes: 38 additions & 11 deletions SFI.ConsoleApp/ClipboardStream.cs
Original file line number Diff line number Diff line change
@@ -1,30 +1,57 @@
using IS4.SFI.Tools;
using IS4.SFI.Application.Tools;
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Schedulers;
using System.Windows.Forms;

namespace IS4.SFI.ConsoleApp
{
internal class ClipboardStream : MemoryStream
internal class ClipboardStream : MappedStream
{
public override void Close()
static readonly Encoding encoding = Encoding.UTF8;

readonly TaskScheduler staTaskScheduler = new StaTaskScheduler(1);

static void SetText(string text)
{
var array = this.GetData();
Clipboard.SetText(text, TextDataFormat.UnicodeText);
}

var text = Encoding.UTF8.GetString(array);
static string GetText()
{
var text = Clipboard.GetText(TextDataFormat.UnicodeText);
if(String.IsNullOrEmpty(text))
{
return Clipboard.GetText(TextDataFormat.Text);
}
return text;
}

protected async override ValueTask Store(ArraySegment<byte> data)
{
var text = encoding.GetString(data);
if(Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
{
Clipboard.SetText(text, TextDataFormat.UnicodeText);
SetText(text);
}else{
var thread = new Thread(() => Clipboard.SetText(text, TextDataFormat.UnicodeText));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
await Task.Factory.StartNew(() => SetText(text), CancellationToken.None, 0, staTaskScheduler);
}
}

base.Close();
protected async override ValueTask Load()
{
string text;
if(Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
{
text = GetText();
}else{
text = await Task.Factory.StartNew(GetText, CancellationToken.None, 0, staTaskScheduler);
}
using var writer = new StreamWriter(this, encoding: encoding, leaveOpen: true);
writer.Write(text);
}
}
}
23 changes: 19 additions & 4 deletions SFI.ConsoleApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ public IEnumerable<IFileNodeInfo> GetFiles(string path)
{
if(StandardPaths.IsInput(path))
{
return new IFileNodeInfo[] { new StandardInput() };
return new IFileNodeInfo[] { new DeviceInput(Console.OpenStandardInput) };
}else if(StandardPaths.IsClipboard(path))
{
return new IFileNodeInfo[] { new DeviceInput(() => new ClipboardStream()) };
}
var fileName = Path.GetFileName(path);
if(fileName.Contains('*') || fileName.Contains('?'))
Expand Down Expand Up @@ -100,10 +103,17 @@ public ValueTask Update()
}

/// <summary>
/// This class represents the standard input as a file.
/// This class represents a device as a file.
/// </summary>
class StandardInput : IFileInfo
class DeviceInput : IFileInfo
{
readonly Func<Stream> openFunc;

public DeviceInput(Func<Stream> openFunc)
{
this.openFunc = openFunc;
}

public string? Name => null;

public string? SubName => null;
Expand Down Expand Up @@ -132,7 +142,12 @@ class StandardInput : IFileInfo

public Stream Open()
{
return Console.OpenStandardInput();
return openFunc();
}

public override string ToString()
{
return null;
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions SFI.ConsoleApp/SFI.ConsoleApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ParallelExtensionsExtras.CrossPlatform" Version="1.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\SFI.MediaAnalysis\SFI.MediaAnalysis.csproj" />
<ProjectReference Include="..\SFI.Application\SFI.Application.csproj" />
Expand Down
24 changes: 15 additions & 9 deletions SFI.WebApp/ClipboardStream.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
using IS4.SFI.Tools;
using IS4.SFI.Application.Tools;
using Microsoft.JSInterop;
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace IS4.SFI.WebApp
{
internal class ClipboardStream : MemoryStream
internal class ClipboardStream : MappedStream
{
static readonly Encoding encoding = Encoding.UTF8;

readonly IJSInProcessRuntime runtime;

public ClipboardStream(IJSInProcessRuntime runtime)
{
this.runtime = runtime;
}

public override void Close()
protected override ValueTask Store(ArraySegment<byte> data)
{
var array = this.GetData();

var text = Encoding.UTF8.GetString(array);

runtime.InvokeVoid("navigator.clipboard.writeText", text);
var text = encoding.GetString(data);
return runtime.InvokeVoidAsync("navigator.clipboard.writeText", text);
}

base.Close();
protected async override ValueTask Load()
{
var text = await runtime.InvokeAsync<string>("navigator.clipboard.readText");
using var writer = new StreamWriter(this, encoding: encoding, leaveOpen: true);
writer.Write(text);
}
}
}
53 changes: 53 additions & 0 deletions SFI.WebApp/WebEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ public WebEnvironment(IJSInProcessRuntime js, TextWriter writer, IReadOnlyDictio
/// <inheritdoc/>
public IEnumerable<IFileNodeInfo> GetFiles(string path)
{
if(StandardPaths.IsClipboard(path))
{
return new IFileNodeInfo[] { new DeviceInput(() => new ClipboardStream(js)) };
}
if(inputFiles == null)
{
return Array.Empty<IFileNodeInfo>();
Expand Down Expand Up @@ -183,5 +187,54 @@ async Task Inner()
}
}
}

/// <summary>
/// This class represents a device as a file.
/// </summary>
class DeviceInput : IFileInfo
{
readonly Func<Stream> openFunc;

public DeviceInput(Func<Stream> openFunc)
{
this.openFunc = openFunc;
}

public string? Name => null;

public string? SubName => null;

public string? Path => null;

public int? Revision => null;

public DateTime? CreationTime => null;

public DateTime? LastWriteTime => null;

public DateTime? LastAccessTime => null;

public FileKind Kind => FileKind.None;

public long Length => -1;

public FileAttributes Attributes => FileAttributes.Device;

public StreamFactoryAccess Access => StreamFactoryAccess.Single;

public object? ReferenceKey => this;

public object? DataKey => null;

public Stream Open()
{
return openFunc();
}

public override string ToString()
{
return null;
}
}
}
}

0 comments on commit 87bfe56

Please sign in to comment.