Get NAPS2 app running on mac

Not showing any ui yet
This commit is contained in:
Ben Olden-Cooligan 2022-08-20 20:42:28 -07:00
parent c7dcdcf832
commit d17c65dd22
14 changed files with 564 additions and 18 deletions

31
NAPS2.App.Mac/.gitignore vendored Normal file
View File

@ -0,0 +1,31 @@
Thumbs.db
*.obj
*.exe
*.pdb
*.user
*.aps
*.pch
*.vspscc
*_i.c
*_p.c
*.ncb
*.suo
*.sln.docstates
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log
[Bb]in
[Dd]ebug*/
*.lib
*.sbr
obj/
[Rr]elease*/
_ReSharper*/
[Tt]est[Rr]esult*
*.vssscc
$tf*/
publish/
bin/

BIN
NAPS2.App.Mac/Icon.icns Normal file

Binary file not shown.

21
NAPS2.App.Mac/Info.plist Normal file
View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>NAPS2</string>
<key>CFBundleIdentifier</key>
<string>com.naps2.desktop</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>LSMinimumSystemVersion</key>
<string>10.15</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>NSHumanReadableCopyright</key>
<string></string>
<!-- TODO: Somehow the build process is generating this icon file rather than using the one we already have in the project folder -->
<key>CFBundleIconFile</key>
<string>Icon.icns</string>
</dict>
</plist>

View File

@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
<OutputType>Exe</OutputType>
<RootNamespace>NAPS2</RootNamespace>
<AssemblyName>NAPS2</AssemblyName>
<Version>7.0.1</Version>
<ApplicationIcon>../NAPS2.Lib/Icons/favicon.ico</ApplicationIcon>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>true</PublishTrimmed>
<!-- TODO: Do we need a separate project for x64? Multiple RuntimeIdentifiers fails with SelfContained. -->
<RuntimeIdentifier>osx-arm64</RuntimeIdentifier>
<Title>NAPS2 (Not Another PDF Scanner 2)</Title>
<Product>NAPS2 (Not Another PDF Scanner 2)</Product>
<Copyright>Copyright 2009, 2012-2022 NAPS2 Contributors</Copyright>
</PropertyGroup>
<Import Project="..\NAPS2.Setup\CommonTargets.targets" />
<Import Project="..\NAPS2.Setup\NativeLibs.targets" />
<Import Project="..\NAPS2.Setup\SdkUsers.targets" />
<ItemGroup>
<ProjectReference Include="..\NAPS2.Lib.Mac\NAPS2.Lib.Mac.csproj" />
<PackageReference Include="Eto.Platform.Mac64" Version="2.7.0" />
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="..\NAPS2.Setup\appsettings.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<Link>appsettings.xml</Link>
<TargetPath>appsettings.xml</TargetPath>
</ContentWithTargetPath>
</ItemGroup>
</Project>

15
NAPS2.App.Mac/Program.cs Normal file
View File

@ -0,0 +1,15 @@
using NAPS2.EntryPoints;
namespace NAPS2;
static class Program
{
/// <summary>
/// The NAPS2.app main method.
/// </summary>
static void Main(string[] args)
{
// Use reflection to avoid antivirus false positives (yes, really)
typeof(MacEntryPoint).GetMethod("Run").Invoke(null, new object[] { args });
}
}

View File

@ -4,19 +4,18 @@ namespace NAPS2.Images.Mac;
public class MacImage : IMemoryImage
{
private readonly NSImage _image;
internal readonly NSBitmapImageRep _imageRep;
public MacImage(NSImage image)
{
_image = image;
NsImage = image;
// TODO: Better error checking
lock (MacImageContext.ConstructorLock)
{
#if MONOMAC
_imageRep = new NSBitmapImageRep(_image.Representations()[0].Handle, false);
_imageRep = new NSBitmapImageRep(Image.Representations()[0].Handle, false);
#else
_imageRep = (NSBitmapImageRep) _image.Representations()[0];
_imageRep = (NSBitmapImageRep) NsImage.Representations()[0];
#endif
}
// TODO: Also verify color spaces.
@ -45,23 +44,25 @@ public class MacImage : IMemoryImage
}
}
public NSImage NsImage { get; }
public void Dispose()
{
_image.Dispose();
Image.Dispose();
// TODO: Does this need to dispose the imageRep?
}
public int Width => (int) _imageRep.PixelsWide;
public int Height => (int) _imageRep.PixelsHigh;
public float HorizontalResolution => (float) _image.Size.Width.ToDouble() / Width * 72;
public float VerticalResolution => (float) _image.Size.Height.ToDouble() / Height * 72;
public float HorizontalResolution => (float) Image.Size.Width.ToDouble() / Width * 72;
public float VerticalResolution => (float) Image.Size.Height.ToDouble() / Height * 72;
public void SetResolution(float xDpi, float yDpi)
{
// TODO: Image size or imagerep size?
if (xDpi > 0 && yDpi > 0)
{
_image.Size = new CGSize(xDpi / 72 * Width, yDpi / 72 * Height);
Image.Size = new CGSize(xDpi / 72 * Width, yDpi / 72 * Height);
}
}
@ -94,6 +95,8 @@ public class MacImage : IMemoryImage
public ImageFileFormat OriginalFileFormat { get; set; }
public NSImage Image => NsImage;
public void Save(string path, ImageFileFormat imageFormat = ImageFileFormat.Unspecified, int quality = -1)
{
if (imageFormat == ImageFileFormat.Unspecified)
@ -156,9 +159,9 @@ public class MacImage : IMemoryImage
}
#if MONOMAC
var nsImage = new NSImage(_image.Copy().Handle, true);
var nsImage = new NSImage(Image.Copy().Handle, true);
#else
var nsImage = (NSImage) _image.Copy();
var nsImage = (NSImage) NsImage.Copy();
#endif
return new MacImage(nsImage)
{

View File

@ -0,0 +1,51 @@
using System;
using System.Threading.Tasks;
using Eto;
using Eto.Forms;
using NAPS2.EtoForms.Ui;
using NAPS2.Logging;
using NAPS2.Modules;
using NAPS2.Util;
using NAPS2.WinForms;
using Ninject;
using UnhandledExceptionEventArgs = Eto.UnhandledExceptionEventArgs;
namespace NAPS2.EntryPoints;
/// <summary>
/// The entry point logic for NAPS2.exe, the NAPS2 GUI.
/// </summary>
public static class MacEntryPoint
{
public static void Run(string[] args)
{
// Initialize Ninject (the DI framework)
var kernel = new StandardKernel(new CommonModule(), new MacModule(), new RecoveryModule(), new ContextModule());
Paths.ClearTemp();
// Set up basic application configuration
kernel.Get<CultureHelper>().SetCulturesFromConfig();
TaskScheduler.UnobservedTaskException += UnhandledTaskException;
// Show the main form
var application = new Application(Platforms.Mac64);
application.UnhandledException += UnhandledException;
var formFactory = kernel.Get<IFormFactory>();
var desktop = formFactory.Create<DesktopForm>();
// Invoker.Current = new WinFormsInvoker(desktop.ToNative());
application.Run(desktop);
}
private static void UnhandledTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
{
Log.FatalException("An error occurred that caused the task to terminate.", e.Exception);
e.SetObserved();
}
private static void UnhandledException(object? sender, UnhandledExceptionEventArgs e)
{
Log.FatalException("An error occurred that caused the application to close.", e.ExceptionObject as Exception);
}
}

View File

@ -0,0 +1,38 @@
using Eto.Drawing;
using Eto.Forms;
using Eto.Mac.Drawing;
using NAPS2.Images;
using NAPS2.Images.Mac;
using sd = System.Drawing;
namespace NAPS2.EtoForms.Mac;
public class MacEtoPlatform : EtoPlatform
{
private const int MIN_BUTTON_WIDTH = 75;
private const int MIN_BUTTON_HEIGHT = 32;
private const int IMAGE_PADDING = 5;
static MacEtoPlatform()
{
}
public override IListView<T> CreateListView<T>(ListViewBehavior<T> behavior) =>
new MacListView<T>(behavior);
public override void ConfigureImageButton(Button button)
{
}
public override Bitmap ToBitmap(IMemoryImage image)
{
var nsImage = ((MacImage) image).NsImage;
return new Bitmap(new BitmapHandler(nsImage));
}
public override IMemoryImage DrawHourglass(IMemoryImage image)
{
// TODO
return image;
}
}

View File

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using Eto.Forms;
using NAPS2.Images;
using NAPS2.Util;
namespace NAPS2.EtoForms.Mac;
public class MacListView<T> : IListView<T> where T : notnull
{
private readonly ListViewBehavior<T> _behavior;
private ListSelection<T> _selection = ListSelection.Empty<T>();
private bool _refreshing;
public MacListView(ListViewBehavior<T> behavior)
{
_behavior = behavior;
}
public int ImageSize
{
get => 0;
set { }
}
// TODO: Properties here vs on behavior?
public bool AllowDrag { get; set; }
public bool AllowDrop { get; set; }
private void OnDragEnter(object? sender, DragEventArgs e)
{
if (!AllowDrop)
{
return;
}
e.Effects = _behavior.GetDropEffect(e.Data);
}
public Control Control { get; set; }
public event EventHandler? SelectionChanged;
public event EventHandler? ItemClicked;
public event EventHandler<DropEventArgs>? Drop;
public void SetItems(IEnumerable<T> items)
{
}
// TODO: Do we need this method? Clean up the name/doc at least
public void RegenerateImages()
{
}
public void ApplyDiffs(ListViewDiffs<T> diffs)
{
}
public ListSelection<T> Selection
{
get => _selection;
set
{
if (_selection == value)
{
return;
}
_selection = value ?? throw new ArgumentNullException(nameof(value));
SelectionChanged?.Invoke(this, EventArgs.Empty);
}
}
}

View File

@ -0,0 +1,43 @@
using NAPS2.Config;
using NAPS2.Images;
using NAPS2.ImportExport.Images;
using NAPS2.Util;
using NAPS2.WinForms;
namespace NAPS2.EtoForms.Ui;
public class MacDesktopForm : DesktopForm
{
public MacDesktopForm(
Naps2Config config,
// KeyboardShortcutManager ksm,
INotificationManager notify,
CultureHelper cultureHelper,
IProfileManager profileManager,
UiImageList imageList,
ImageTransfer imageTransfer,
ThumbnailRenderQueue thumbnailRenderQueue,
UiThumbnailProvider thumbnailProvider,
DesktopController desktopController,
IDesktopScanController desktopScanController,
ImageListActions imageListActions,
DesktopFormProvider desktopFormProvider,
IDesktopSubFormController desktopSubFormController)
: base(config, /*ksm,*/ notify, cultureHelper, profileManager,
imageList, imageTransfer, thumbnailRenderQueue, thumbnailProvider, desktopController, desktopScanController,
imageListActions, desktopFormProvider, desktopSubFormController)
{
}
protected override void CreateToolbarsAndMenus()
{
}
protected override void ConfigureToolbar()
{
}
protected override void AfterLayout()
{
}
}

View File

@ -0,0 +1,209 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using NAPS2.EtoForms;
using NAPS2.EtoForms.Mac;
using NAPS2.EtoForms.Ui;
using NAPS2.Images;
using NAPS2.ImportExport;
using NAPS2.ImportExport.Email;
using NAPS2.ImportExport.Pdf;
using NAPS2.Logging;
using NAPS2.Operation;
using NAPS2.Scan;
using NAPS2.Update;
using NAPS2.Util;
using NAPS2.WinForms;
using Ninject;
using Ninject.Modules;
namespace NAPS2.Modules;
public class MacModule : NinjectModule
{
public override void Load()
{
// Bind<IBatchScanPerformer>().To<BatchScanPerformer>();
Bind<IPdfPasswordProvider>().To<StubPdfPasswordProvider>();
Bind<ErrorOutput>().To<StubErrorOutput>();
Bind<IOverwritePrompt>().To<StubOverwritePrompt>();
Bind<OperationProgress>().To<StubOperationProgress>().InSingletonScope();
Bind<DialogHelper>().To<StubDialogHelper>();
Bind<INotificationManager>().To<StubNotificationManager>().InSingletonScope();
Bind<ISaveNotify>().ToMethod(ctx => ctx.Kernel.Get<INotificationManager>());
Bind<IScannedImagePrinter>().To<StubScannedImagePrinter>();
Bind<IDevicePrompt>().To<StubDevicePrompt>();
Bind<DesktopController>().ToSelf().InSingletonScope();
Bind<IUpdateChecker>().To<UpdateChecker>();
Bind<IWinFormsExportHelper>().To<StubExportHelper>();
Bind<IDesktopScanController>().To<StubDesktopScanController>();
Bind<IDesktopSubFormController>().To<StubDesktopSubFormController>();
Bind<DesktopFormProvider>().ToSelf().InSingletonScope();
Bind<DesktopForm>().To<MacDesktopForm>();
EtoPlatform.Current = new MacEtoPlatform();
// Log.EventLogger = new WindowsEventLogger(Kernel!.Get<Naps2Config>());
}
}
public class StubDesktopSubFormController : IDesktopSubFormController
{
public void ShowCropForm()
{
}
public void ShowBrightnessContrastForm()
{
}
public void ShowHueSaturationForm()
{
}
public void ShowBlackWhiteForm()
{
}
public void ShowSharpenForm()
{
}
public void ShowRotateForm()
{
}
public void ShowProfilesForm()
{
}
public void ShowOcrForm()
{
}
public void ShowBatchScanForm()
{
}
public void ShowViewerForm()
{
}
public void ShowPdfSettingsForm()
{
}
public void ShowImageSettingsForm()
{
}
public void ShowEmailSettingsForm()
{
}
public void ShowAboutForm()
{
}
public void ShowSettingsForm()
{
}
}
public class StubDesktopScanController : IDesktopScanController
{
public Task ScanWithDevice(string deviceID)
{
return Task.CompletedTask;
}
public Task ScanDefault()
{
return Task.CompletedTask;
}
public Task ScanWithNewProfile()
{
return Task.CompletedTask;
}
public Task ScanWithProfile(ScanProfile profile)
{
return Task.CompletedTask;
}
}
public class StubExportHelper : IWinFormsExportHelper
{
public Task<bool> SavePDF(IList<ProcessedImage> images, ISaveNotify notify)
{
return Task.FromResult(false);
}
public Task<bool> ExportPDF(string filename, IList<ProcessedImage> images, bool email, EmailMessage emailMessage)
{
return Task.FromResult(false);
}
public Task<bool> SaveImages(IList<ProcessedImage> images, ISaveNotify notify)
{
return Task.FromResult(false);
}
public Task<bool> EmailPDF(IList<ProcessedImage> images)
{
return Task.FromResult(false);
}
}
public class StubDevicePrompt : IDevicePrompt
{
public ScanDevice? PromptForDevice(List<ScanDevice> deviceList, IntPtr dialogParent)
{
return null;
}
}
public class StubScannedImagePrinter : IScannedImagePrinter
{
public Task<bool> PromptToPrint(IList<ProcessedImage> images, IList<ProcessedImage> selectedImages)
{
return Task.FromResult(false);
}
}
public class StubNotificationManager : INotificationManager
{
public void PdfSaved(string path)
{
}
public void ImagesSaved(int imageCount, string path)
{
}
public void DonatePrompt()
{
}
public void OperationProgress(OperationProgress opModalProgress, IOperation op)
{
}
public void UpdateAvailable(IUpdateChecker updateChecker, UpdateInfo update)
{
}
public void Rebuild()
{
}
}
public class StubPdfPasswordProvider : IPdfPasswordProvider
{
public bool ProvidePassword(string fileName, int attemptCount, out string password)
{
password = null!;
return false;
}
}

View File

@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6;net6-macos10.15</TargetFrameworks>
<!-- TODO: Add net6-macos10.15 target once Eto supports it for the latest dotnet -->
<TargetFrameworks>net6</TargetFrameworks>
<Nullable>enable</Nullable>
<GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources>
<RootNamespace>NAPS2</RootNamespace>
@ -15,7 +16,8 @@
<!-- <Import Project="..\NAPS2.Setup\LibUsers.targets" />-->
<ItemGroup>
<!-- <ProjectReference Include="..\NAPS2.Lib\NAPS2.Lib.csproj" />-->
<ProjectReference Include="..\NAPS2.Images.Mac\NAPS2.Images.Mac.csproj" />
<ProjectReference Include="..\NAPS2.Lib\NAPS2.Lib.csproj" />
<PackageReference Include="Eto.Forms" Version="2.7.0" />
<PackageReference Include="Eto.Platform.Mac64" Version="2.7.0" Condition="'$(TargetFramework)' == 'net6'" />
<PackageReference Include="Eto.Platform.XamMac2" Version="2.7.0" Condition="'$(TargetFramework)' == 'net6-macos10.15'" />

View File

@ -433,17 +433,22 @@ public abstract class DesktopForm : EtoFormBase
{
}
protected abstract void ConfigureToolbar();
protected virtual void ConfigureToolbar()
{
}
protected abstract void CreateToolbarButton(Command command);
protected virtual void CreateToolbarButton(Command command) => throw new InvalidOperationException();
protected abstract void CreateToolbarButtonWithMenu(Command command, MenuProvider menu);
protected virtual void CreateToolbarButtonWithMenu(Command command, MenuProvider menu) =>
throw new InvalidOperationException();
protected abstract void CreateToolbarMenu(Command command, MenuProvider menu);
protected virtual void CreateToolbarMenu(Command command, MenuProvider menu) =>
throw new InvalidOperationException();
protected abstract void CreateToolbarStackedButtons(Command command1, Command command2);
protected virtual void CreateToolbarStackedButtons(Command command1, Command command2) =>
throw new InvalidOperationException();
protected abstract void CreateToolbarSeparator();
protected virtual void CreateToolbarSeparator() => throw new InvalidOperationException();
protected virtual void SetContent(Control content)
{

View File

@ -62,6 +62,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NAPS2.Escl.Client", "NAPS2.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NAPS2.Escl.Tests", "NAPS2.Escl.Tests\NAPS2.Escl.Tests.csproj", "{ECD69481-CD4D-4EEC-A3BA-612DB29F13B3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NAPS2.App.Mac", "NAPS2.App.Mac\NAPS2.App.Mac.csproj", "{A280B315-3670-484D-B7A1-294E3DE56E7E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -338,6 +340,18 @@ Global
{ECD69481-CD4D-4EEC-A3BA-612DB29F13B3}.Standalone|Any CPU.Build.0 = Debug|Any CPU
{ECD69481-CD4D-4EEC-A3BA-612DB29F13B3}.Tools|Any CPU.ActiveCfg = Debug|Any CPU
{ECD69481-CD4D-4EEC-A3BA-612DB29F13B3}.Tools|Any CPU.Build.0 = Debug|Any CPU
{A280B315-3670-484D-B7A1-294E3DE56E7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A280B315-3670-484D-B7A1-294E3DE56E7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A280B315-3670-484D-B7A1-294E3DE56E7E}.DebugLang|Any CPU.ActiveCfg = Debug|Any CPU
{A280B315-3670-484D-B7A1-294E3DE56E7E}.DebugLang|Any CPU.Build.0 = Debug|Any CPU
{A280B315-3670-484D-B7A1-294E3DE56E7E}.InstallerEXE|Any CPU.ActiveCfg = Debug|Any CPU
{A280B315-3670-484D-B7A1-294E3DE56E7E}.InstallerEXE|Any CPU.Build.0 = Debug|Any CPU
{A280B315-3670-484D-B7A1-294E3DE56E7E}.InstallerMSI|Any CPU.ActiveCfg = Debug|Any CPU
{A280B315-3670-484D-B7A1-294E3DE56E7E}.InstallerMSI|Any CPU.Build.0 = Debug|Any CPU
{A280B315-3670-484D-B7A1-294E3DE56E7E}.Standalone|Any CPU.ActiveCfg = Debug|Any CPU
{A280B315-3670-484D-B7A1-294E3DE56E7E}.Standalone|Any CPU.Build.0 = Debug|Any CPU
{A280B315-3670-484D-B7A1-294E3DE56E7E}.Tools|Any CPU.ActiveCfg = Debug|Any CPU
{A280B315-3670-484D-B7A1-294E3DE56E7E}.Tools|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE