using System.Runtime.InteropServices; using ImageCatalog; using ImageCatalog_2.Services; using MaddoShared; using Microsoft.Extensions.DependencyInjection; using AutoMapper; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Console; using System.IO; using Microsoft.Extensions.Options; namespace ImageCatalog_2; static class Program { [DllImport("kernel32.dll", SetLastError = true)] private static extern bool AllocConsole(); [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr GetStdHandle(int nStdHandle); private const int STD_OUTPUT_HANDLE = -11; private const int STD_ERROR_HANDLE = -12; [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetStdHandle(int nStdHandle, IntPtr handle); [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr GetConsoleWindow(); [DllImport("kernel32.dll", SetLastError = true)] static extern bool AttachConsole(int dwProcessId); [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr CreateFile( string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile); private const uint GENERIC_WRITE = 0x40000000; private const uint OPEN_EXISTING = 3; private static void RedirectConsoleOutput() { var stdOutHandle = CreateFile("CONOUT$", GENERIC_WRITE, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero); var safeFileHandle = new Microsoft.Win32.SafeHandles.SafeFileHandle(stdOutHandle, true); var fileStream = new FileStream(safeFileHandle, FileAccess.Write); var standardOutput = new StreamWriter(fileStream) { AutoFlush = true }; Console.SetOut(standardOutput); Console.SetError(standardOutput); } public static IServiceProvider ServiceProvider { get; private set; } [STAThread] static void Main(string[] args) { Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); AllocConsole(); RedirectConsoleOutput(); var serviceCollection = new ServiceCollection(); ConfigureServices(serviceCollection); ServiceProvider = serviceCollection.BuildServiceProvider(); // Resolve WPF MainWindow when available, otherwise fall back to WinForms MainForm var serviceProvider = ServiceProvider; // Determine UI based on command line. Default: WinForms. Use --wpf to explicitly request WPF. bool useWpf = args is not null && Array.Exists(args, a => string.Equals(a, "--wpf", StringComparison.OrdinalIgnoreCase)); if (useWpf) { // Create the WPF Application and merge MahApps resources BEFORE constructing the MainWindow // so InitializeComponent sees the theme resources on first render. var wpfApp = new System.Windows.Application(); try { wpfApp.Resources.MergedDictionaries.Add(new System.Windows.ResourceDictionary { Source = new Uri("pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml") }); wpfApp.Resources.MergedDictionaries.Add(new System.Windows.ResourceDictionary { Source = new Uri("pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml") }); // Default Light theme (can be replaced at runtime) wpfApp.Resources.MergedDictionaries.Add(new System.Windows.ResourceDictionary { Source = new Uri("pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml") }); // Also notify ThemeManager about initial theme so chrome and MahApps brushes are applied try { ControlzEx.Theming.ThemeManager.Current.ChangeTheme(wpfApp, "Light.Blue"); } catch { // ignore if ThemeManager API isn't present } } catch { // If resources fail to load (package not present at runtime), continue silently } // Now resolve the WPF MainWindow so its constructor runs with the application resources available var wpfMain = serviceProvider.GetService(typeof(ImageCatalog_2.MainWindow)) as ImageCatalog_2.MainWindow; if (wpfMain is not null) { wpfApp.Run(wpfMain); return; } // If WPF was requested but not available, fall back to WinForms. } // Default / fallback to WinForms UI var mainForm = serviceProvider.GetRequiredService(); System.Windows.Forms.Application.Run(mainForm); } private static void ConfigureServices(ServiceCollection services) { // Register AutoMapper (new AddAutoMapper overload — provide config and marker types) services.AddAutoMapper(cfg => { }, typeof(Program)); // Register your services here services.AddTransient(); services.AddTransient(); services.AddTransient(sp => { // Resolve optional version provider and pass to DataModel var testService = sp.GetRequiredService(); var settingsService = sp.GetRequiredService(); var imageCreation = sp.GetRequiredService(); var picSettings = sp.GetRequiredService(); var mapper = sp.GetRequiredService(); var logger = sp.GetRequiredService>(); var versionProvider = sp.GetService(); return new DataModel(testService, settingsService, imageCreation, picSettings, mapper, logger, versionProvider); }); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); // Register IImageCreator to be resolved via ImageCreatorMapper which selects concrete implementation at call time services.AddTransient(sp => sp.GetRequiredService()); // Register a ParametriSetup singleton that persists user preferences in LocalApplicationData var userPrefsPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "ImageCatalog", "userprefs.xml"); services.AddSingleton(new ParametriSetup(userPrefsPath)); services.AddSingleton(); // Register your forms services.AddTransient(); // Register WPF MainWindow so it can be resolved with the existing DataModel services.AddTransient(); // Version provider for UI and logging services.AddSingleton(); services.AddLogging(configure => { configure.AddCustomFormatter(); configure.AddConsole(); configure.SetMinimumLevel(LogLevel.Debug); }); } } public static class ConsoleLoggerExtensions { public static ILoggingBuilder AddCustomFormatter( this ILoggingBuilder builder) => builder .AddConsole(options => options.FormatterName = nameof(CustomLoggingFormatter)) .AddConsoleFormatter() .AddFilter("LuckyPennySoftware.AutoMapper.License", LogLevel.None); } public sealed class CustomLoggingFormatter : ConsoleFormatter, IDisposable { private readonly IDisposable _optionsReloadToken; private ConsoleFormatterOptions _formatterOptions; public CustomLoggingFormatter(IOptionsMonitor options) // Case insensitive : base(nameof(CustomLoggingFormatter)) => (_optionsReloadToken, _formatterOptions) = (options.OnChange(ReloadLoggerOptions), options.CurrentValue); private void ReloadLoggerOptions(ConsoleFormatterOptions options) => _formatterOptions = options; public override void Write( in LogEntry logEntry, IExternalScopeProvider? scopeProvider, TextWriter? textWriter) { string? message = logEntry.Formatter?.Invoke( logEntry.State, logEntry.Exception); if (message is null) { return; } textWriter.WriteLine($"{message}"); } public void Dispose() => _optionsReloadToken?.Dispose(); }