Skip to content

Commit

Permalink
Fix default TwainHandleManager implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
cyanfish committed Sep 5, 2024
1 parent 6456f1c commit fb5a0c9
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 15 deletions.
3 changes: 3 additions & 0 deletions NAPS2.Sdk/Platform/Windows/Win32MessagePump.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

namespace NAPS2.Platform.Windows;

/// <summary>
/// Allows creation of a Win32 event loop without any references to WinForms or WPF.
/// </summary>
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
internal class Win32MessagePump : IInvoker, IDisposable
{
Expand Down
5 changes: 5 additions & 0 deletions NAPS2.Sdk/Platform/Windows/Win32TwainHandleManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

namespace NAPS2.Platform.Windows;

/// <summary>
/// TwainHandleManager implementation that uses a Win32MessagePump to get window handles to hand off to TWAIN.
/// </summary>
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
internal class Win32TwainHandleManager : TwainHandleManager
{
Expand Down Expand Up @@ -64,6 +67,8 @@ public override MessageLoopHook CreateMessageLoopHook(IntPtr dialogParent = defa
return new Win32MessageLoopHook(_messagePump, GetDsmHandle(dialogParent, useNativeUi));
}

public override IInvoker Invoker => _messagePump;

public override void Dispose()
{
if (_disposed) return;
Expand Down
65 changes: 65 additions & 0 deletions NAPS2.Sdk/Scan/Internal/Twain/DefaultTwainHandleManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#if !MAC
using System.Threading;
using NAPS2.Platform.Windows;
using NTwain;

namespace NAPS2.Scan.Internal.Twain;

/// <summary>
/// TwainHandleManager implementation that lazily starts a Win32MessagePump and delegates to a Win32TwainHandleManager.
/// </summary>
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
internal class DefaultTwainHandleManager : TwainHandleManager
{
private Win32MessagePump? _messagePump;
private Win32TwainHandleManager? _inner;

private Win32TwainHandleManager CreateInner()
{
var mre = new ManualResetEvent(false);
var messageThread = new Thread(() =>
{
_messagePump = Win32MessagePump.Create();
mre.Set();
_messagePump.RunMessageLoop();
});
messageThread.SetApartmentState(ApartmentState.STA);
messageThread.Start();
mre.WaitOne();
return new Win32TwainHandleManager(_messagePump!);
}

public override IntPtr GetDsmHandle(IntPtr dialogParent, bool useNativeUi)
{
_inner ??= CreateInner();
return _inner.GetDsmHandle(dialogParent, useNativeUi);
}

public override IntPtr GetEnableHandle(IntPtr dialogParent, bool useNativeUi)
{
_inner ??= CreateInner();
return _inner.GetEnableHandle(dialogParent, useNativeUi);
}

public override MessageLoopHook CreateMessageLoopHook(IntPtr dialogParent = default, bool useNativeUi = false)
{
_inner ??= CreateInner();
return _inner.CreateMessageLoopHook(dialogParent, useNativeUi);
}

public override IInvoker Invoker
{
get
{
_inner ??= CreateInner();
return _inner.Invoker;
}
}

public override void Dispose()
{
_inner?.Dispose();
_messagePump?.Dispose();
}
}
#endif
6 changes: 4 additions & 2 deletions NAPS2.Sdk/Scan/Internal/Twain/LocalTwainController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ private List<ScanDevice> InternalGetDeviceList(ScanOptions options)
{
PlatformInfo.Current.PreferNewDSM = options.TwainOptions.Dsm != TwainDsm.Old;
var session = new TwainSession(TwainAppId);
session.Open(TwainHandleManager.Factory().CreateMessageLoopHook());
using var handleManager = TwainHandleManager.Factory();
session.Open(handleManager.CreateMessageLoopHook());
try
{
return session.GetSources().Select(ds => new ScanDevice(Driver.Twain, ds.Name, ds.Name)).ToList();
Expand Down Expand Up @@ -103,7 +104,8 @@ private ScanCaps InternalGetCaps(ScanOptions options)
{
PlatformInfo.Current.PreferNewDSM = options.TwainOptions.Dsm != TwainDsm.Old;
var session = new TwainSession(TwainAppId);
session.Open(TwainHandleManager.Factory().CreateMessageLoopHook());
using var handleManager = TwainHandleManager.Factory();
session.Open(handleManager.CreateMessageLoopHook());
try
{
var ds = session.GetSources().FirstOrDefault(ds => ds.Name == options.Device!.ID);
Expand Down
26 changes: 17 additions & 9 deletions NAPS2.Sdk/Scan/Internal/Twain/TwainHandleManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,31 @@

namespace NAPS2.Scan.Internal.Twain;

internal class TwainHandleManager : IDisposable
/// <summary>
/// Abstracts how HWND handles are obtained for use with TWAIN.
/// </summary>
internal abstract class TwainHandleManager : IDisposable
{
public static Func<TwainHandleManager> Factory { get; set; } = () => new TwainHandleManager();
public static Func<TwainHandleManager> Factory { get; set; } = () =>
{
#if NET6_0_OR_GREATER
if (!OperatingSystem.IsWindows()) throw new NotSupportedException();
#endif
return new DefaultTwainHandleManager();
};

protected TwainHandleManager()
{
}

public virtual IntPtr GetDsmHandle(IntPtr dialogParent, bool useNativeUi) => dialogParent;
public abstract IntPtr GetDsmHandle(IntPtr dialogParent, bool useNativeUi);

public virtual IntPtr GetEnableHandle(IntPtr dialogParent, bool useNativeUi) => dialogParent;
public abstract IntPtr GetEnableHandle(IntPtr dialogParent, bool useNativeUi);

public virtual MessageLoopHook CreateMessageLoopHook(IntPtr dialogParent = default, bool useNativeUi = false) =>
throw new NotSupportedException();
public abstract MessageLoopHook CreateMessageLoopHook(IntPtr dialogParent = default, bool useNativeUi = false);

public virtual void Dispose()
{
}
public abstract IInvoker Invoker { get; }

public abstract void Dispose();
}
#endif
7 changes: 3 additions & 4 deletions NAPS2.Sdk/Scan/Internal/Twain/TwainScanRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ public TwainScanRunner(ILogger logger, TWIdentity twainAppId, TwainDsm dsm, Scan

public Task Run()
{
// TODO: Work around needing Invoker (maybe pass in SyncContext or something?) and move it to NAPS2.Lib
Invoker.Current.InvokeDispatch(Init);
_handleManager.Invoker.InvokeDispatch(Init);
return _tcs.Task;
}

Expand Down Expand Up @@ -99,8 +98,8 @@ private void Init()
throw GetExceptionForStatus(_source.GetStatus());
}

_cancelToken.Register(() => Invoker.Current.Invoke(FinishWithCancellation));
_sourceDisabledTcs.Task.ContinueWith(_ => Invoker.Current.Invoke(FinishWithCompletion)).AssertNoAwait();
_cancelToken.Register(() => _handleManager.Invoker.Invoke(FinishWithCancellation));
_sourceDisabledTcs.Task.ContinueWith(_ => _handleManager.Invoker.Invoke(FinishWithCompletion)).AssertNoAwait();
}
catch (Exception ex)
{
Expand Down

0 comments on commit fb5a0c9

Please sign in to comment.