diff --git a/KeyStats.Windows/KeyStats/Helpers/KeyNameMapper.cs b/KeyStats.Windows/KeyStats/Helpers/KeyNameMapper.cs index dc48de6..84c4fa1 100644 --- a/KeyStats.Windows/KeyStats/Helpers/KeyNameMapper.cs +++ b/KeyStats.Windows/KeyStats/Helpers/KeyNameMapper.cs @@ -6,6 +6,8 @@ namespace KeyStats.Helpers; public static class KeyNameMapper { + private const uint ExtendedKeyFlag = 0x01; + private static readonly Dictionary VirtualKeyNames = new() { // Function keys @@ -65,9 +67,9 @@ public static class KeyNameMapper 0x10, 0x11, 0x12, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x5B, 0x5C }; - public static string GetKeyName(int vkCode) + public static string GetKeyName(int vkCode, uint scanCode = 0, uint flags = 0) { - var baseName = GetBaseKeyName(vkCode); + var baseName = GetBaseKeyName(vkCode, scanCode, flags); var modifiers = GetModifierNames(vkCode); if (modifiers.Count == 0) @@ -78,8 +80,18 @@ public static string GetKeyName(int vkCode) return string.Join("+", modifiers) + "+" + baseName; } - private static string GetBaseKeyName(int vkCode) + private static string GetBaseKeyName(int vkCode, uint scanCode, uint flags) { + if (vkCode == 0x0D && (flags & ExtendedKeyFlag) != 0) + { + return "NumEnter"; + } + + if (GetNumLockOffNumpadKeyName(vkCode, scanCode, flags) is string numpadKeyName) + { + return numpadKeyName; + } + if (VirtualKeyNames.TryGetValue(vkCode, out var name)) { return name; @@ -114,11 +126,18 @@ private static string GetBaseKeyName(int vkCode) } // Try to get the key name using Windows API - var scanCode = NativeInterop.MapVirtualKey((uint)vkCode, NativeInterop.MAPVK_VK_TO_VSC); - if (scanCode > 0) + var effectiveScanCode = scanCode != 0 + ? scanCode + : NativeInterop.MapVirtualKey((uint)vkCode, NativeInterop.MAPVK_VK_TO_VSC); + if (effectiveScanCode > 0) { var keyNameBuffer = new char[32]; - var lParam = (int)(scanCode << 16); + var lParam = (int)((effectiveScanCode & 0xFF) << 16); + if ((flags & ExtendedKeyFlag) != 0) + { + lParam |= 1 << 24; + } + var result = NativeInterop.GetKeyNameText(lParam, keyNameBuffer, keyNameBuffer.Length); if (result > 0) { @@ -129,6 +148,30 @@ private static string GetBaseKeyName(int vkCode) return $"Key{vkCode}"; } + private static string? GetNumLockOffNumpadKeyName(int vkCode, uint scanCode, uint flags) + { + if ((flags & ExtendedKeyFlag) != 0) + { + return null; + } + + return (vkCode, scanCode) switch + { + (0x2D, 0x52u) => "Num0", + (0x23, 0x4Fu) => "Num1", + (0x28, 0x50u) => "Num2", + (0x22, 0x51u) => "Num3", + (0x25, 0x4Bu) => "Num4", + (0x0C, 0x4Cu) => "Num5", + (0x27, 0x4Du) => "Num6", + (0x24, 0x47u) => "Num7", + (0x26, 0x48u) => "Num8", + (0x21, 0x49u) => "Num9", + (0x2E, 0x53u) => "Num.", + _ => null + }; + } + private static List GetModifierNames(int vkCode) { var modifiers = new List(); diff --git a/KeyStats.Windows/KeyStats/Services/InputMonitorService.cs b/KeyStats.Windows/KeyStats/Services/InputMonitorService.cs index 87feacc..bdff587 100644 --- a/KeyStats.Windows/KeyStats/Services/InputMonitorService.cs +++ b/KeyStats.Windows/KeyStats/Services/InputMonitorService.cs @@ -269,7 +269,7 @@ private IntPtr KeyboardHookCallback(int nCode, IntPtr wParam, IntPtr lParam) if (isNewKey) { - var keyName = KeyNameMapper.GetKeyName(vkCode); + var keyName = KeyNameMapper.GetKeyName(vkCode, hookStruct.scanCode, hookStruct.flags); var hWnd = NativeInterop.GetForegroundWindow(); NativeInterop.GetWindowThreadProcessId(hWnd, out uint pid); ThreadPool.QueueUserWorkItem(_ => diff --git a/KeyStats.Windows/KeyStats/Services/StatsManager.cs b/KeyStats.Windows/KeyStats/Services/StatsManager.cs index b61a481..5ad68fe 100644 --- a/KeyStats.Windows/KeyStats/Services/StatsManager.cs +++ b/KeyStats.Windows/KeyStats/Services/StatsManager.cs @@ -1304,6 +1304,17 @@ private static Dictionary AggregateKeyboardHeatmapCounts(Dictionary } var rawKey = kvp.Key ?? string.Empty; + if (NormalizeKeyboardHeatmapKey(rawKey) is string exactKey) + { + aggregated[exactKey] = SafeAdd(aggregated.TryGetValue(exactKey, out var current) ? current : 0, count); + continue; + } + + if (rawKey.IndexOf("Num+", StringComparison.OrdinalIgnoreCase) >= 0) + { + aggregated["Num+"] = SafeAdd(aggregated.TryGetValue("Num+", out var current) ? current : 0, count); + } + var components = rawKey .Split(new[] { '+' }, StringSplitOptions.RemoveEmptyEntries) .Select(part => part.Trim()) @@ -1353,24 +1364,32 @@ private static Dictionary AggregateKeyboardHeatmapCounts(Dictionary var suffix = upper.Substring(3); if (suffix.Length == 1 && char.IsDigit(suffix[0])) { - return suffix; + return "Num" + suffix; } if (suffix == ".") { - return "."; + return "Num."; } if (suffix == "+") { - return "="; + return "Num+"; } if (suffix == "-") { - return "-"; + return "Num-"; } if (suffix == "/") { - return "/"; + return "Num/"; + } + if (suffix == "*") + { + return "Num*"; + } + if (suffix == "ENTER") + { + return "NumEnter"; } } @@ -1460,6 +1479,8 @@ private static Dictionary AggregateKeyboardHeatmapCounts(Dictionary case "SCROLLLOCK": case "SCROLL": return "ScrollLock"; + case "NUMLOCK": + return "NumLock"; case "PAUSE": case "BREAK": return "Pause"; @@ -1480,7 +1501,7 @@ private static Dictionary AggregateKeyboardHeatmapCounts(Dictionary case "DOWNARROW": return "Down"; default: - return trimmed; + return null; } } diff --git a/KeyStats.Windows/KeyStats/Views/Controls/KeyboardHeatmapControl.cs b/KeyStats.Windows/KeyStats/Views/Controls/KeyboardHeatmapControl.cs index d8e863b..62a5c0f 100644 --- a/KeyStats.Windows/KeyStats/Views/Controls/KeyboardHeatmapControl.cs +++ b/KeyStats.Windows/KeyStats/Views/Controls/KeyboardHeatmapControl.cs @@ -37,7 +37,6 @@ public RenderMetrics(double scale, Point origin) } private const double LayoutInset = 8; - private const double MaximumKeyboardScale = 58; private static readonly HashSet NumberKeyIds = new(Enumerable.Range(0, 10).Select(i => i.ToString(CultureInfo.InvariantCulture))); private static readonly IReadOnlyList Layout = BuildLayout(); @@ -131,7 +130,7 @@ private void OnThemeChanged() var scaleX = available.Width / LayoutBounds.Width; var scaleY = available.Height / LayoutBounds.Height; - var scale = Math.Min(Math.Min(scaleX, scaleY), MaximumKeyboardScale); + var scale = Math.Min(scaleX, scaleY); var width = LayoutBounds.Width * scale; var height = LayoutBounds.Height * scale; var origin = new Point( @@ -517,6 +516,10 @@ void AppendRow(double y, double startX, IEnumerable<(string id, string label, do var yNavTop = y1; var yNavBottom = y1 + 1.0 + navRowGap; + items.Add(new KeySpec("PrintScreen", "prt\nsc", new Rect(navStartX, yFunction, navKeyWidth, 1.0))); + items.Add(new KeySpec("ScrollLock", "scroll\nlock", new Rect(navStartX + navStepX, yFunction, navKeyWidth, 1.0))); + items.Add(new KeySpec("Pause", "pause", new Rect(navStartX + navStepX * 2, yFunction, navKeyWidth, 1.0))); + items.Add(new KeySpec("Insert", "ins", new Rect(navStartX, yNavTop, navKeyWidth, 1.0))); items.Add(new KeySpec("Home", "home", new Rect(navStartX + navStepX, yNavTop, navKeyWidth, 1.0))); items.Add(new KeySpec("PageUp", "pg up", new Rect(navStartX + navStepX * 2, yNavTop, navKeyWidth, 1.0))); @@ -534,6 +537,35 @@ void AppendRow(double y, double startX, IEnumerable<(string id, string label, do items.Add(new KeySpec("Down", "down", new Rect(arrowStartX + arrowStep, yArrowBottom, arrowWidth, 1.0))); items.Add(new KeySpec("Right", "right", new Rect(arrowStartX + arrowStep * 2, yArrowBottom, arrowWidth, 1.0))); + var numpadGap = 0.55; + var numStartX = navStartX + navStepX * 3 + numpadGap; + var numKeyWidth = 1.0; + var numStep = numKeyWidth + keyGap; + var tallKeyHeight = 2.0 + rowGap; + var wideKeyWidth = 2.0 + keyGap; + + items.Add(new KeySpec("NumLock", "num\nlock", new Rect(numStartX, y1, numKeyWidth, 1.0))); + items.Add(new KeySpec("Num/", "/", new Rect(numStartX + numStep, y1, numKeyWidth, 1.0))); + items.Add(new KeySpec("Num*", "*", new Rect(numStartX + numStep * 2, y1, numKeyWidth, 1.0))); + items.Add(new KeySpec("Num-", "-", new Rect(numStartX + numStep * 3, y1, numKeyWidth, 1.0))); + + items.Add(new KeySpec("Num7", "7", new Rect(numStartX, y2, numKeyWidth, 1.0))); + items.Add(new KeySpec("Num8", "8", new Rect(numStartX + numStep, y2, numKeyWidth, 1.0))); + items.Add(new KeySpec("Num9", "9", new Rect(numStartX + numStep * 2, y2, numKeyWidth, 1.0))); + items.Add(new KeySpec("Num+", "+", new Rect(numStartX + numStep * 3, y2, numKeyWidth, tallKeyHeight))); + + items.Add(new KeySpec("Num4", "4", new Rect(numStartX, y3, numKeyWidth, 1.0))); + items.Add(new KeySpec("Num5", "5", new Rect(numStartX + numStep, y3, numKeyWidth, 1.0))); + items.Add(new KeySpec("Num6", "6", new Rect(numStartX + numStep * 2, y3, numKeyWidth, 1.0))); + + items.Add(new KeySpec("Num1", "1", new Rect(numStartX, y4, numKeyWidth, 1.0))); + items.Add(new KeySpec("Num2", "2", new Rect(numStartX + numStep, y4, numKeyWidth, 1.0))); + items.Add(new KeySpec("Num3", "3", new Rect(numStartX + numStep * 2, y4, numKeyWidth, 1.0))); + items.Add(new KeySpec("NumEnter", "enter", new Rect(numStartX + numStep * 3, y4, numKeyWidth, tallKeyHeight))); + + items.Add(new KeySpec("Num0", "0", new Rect(numStartX, y5, wideKeyWidth, 1.0))); + items.Add(new KeySpec("Num.", ".", new Rect(numStartX + numStep * 2, y5, numKeyWidth, 1.0))); + return items; } } diff --git a/KeyStats.Windows/KeyStats/Views/KeyboardHeatmapWindow.xaml b/KeyStats.Windows/KeyStats/Views/KeyboardHeatmapWindow.xaml index e2fdfc2..f225bd1 100644 --- a/KeyStats.Windows/KeyStats/Views/KeyboardHeatmapWindow.xaml +++ b/KeyStats.Windows/KeyStats/Views/KeyboardHeatmapWindow.xaml @@ -4,12 +4,11 @@ xmlns:controls="clr-namespace:KeyStats.Views.Controls" xmlns:p="clr-namespace:KeyStats.Properties" Title="{x:Static p:Strings.Heatmap_WindowTitle}" - Width="860" + Width="1080" Height="460" MinWidth="860" MinHeight="460" - MaxWidth="860" - MaxHeight="460" + ResizeMode="CanResize" WindowStartupLocation="CenterScreen" Background="{DynamicResource WindowSurfaceBrush}" ShowInTaskbar="True"> @@ -221,7 +220,7 @@ PopupAnimation="Fade" Closed="DatePickerPopup_Closed">