From 02e85af4af23ce4a156b6b7e8139083e39d7517c Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 31 Mar 2023 23:23:18 +0200 Subject: [PATCH] Fixed Windows input provider, Vulkan and logging --- .../DryIoc/ContainerExtensions.cs | 2 +- .../Providers/Input/WindowsInputProvider.cs | 47 +++++++++----- .../SkiaSharp/Vulkan/Win32VkContext.cs | 12 ++-- src/Artemis.UI/Artemis.UI.csproj | 1 - .../Screens/Debugger/DebugView.axaml | 1 - .../Debugger/Tabs/Logs/LogsDebugView.axaml | 18 +----- .../Debugger/Tabs/Logs/LogsDebugView.axaml.cs | 63 +++++++++++-------- .../Debugger/Tabs/Logs/LogsDebugViewModel.cs | 62 +++++++++--------- 8 files changed, 110 insertions(+), 96 deletions(-) diff --git a/src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs b/src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs index de6fb01df..337e21744 100644 --- a/src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs +++ b/src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs @@ -20,7 +20,7 @@ public static class UIContainerExtensions public static void RegisterProviders(this IContainer container) { container.Register(Reuse.Singleton); - // container.Register(Reuse.Singleton); + container.Register(Reuse.Singleton); container.Register(); container.Register(serviceKey: WindowsInputProvider.Id); container.Register(); diff --git a/src/Artemis.UI.Windows/Providers/Input/WindowsInputProvider.cs b/src/Artemis.UI.Windows/Providers/Input/WindowsInputProvider.cs index cd3004b69..b34201da2 100644 --- a/src/Artemis.UI.Windows/Providers/Input/WindowsInputProvider.cs +++ b/src/Artemis.UI.Windows/Providers/Input/WindowsInputProvider.cs @@ -5,7 +5,7 @@ using System.Timers; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Windows.Utilities; -using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Controls.Platform; using Avalonia.Platform; using Linearstar.Windows.RawInput; using Linearstar.Windows.RawInput.Native; @@ -15,35 +15,43 @@ namespace Artemis.UI.Windows.Providers.Input; public class WindowsInputProvider : InputProvider { + private const int GWL_WNDPROC = -4; private const int WM_INPUT = 0x00FF; + private readonly IWindowImpl _window; + private readonly nint _hWndProcHook; + private readonly WndProc? _fnWndProcHook; private readonly IInputService _inputService; private readonly ILogger _logger; private readonly Timer _taskManagerTimer; + private int _lastProcessId; + delegate nint WndProc(nint hWnd, uint msg, nint wParam, nint lParam); + + private nint CustomWndProc(nint hWnd, uint msg, nint wParam, nint lParam) + { + OnWndProcCalled(hWnd, msg, wParam, lParam); + return CallWindowProc(_hWndProcHook, hWnd, msg, wParam, lParam); + } public WindowsInputProvider(ILogger logger, IInputService inputService) { _logger = logger; _inputService = inputService; - + _taskManagerTimer = new Timer(500); _taskManagerTimer.Elapsed += TaskManagerTimerOnElapsed; _taskManagerTimer.Start(); - // if (Avalonia.Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - // { - // IWindowImpl window = desktop.MainWindow!.PlatformImpl!; - // - // // https://github.com/sanjay900/guitar-configurator/blob/master/Notify/WindowsDeviceNotifierAvalonia.cs - // // _hWndProcHook = GetWindowLongPtr(window.Handle.Handle, GwlWndproc); - // // _fnWndProcHook = CustomWndProc; - // // var newLong = Marshal.GetFunctionPointerForDelegate(_fnWndProcHook); - // // SetWindowLongPtr(window.Handle.Handle, GwlWndproc, newLong); - // - // RawInputDevice.RegisterDevice(HidUsageAndPage.Keyboard, RawInputDeviceFlags.InputSink, window.Handle.Handle); - // RawInputDevice.RegisterDevice(HidUsageAndPage.Mouse, RawInputDeviceFlags.InputSink, window.Handle.Handle); - // } + _window = PlatformManager.CreateWindow(); + + _hWndProcHook = GetWindowLongPtr(_window.Handle.Handle, GWL_WNDPROC); + _fnWndProcHook = CustomWndProc; + nint newLong = Marshal.GetFunctionPointerForDelegate(_fnWndProcHook); + SetWindowLongPtr(_window.Handle.Handle, GWL_WNDPROC, newLong); + + RawInputDevice.RegisterDevice(HidUsageAndPage.Keyboard, RawInputDeviceFlags.InputSink, _window.Handle.Handle); + RawInputDevice.RegisterDevice(HidUsageAndPage.Mouse, RawInputDeviceFlags.InputSink, _window.Handle.Handle); } public static Guid Id { get; } = new("6737b204-ffb1-4cd9-8776-9fb851db303a"); @@ -256,6 +264,15 @@ public class WindowsInputProvider : InputProvider #region Native + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + static extern IntPtr CallWindowProc(nint lpPrevWndFunc, IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr", CharSet = CharSet.Unicode)] + private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", CharSet = CharSet.Unicode)] + private static extern IntPtr SetWindowLongPtr(nint hWnd, int nIndex, IntPtr dwNewLong); + [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool GetCursorPos(ref Win32Point pt); diff --git a/src/Artemis.UI.Windows/SkiaSharp/Vulkan/Win32VkContext.cs b/src/Artemis.UI.Windows/SkiaSharp/Vulkan/Win32VkContext.cs index 01a2f92d2..7ebb48a16 100644 --- a/src/Artemis.UI.Windows/SkiaSharp/Vulkan/Win32VkContext.cs +++ b/src/Artemis.UI.Windows/SkiaSharp/Vulkan/Win32VkContext.cs @@ -1,7 +1,7 @@ using System; using System.Linq; -using Avalonia.Controls; -using Avalonia.Win32; +using Avalonia.Controls.Platform; +using Avalonia.Platform; using SharpVk; using SharpVk.Khronos; @@ -11,10 +11,10 @@ internal sealed class Win32VkContext : VkContext { public Win32VkContext() { - Window = new Window(); + Window = PlatformManager.CreateWindow(); Instance = Instance.Create(null, new[] {"VK_KHR_surface", "VK_KHR_win32_surface"}); PhysicalDevice = Instance.EnumeratePhysicalDevices().First(); - Surface = Instance.CreateWin32Surface(Kernel32.CurrentModuleHandle, Window.PlatformImpl!.Handle.Handle); + Surface = Instance.CreateWin32Surface(Kernel32.CurrentModuleHandle, Window.Handle.Handle); (GraphicsFamily, PresentFamily) = FindQueueFamilies(); @@ -44,12 +44,12 @@ internal sealed class Win32VkContext : VkContext }; } - public Window Window { get; } + public IWindowImpl Window { get; } public override void Dispose() { base.Dispose(); - Window.Close(); + Window.Dispose(); } private IntPtr Proc(string name, IntPtr instanceHandle, IntPtr deviceHandle) diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index cffb4d57c..f1fc2ee93 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -16,7 +16,6 @@ - diff --git a/src/Artemis.UI/Screens/Debugger/DebugView.axaml b/src/Artemis.UI/Screens/Debugger/DebugView.axaml index 2bc369cab..26097641e 100644 --- a/src/Artemis.UI/Screens/Debugger/DebugView.axaml +++ b/src/Artemis.UI/Screens/Debugger/DebugView.axaml @@ -16,7 +16,6 @@ Height="800"> - diff --git a/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugView.axaml b/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugView.axaml index bffe6621c..5a5c6d457 100644 --- a/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugView.axaml +++ b/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugView.axaml @@ -8,19 +8,7 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.Debugger.Logs.LogsDebugView" x:DataType="logs:LogsDebugViewModel"> - - - - - - + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugView.axaml.cs b/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugView.axaml.cs index d6cf4bda3..219215877 100644 --- a/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugView.axaml.cs +++ b/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugView.axaml.cs @@ -1,7 +1,10 @@ using System; +using Avalonia.Controls; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; using Avalonia.Threading; +using ReactiveUI; +using Serilog; namespace Artemis.UI.Screens.Debugger.Logs; @@ -19,36 +22,42 @@ public partial class LogsDebugView : ReactiveUserControl protected override void OnInitialized() { base.OnInitialized(); - Dispatcher.UIThread.Post(() => LogTextEditor.ScrollToEnd(), DispatcherPriority.ApplicationIdle); + Dispatcher.UIThread.Post(() => LogsScrollViewer.ScrollToEnd(), DispatcherPriority.ApplicationIdle); } - private void OnTextChanged(object? sender, EventArgs e) + // private void OnTextChanged(object? sender, EventArgs e) + // { + // if (LogTextEditor.ExtentHeight == 0) + // return; + // + // int linesAdded = LogTextEditor.LineCount - _lineCount; + // double lineHeight = LogTextEditor.ExtentHeight / LogTextEditor.LineCount; + // double outOfScreenTextHeight = LogTextEditor.ExtentHeight - LogTextEditor.VerticalOffset - LogTextEditor.ViewportHeight; + // double outOfScreenLines = outOfScreenTextHeight / lineHeight; + // + // //we need this help distance because of rounding. + // //if we scroll slightly above the end, we still want it + // //to scroll down to the new lines. + // const double GRACE_DISTANCE = 1d; + // + // //if we were at the bottom of the log and + // //if the last log event was 5 lines long + // //we will be 5 lines out sync. + // //if this is the case, scroll down. + // + // //if we are more than that out of sync, + // //the user scrolled up and we should not + // //mess with anything. + // if (_lineCount == 0 || linesAdded + GRACE_DISTANCE > outOfScreenLines) + // { + // Dispatcher.UIThread.Post(() => LogTextEditor.ScrollToEnd(), DispatcherPriority.ApplicationIdle); + // _lineCount = LogTextEditor.LineCount; + // } + // } + private void Control_OnSizeChanged(object? sender, SizeChangedEventArgs e) { - if (LogTextEditor.ExtentHeight == 0) + if (!(LogsScrollViewer.Extent.Height - LogsScrollViewer.Offset.Y - LogsScrollViewer.Bounds.Bottom <= 60)) return; - - int linesAdded = LogTextEditor.LineCount - _lineCount; - double lineHeight = LogTextEditor.ExtentHeight / LogTextEditor.LineCount; - double outOfScreenTextHeight = LogTextEditor.ExtentHeight - LogTextEditor.VerticalOffset - LogTextEditor.ViewportHeight; - double outOfScreenLines = outOfScreenTextHeight / lineHeight; - - //we need this help distance because of rounding. - //if we scroll slightly above the end, we still want it - //to scroll down to the new lines. - const double GRACE_DISTANCE = 1d; - - //if we were at the bottom of the log and - //if the last log event was 5 lines long - //we will be 5 lines out sync. - //if this is the case, scroll down. - - //if we are more than that out of sync, - //the user scrolled up and we should not - //mess with anything. - if (_lineCount == 0 || linesAdded + GRACE_DISTANCE > outOfScreenLines) - { - Dispatcher.UIThread.Post(() => LogTextEditor.ScrollToEnd(), DispatcherPriority.ApplicationIdle); - _lineCount = LogTextEditor.LineCount; - } + Dispatcher.UIThread.Post(() => LogsScrollViewer.ScrollToEnd(), DispatcherPriority.Normal); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugViewModel.cs b/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugViewModel.cs index c1860b01a..4df1be54e 100644 --- a/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugViewModel.cs @@ -1,12 +1,15 @@ -using Artemis.Core; +using System; +using System.Collections.ObjectModel; +using Artemis.Core; using Artemis.UI.Shared; using Avalonia.Threading; -using AvaloniaEdit.Document; using ReactiveUI; using Serilog.Events; using Serilog.Formatting.Display; using System.IO; using System.Reactive.Disposables; +using Avalonia.Controls.Documents; +using Avalonia.Media; namespace Artemis.UI.Screens.Debugger.Logs; @@ -14,38 +17,32 @@ public class LogsDebugViewModel : ActivatableViewModelBase { private readonly MessageTemplateTextFormatter _formatter; - public TextDocument Document { get; } + public InlineCollection Lines { get; } = new InlineCollection(); private const int MAX_ENTRIES = 1000; - + public LogsDebugViewModel() { DisplayName = "Logs"; - Document = new TextDocument(); + _formatter = new MessageTemplateTextFormatter( "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}" ); - foreach(LogEvent logEvent in LogStore.Events) + foreach (LogEvent logEvent in LogStore.Events) AddLogEvent(logEvent); - + this.WhenActivated(disp => - { + { LogStore.EventAdded += OnLogEventAdded; - Disposable.Create(() => - { - LogStore.EventAdded -= OnLogEventAdded; - }).DisposeWith(disp); + Disposable.Create(() => { LogStore.EventAdded -= OnLogEventAdded; }).DisposeWith(disp); }); } private void OnLogEventAdded(object? sender, LogEventEventArgs e) { - Dispatcher.UIThread.Post(() => - { - AddLogEvent(e.LogEvent); - }); + Dispatcher.UIThread.Post(() => { AddLogEvent(e.LogEvent); }); } private void AddLogEvent(LogEvent? logEvent) @@ -56,22 +53,27 @@ public class LogsDebugViewModel : ActivatableViewModelBase using StringWriter writer = new(); _formatter.Format(logEvent, writer); string line = writer.ToString(); - Document.Insert(Document.TextLength, '\n' + line.TrimEnd('\r', '\n')); - while (Document.LineCount > MAX_ENTRIES) - RemoveOldestLine(); + + + Lines.Add(new Run(line.TrimEnd('\r', '\n') + '\n') + { + Foreground = logEvent.Level switch + { + LogEventLevel.Verbose => new SolidColorBrush(Colors.White), + LogEventLevel.Debug => new SolidColorBrush(Color.FromRgb(216, 216, 216)), + LogEventLevel.Information => new SolidColorBrush(Color.FromRgb(93, 201, 255)), + LogEventLevel.Warning => new SolidColorBrush(Color.FromRgb(255, 177, 53)), + LogEventLevel.Error => new SolidColorBrush(Color.FromRgb(255, 63, 63)), + LogEventLevel.Fatal => new SolidColorBrush(Colors.Red), + _ => throw new ArgumentOutOfRangeException() + } + }); + LimitLines(); } - private void RemoveOldestLine() + private void LimitLines() { - int firstNewLine = Document.IndexOf('\n', 0, Document.TextLength); - if (firstNewLine == -1) - { - //this should never happen. - //just in case let's return - //instead of throwing - return; - } - - Document.Remove(0, firstNewLine + 1); + if (Lines.Count > MAX_ENTRIES) + Lines.RemoveRange(0, Lines.Count - MAX_ENTRIES); } } \ No newline at end of file