Skip to content

Commit

Permalink
use /dev/picker to open dialogs
Browse files Browse the repository at this point in the history
  • Loading branch information
IS4Code committed Jul 15, 2023
1 parent 8e50b73 commit f2954eb
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 33 deletions.
12 changes: 12 additions & 0 deletions SFI.Application/Tools/StandardPaths.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,17 @@ public static bool IsClipboard(string path)
{
return path is "/dev/clipboard";
}

/// <summary>
/// Checks whether <paramref name="path"/> identifies the file picker dialog.
/// </summary>
/// <param name="path">The path to check.</param>
/// <returns>
/// Whether the path equals <c>/dev/picker</c>.
/// </returns>
public static bool IsFilePicker(string path)
{
return path is "/dev/picker";
}
}
}
41 changes: 9 additions & 32 deletions SFI.ConsoleApp/ClipboardStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
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
Expand All @@ -13,43 +11,22 @@ internal class ClipboardStream : MappedStream
{
static readonly Encoding encoding = Encoding.UTF8;

readonly TaskScheduler staTaskScheduler = new StaTaskScheduler(1);

static void SetText(string text)
{
Clipboard.SetText(text, TextDataFormat.UnicodeText);
}

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)
{
SetText(text);
}else{
await Task.Factory.StartNew(() => SetText(text), CancellationToken.None, 0, staTaskScheduler);
}
await StaThread.InvokeAsync(() => Clipboard.SetText(text, TextDataFormat.UnicodeText));
}

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);
}
var text = await StaThread.InvokeAsync(() => {
var text = Clipboard.GetText(TextDataFormat.UnicodeText);
if(String.IsNullOrEmpty(text))
{
return Clipboard.GetText(TextDataFormat.Text);
}
return text;
});
using var writer = new StreamWriter(this, encoding: encoding, leaveOpen: true);
writer.Write(text);
}
Expand Down
51 changes: 50 additions & 1 deletion SFI.ConsoleApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace IS4.SFI.ConsoleApp
{
Expand Down Expand Up @@ -50,6 +50,12 @@ public IEnumerable<IFileNodeInfo> GetFiles(string path)
}else if(StandardPaths.IsClipboard(path))
{
return new IFileNodeInfo[] { new DeviceInput(() => new ClipboardStream()) };
}else if(StandardPaths.IsFilePicker(path))
{
return new IFileNodeInfo[] { new DeviceInput(() => {
var path = ShowDialog<OpenFileDialog>("Load input file", null);
return File.OpenRead(path);
}) };
}
var fileName = Path.GetFileName(path);
if(fileName.Contains('*') || fileName.Contains('?'))
Expand Down Expand Up @@ -88,6 +94,10 @@ public Stream CreateFile(string path, string mediaType)
}else if(StandardPaths.IsClipboard(path))
{
return new ClipboardStream();
}else if(StandardPaths.IsFilePicker(path))
{
path = ShowDialog<SaveFileDialog>("Save output file", mediaType);
return File.Create(path);
}
var dir = Path.GetDirectoryName(path);
if(!String.IsNullOrWhiteSpace(dir))
Expand All @@ -102,6 +112,45 @@ public ValueTask Update()
return default;
}

static readonly byte[] appGuidNS = Guid.Parse("b1ebfec4-0e2f-42e3-8357-6724abc06db3").ToByteArray();

static string ShowDialog<TDialog>(string title, string? filter) where TDialog : FileDialog, new()
{
return StaThread.Invoke(() => {
var dialog = new TDialog();
dialog.DereferenceLinks = false;
dialog.ClientGuid = DataTools.GuidFromName(appGuidNS, title);
dialog.Title = title;
if(filter != null)
{
dialog.Filter = filter + "|*";
}

DialogResult result;
var window = ConsoleWindow.Instance;
if(window.Handle != IntPtr.Zero)
{
result = dialog.ShowDialog(window);
}else{
result = dialog.ShowDialog();
}
if(result != DialogResult.OK)
{
throw new ApplicationException($"{title} dialog canceled!");
}
return dialog.FileName;
});
}

class ConsoleWindow : IWin32Window
{
public static readonly ConsoleWindow Instance = new ConsoleWindow();

readonly Process process = Process.GetCurrentProcess();

public IntPtr Handle => process.MainWindowHandle;
}

/// <summary>
/// This class represents a device as a file.
/// </summary>
Expand Down
71 changes: 71 additions & 0 deletions SFI.ConsoleApp/StaThread.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Schedulers;

namespace IS4.SFI.ConsoleApp
{
/// <summary>
/// Used to execute code in a STA context.
/// </summary>
static class StaThread
{
static readonly TaskScheduler scheduler = new StaTaskScheduler(1);

static bool IsSTA => Thread.CurrentThread.GetApartmentState() == ApartmentState.STA;

public static void Invoke(Action action)
{
if(IsSTA)
{
action();
}else{
try{
Task.Factory.StartNew(action, CancellationToken.None, 0, scheduler).Wait();
}catch(AggregateException e) when(e.InnerExceptions.Count == 1)
{
ExceptionDispatchInfo.Capture(e.InnerException!).Throw();
throw;
}
}
}

public static T Invoke<T>(Func<T> func)
{
if(IsSTA)
{
return func();
}else{
try{
return Task<T>.Factory.StartNew(func, CancellationToken.None, 0, scheduler).Result;
}catch(AggregateException e) when(e.InnerExceptions.Count == 1)
{
ExceptionDispatchInfo.Capture(e.InnerException!).Throw();
throw;
}
}
}

public static ValueTask InvokeAsync(Action action)
{
if(IsSTA)
{
action();
return new();
}else{
return new(Task.Factory.StartNew(action, CancellationToken.None, 0, scheduler));
}
}

public static ValueTask<T> InvokeAsync<T>(Func<T> func)
{
if(IsSTA)
{
return new(func());
}else{
return new(Task<T>.Factory.StartNew(func, CancellationToken.None, 0, scheduler));
}
}
}
}

0 comments on commit f2954eb

Please sign in to comment.