Add minidump generation, more error handling and logging

pull/7/head
LeapwardKoex 2024-02-13 22:40:12 +13:00
parent 6f364833a8
commit 31f97060b8
8 changed files with 266 additions and 89 deletions

View File

@ -1,5 +1,5 @@
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Windows;
namespace WpfMaiTouchEmulator;
@ -8,5 +8,38 @@ namespace WpfMaiTouchEmulator;
/// </summary>
public partial class App : Application
{
public App()
{
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
DispatcherUnhandledException += App_DispatcherUnhandledException;
Logger.CleanupOldLogFiles();
}
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
CreateDump(e.Exception);
e.Handled = true;
}
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
CreateDump(e.ExceptionObject as Exception);
}
private void CreateDump(Exception exception)
{
var dumpFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "CrashDump.dmp");
using (var fs = new FileStream(dumpFilePath, FileMode.Create))
{
var process = Process.GetCurrentProcess();
DumpCreator.MiniDumpWriteDump(process.Handle, (uint)process.Id, fs.SafeFileHandle, DumpCreator.Typ.MiniDumpNormal, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
}
Logger.Fatal("App encountered a fatal exception", exception);
MessageBox.Show($"A uncaught exception was thrown: {exception.Message}. \n\n {exception.StackTrace}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
Current.Shutdown(1);
}
}

20
DumpCreator.cs 100644
View File

@ -0,0 +1,20 @@
using System;
using System.Runtime.InteropServices;
using System.IO;
using System.Diagnostics;
class DumpCreator
{
[Flags]
public enum Typ : uint
{
// Add the MiniDump flags you need, for example:
MiniDumpNormal = 0x00000000,
MiniDumpWithDataSegs = 0x00000001,
// etc.
}
[DllImport("DbgHelp.dll")]
public static extern bool MiniDumpWriteDump(IntPtr hProcess, uint ProcessId, SafeHandle hFile, Typ DumpType,
IntPtr ExceptionParam, IntPtr UserStreamParam, IntPtr CallbackParam);
}

91
Logger.cs 100644
View File

@ -0,0 +1,91 @@
using System;
using System.IO;
using System.Text;
public static class Logger
{
private static readonly object lockObj = new();
private static string? logFilePath;
private static string GetLogFilePath()
{
if (logFilePath == null)
{
var fileName = $"app_{DateTime.Now:yyyy-MM-dd}.log";
logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
}
return logFilePath;
}
public static void CleanupOldLogFiles()
{
var directory = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
var oldFiles = directory.GetFiles("app_*.log")
.Where(f => f.CreationTime < DateTime.Now.AddDays(-7))
.ToList();
foreach (var file in oldFiles)
{
try
{
file.Delete();
}
catch (Exception ex)
{
Error("Failed to delete log file", ex);
}
}
}
public static void Info(string message)
{
Log("INFO", message);
}
public static void Warn(string message)
{
Log("WARN", message);
}
public static void Error(string message, Exception? ex = null)
{
var logMessage = new StringBuilder(message);
if (ex != null)
{
logMessage.AppendLine(); // Ensure the exception starts on a new line
logMessage.AppendLine($"Exception: {ex.Message}");
logMessage.AppendLine($"StackTrace: {ex.StackTrace}");
}
Log("ERROR", logMessage.ToString());
}
public static void Fatal(string message, Exception? ex = null)
{
var logMessage = new StringBuilder(message);
if (ex != null)
{
logMessage.AppendLine(); // Ensure the exception starts on a new line
logMessage.AppendLine($"Exception: {ex.Message}");
logMessage.AppendLine($"StackTrace: {ex.StackTrace}");
}
Log("FATAL", logMessage.ToString());
}
private static void Log(string level, string message)
{
try
{
lock (lockObj)
{
using var sw = new StreamWriter(GetLogFilePath(), true, Encoding.UTF8);
sw.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{level}] {message}");
}
}
catch
{
}
}
}

View File

@ -2,54 +2,52 @@
using System.Windows;
namespace WpfMaiTouchEmulator;
internal class MaiTouchComConnector
internal class MaiTouchComConnector(MaiTouchSensorButtonStateManager buttonState)
{
private static SerialPort? serialPort;
private bool isActiveMode;
private bool _connected;
private bool _shouldReconnect = true;
private readonly MaiTouchSensorButtonStateManager _buttonState;
private readonly MaiTouchSensorButtonStateManager _buttonState = buttonState;
public Action<string> OnConnectStatusChange
public Action<string>? OnConnectStatusChange
{
get;
internal set;
}
public Action OnConnectError
public Action? OnConnectError
{
get;
internal set;
}
public Action<string> OnDataSent
public Action<string>? OnDataSent
{
get;
internal set;
}
public Action<string> OnDataRecieved
public Action<string>? OnDataRecieved
{
get;
internal set;
}
public MaiTouchComConnector(MaiTouchSensorButtonStateManager buttonState)
{
_buttonState = buttonState;
}
public async Task StartTouchSensorPolling()
{
if (!_connected && _shouldReconnect)
{
Logger.Info("Trying to connect to COM port...");
var virtualPort = "COM23"; // Adjust as needed
try
{
OnConnectStatusChange("Conecting...");
serialPort = new SerialPort(virtualPort, 9600, Parity.None, 8, StopBits.One);
serialPort.WriteTimeout = 100;
OnConnectStatusChange?.Invoke("Conecting...");
serialPort = new SerialPort(virtualPort, 9600, Parity.None, 8, StopBits.One)
{
WriteTimeout = 100
};
serialPort.DataReceived += SerialPort_DataReceived;
serialPort.Open();
Console.WriteLine("Serial port opened successfully.");
OnConnectStatusChange("Connected to port");
Logger.Info("Serial port opened successfully.");
OnConnectStatusChange?.Invoke("Connected to port");
_connected = true;
while (true)
@ -69,7 +67,7 @@ internal class MaiTouchComConnector
catch (TimeoutException) { }
catch (Exception ex)
{
OnConnectError();
OnConnectError?.Invoke();
Application.Current.Dispatcher.Invoke(() =>
{
MessageBox.Show(ex.Message, "Error connecting to COM port", MessageBoxButton.OK, MessageBoxImage.Error);
@ -78,10 +76,13 @@ internal class MaiTouchComConnector
}
finally
{
Logger.Info("Disconnecting from COM port");
_connected = false;
OnConnectStatusChange("Not Connected");
OnConnectStatusChange?.Invoke("Not Connected");
if (serialPort?.IsOpen == true)
{
serialPort.DiscardInBuffer();
serialPort.DiscardOutBuffer();
serialPort.Close();
}
}
@ -90,9 +91,12 @@ internal class MaiTouchComConnector
public async Task Disconnect()
{
Logger.Info("Disconnecting from COM port");
_shouldReconnect = false;
_connected = false;
try
{
if (serialPort != null)
{
serialPort.DtrEnable = false;
serialPort.RtsEnable = false;
@ -105,21 +109,26 @@ internal class MaiTouchComConnector
serialPort.Close();
}
}
}
catch (Exception ex)
{
Logger.Error("Error whilst disconnecting from COM port", ex);
MessageBox.Show(ex.Message);
}
}
void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
var recievedData = serialPort.ReadExisting();
var commands = recievedData.Split(new[] { '}' }, StringSplitOptions.RemoveEmptyEntries);
var recievedData = serialPort?.ReadExisting();
var commands = recievedData?.Split(new[] { '}' }, StringSplitOptions.RemoveEmptyEntries);
if (commands != null)
{
foreach (var command in commands)
{
var cleanedCommand = command.TrimStart('{');
Console.WriteLine($"Received data: {cleanedCommand}");
OnDataRecieved(cleanedCommand);
Logger.Info($"Received serial data: {cleanedCommand}");
OnDataRecieved?.Invoke(cleanedCommand);
if (cleanedCommand == "STAT")
{
@ -140,12 +149,13 @@ internal class MaiTouchComConnector
var ratio = cleanedCommand[3];
var newString = $"({leftOrRight}{sensor}{cleanedCommand[2]}{ratio})";
serialPort.Write(newString);
OnDataSent(newString);
serialPort?.Write(newString);
OnDataSent?.Invoke(newString);
}
else
{
Console.WriteLine(cleanedCommand);
Logger.Warn($"Unhandled serial data command {cleanedCommand}");
}
}
}
}

View File

@ -47,12 +47,14 @@ public partial class MainWindow : Window
if (Properties.Settings.Default.FirstOpen)
{
Logger.Info("First open occurred");
MessageBox.Show("Please remove any COM devices using the COM3 port before installing the virtual COM port. In Device Manager click \"View\" then enabled \"Show hidden devices\" and uninstall any devices that are using the COM3 port.\n\nAfter ensuring COM3 is free please use the install COM port button in the app to register the app.\n\nThe app needs to connect to the port prior to Sinmai.exe being opened.", "First time setup", MessageBoxButton.OK, MessageBoxImage.Information);
Properties.Settings.Default.FirstOpen = false;
Properties.Settings.Default.Save();
}
Loaded += (s, e) => {
Logger.Info("Main window loaded, creating touch panel");
_touchPanel = new TouchPanel();
_touchPanel.onTouch = (value) => { buttonState.PressButton(value); };
_touchPanel.onRelease = (value) => { buttonState.ReleaseButton(value); };
@ -90,6 +92,7 @@ public partial class MainWindow : Window
var processes = Process.GetProcessesByName("Sinmai");
if (processes.Length > 0)
{
Logger.Info("Found sinmai process to exit alongside with");
sinamiProcess = processes[0];
}
else
@ -98,10 +101,13 @@ public partial class MainWindow : Window
}
}
await sinamiProcess.WaitForExitAsync();
Logger.Info("Sinmai exited");
var dataContext = (MainWindowViewModel)DataContext;
if (dataContext.IsExitWithSinmaiEnabled)
{
Logger.Info("Disconnecting from COM port before shutting down");
await connector.Disconnect();
Logger.Info("Shutting down...");
Application.Current.Shutdown();
}
}
@ -178,6 +184,8 @@ public partial class MainWindow : Window
private async void buttonInstallComPort_Click(object sender, RoutedEventArgs e)
{
throw new Exception("Test exception for crash dump generation");
await comPortManager.InstallComPort();
}

View File

@ -11,10 +11,10 @@ namespace WpfMaiTouchEmulator;
/// </summary>
public partial class TouchPanel : Window
{
internal Action<TouchValue> onTouch;
internal Action<TouchValue> onRelease;
internal Action<TouchValue>? onTouch;
internal Action<TouchValue>? onRelease;
private readonly Dictionary<int, System.Windows.Controls.Image> activeTouches = [];
private readonly Dictionary<int, Image> activeTouches = [];
private readonly TouchPanelPositionManager _positionManager;
private List<Image> buttons = [];
@ -44,10 +44,10 @@ public partial class TouchPanel : Window
{
while (true)
{
if (activeTouches.Any() && !TouchesOver.Any())
if (activeTouches.Count != 0 && !TouchesOver.Any())
{
await Task.Delay(100);
if (activeTouches.Any() && !TouchesOver.Any())
if (activeTouches.Count != 0 && !TouchesOver.Any())
{
DeselectAllItems();
}
@ -64,8 +64,11 @@ public partial class TouchPanel : Window
public void PositionTouchPanel()
{
var position = _positionManager.GetSinMaiWindowPosition();
if (position != null)
if (position != null &&
(Top != position.Value.Top || Left != position.Value.Left || Width != position.Value.Width || Height != position.Value.Height)
)
{
Logger.Info("Touch panel not over sinmai window, repositioning");
Top = position.Value.Top;
Left = position.Value.Left;
Width = position.Value.Width;
@ -103,7 +106,7 @@ public partial class TouchPanel : Window
{
// Highlight the element and add it to the active touches tracking.
HighlightElement(element, true);
onTouch((TouchValue)element.Tag);
onTouch?.Invoke((TouchValue)element.Tag);
activeTouches[e.TouchDevice.Id] = element;
}
e.Handled = true;
@ -125,7 +128,7 @@ public partial class TouchPanel : Window
HighlightElement(previousElement, false);
Application.Current.Dispatcher.Invoke(() =>
{
onRelease((TouchValue)previousElement.Tag);
onRelease?.Invoke((TouchValue)previousElement.Tag);
});
});
@ -133,7 +136,7 @@ public partial class TouchPanel : Window
// Highlight the new element and update the tracking.
HighlightElement(newElement, true);
onTouch((TouchValue)newElement.Tag);
onTouch?.Invoke((TouchValue)newElement.Tag);
activeTouches[e.TouchDevice.Id] = newElement;
}
@ -146,29 +149,20 @@ public partial class TouchPanel : Window
if (activeTouches.TryGetValue(e.TouchDevice.Id, out var element))
{
HighlightElement(element, false);
onRelease((TouchValue)element.Tag);
onRelease?.Invoke((TouchValue)element.Tag);
activeTouches.Remove(e.TouchDevice.Id);
}
e.Handled = true;
}
private bool IsTouchInsideWindow(Point touchPoint)
{
// Define the window's bounds
var windowBounds = new Rect(0, 0, ActualWidth, ActualHeight);
// Check if the touch point is within the window's bounds
return windowBounds.Contains(touchPoint);
}
private void DeselectAllItems()
{
// Logic to deselect all items or the last touched item
foreach (var element in activeTouches.Values)
{
HighlightElement(element, false);
onRelease((TouchValue)element.Tag);
onRelease?.Invoke((TouchValue)element.Tag);
}
activeTouches.Clear();
}
@ -181,7 +175,7 @@ public partial class TouchPanel : Window
});
}
private void HighlightElement(System.Windows.Controls.Image element, bool highlight)
private void HighlightElement(Image element, bool highlight)
{
if (Properties.Settings.Default.IsDebugEnabled)
{

View File

@ -22,6 +22,8 @@ class TouchPanelPositionManager
}
public Rect? GetSinMaiWindowPosition()
{
try
{
var hWnd = FindWindow(null, "Sinmai");
if (hWnd != IntPtr.Zero)
@ -30,13 +32,19 @@ class TouchPanelPositionManager
if (GetWindowRect(hWnd, out rect))
{
// Calculate the desired size and position based on the other application's window
var width = Convert.ToInt32((rect.Right - rect.Left));
var width = rect.Right - rect.Left;
var height = width;
var left = rect.Left + ((rect.Right - rect.Left) - width) / 2; // Center horizontally
var top = rect.Bottom - height;
return new Rect(left, top, width, height);
}
}
}
catch (Exception ex)
{
Logger.Error("Failed top get sinmai window position", ex);
}
return null;
}
}

View File

@ -28,57 +28,69 @@ internal class VirtualComPortManager
public async Task InstallComPort()
{
Logger.Info("Trying to install virtual COM port.");
if (await CheckIfPortInstalled("COM3", false))
{
Logger.Warn("Port COM3 already registered.");
MessageBox.Show("Port COM3 already registered. Either remove it via Device Manager or uninstall the virutal port.");
return;
}
try
{
Logger.Info("Calling com0com to install virtual COM ports");
await ExecuteCommandAsync("setupc.exe", $"install PortName=COM3 PortName=COM23");
if (await CheckIfPortInstalled("COM3", true))
{
Logger.Info("Port COM3 successfully installed.");
MessageBox.Show("Port COM3 successfully installed.");
}
else
{
Logger.Error("Port COM3 failed to install");
MessageBox.Show($"Port COM3 failed to install", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
catch (Exception ex)
{
Logger.Error("Port COM3 failed to install", ex);
MessageBox.Show($"Port COM3 failed to install. {ex}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
public async Task UninstallVirtualPorts()
{
Logger.Info("Trying to uninstall virtual COM port.");
if (!await CheckIfPortInstalled("COM3", true))
{
Logger.Warn("Port COM3 not found. No need to uninstall.");
MessageBox.Show("Port COM3 not found. No need to uninstall.");
return;
}
try
{
Logger.Info("Calling com0com to uninstall virtual COM ports");
await ExecuteCommandAsync("setupc.exe", $"uninstall");
if (!await CheckIfPortInstalled("COM3", false))
{
Logger.Info("Port COM3 successfully uninstalled.");
MessageBox.Show("Port COM3 successfully uninstalled.");
}
else
{
Logger.Error("Port COM3 failed to uninstall");
MessageBox.Show($"Port COM3 failed to uninstall. It may be a real device, uninstall it from Device Manager", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
catch (Exception ex)
{
Logger.Error("Port COM3 failed to uninstall", ex);
MessageBox.Show($"Port COM3 failed to uninstall. {ex}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private async Task ExecuteCommandAsync(string command, string arguments)
{
Logger.Info($"Executing command {command} with arguments {arguments}");
var processStartInfo = new ProcessStartInfo
{
FileName = command,
@ -92,5 +104,6 @@ internal class VirtualComPortManager
process.Start();
await process.WaitForExitAsync();
Logger.Info($"Command {command} completed");
}
}