diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 97a6ad2879..1c15db229e 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -742,7 +742,7 @@ HKCC HKCR HKCU hkey -hkl +HKL HKLM HKPD HKU diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index 58c63a041d..fc83a9c766 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -117,6 +117,7 @@ "modules\\PowerAccent\\PowerToys.PowerAccent.dll", "modules\\PowerAccent\\PowerToys.PowerAccent.exe", "modules\\PowerAccent\\PowerToys.PowerAccentModuleInterface.dll", + "modules\\PowerAccent\\PowerToys.PowerAccentKeyboardService.dll", "modules\\PowerRename\\PowerToys.PowerRenameExt.dll", "modules\\PowerRename\\PowerToys.PowerRename.exe", diff --git a/PowerToys.sln b/PowerToys.sln index e45815efc7..831a4daa97 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -447,6 +447,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MeasureToolModuleInterface" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MeasureToolUI", "src\modules\MeasureTool\MeasureToolUI\MeasureToolUI.csproj", "{515554D1-D004-4F7F-A107-2211FC0F6B2C}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerAccentKeyboardService", "src\modules\poweraccent\PowerAccentKeyboardService\PowerAccentKeyboardService.vcxproj", "{C97D9A5D-206C-454E-997E-009E227D7F02}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -1790,6 +1792,18 @@ Global {515554D1-D004-4F7F-A107-2211FC0F6B2C}.Release|x86.ActiveCfg = Release|x86 {515554D1-D004-4F7F-A107-2211FC0F6B2C}.Release|x86.Build.0 = Release|x86 {515554D1-D004-4F7F-A107-2211FC0F6B2C}.Release|x86.Deploy.0 = Release|x86 + {C97D9A5D-206C-454E-997E-009E227D7F02}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {C97D9A5D-206C-454E-997E-009E227D7F02}.Debug|ARM64.Build.0 = Debug|ARM64 + {C97D9A5D-206C-454E-997E-009E227D7F02}.Debug|x64.ActiveCfg = Debug|x64 + {C97D9A5D-206C-454E-997E-009E227D7F02}.Debug|x64.Build.0 = Debug|x64 + {C97D9A5D-206C-454E-997E-009E227D7F02}.Debug|x86.ActiveCfg = Debug|x64 + {C97D9A5D-206C-454E-997E-009E227D7F02}.Debug|x86.Build.0 = Debug|x64 + {C97D9A5D-206C-454E-997E-009E227D7F02}.Release|ARM64.ActiveCfg = Release|ARM64 + {C97D9A5D-206C-454E-997E-009E227D7F02}.Release|ARM64.Build.0 = Release|ARM64 + {C97D9A5D-206C-454E-997E-009E227D7F02}.Release|x64.ActiveCfg = Release|x64 + {C97D9A5D-206C-454E-997E-009E227D7F02}.Release|x64.Build.0 = Release|x64 + {C97D9A5D-206C-454E-997E-009E227D7F02}.Release|x86.ActiveCfg = Release|x64 + {C97D9A5D-206C-454E-997E-009E227D7F02}.Release|x86.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1941,6 +1955,7 @@ Global {54A93AF7-60C7-4F6C-99D2-FBB1F75F853A} = {7AC943C9-52E8-44CF-9083-744D8049667B} {92C39820-9F84-4529-BC7D-22AAE514D63B} = {7AC943C9-52E8-44CF-9083-744D8049667B} {515554D1-D004-4F7F-A107-2211FC0F6B2C} = {7AC943C9-52E8-44CF-9083-744D8049667B} + {C97D9A5D-206C-454E-997E-009E227D7F02} = {0F14491C-6369-4C45-AAA8-135814E66E6B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index 3e7a208f7b..e00e656393 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -125,7 +125,7 @@ - + - - - net6.0-windows - win-x64;win-arm64 - enable - disable - True - + + + net6.0-windows10.0.19041.0 + win-x64;win-arm64 + enable + disable + True + true + + + PowerToys.PowerAccentKeyboardService + $(OutDir) + false + - - - - + + + + + - - - + + + + diff --git a/src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs b/src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs index 959c6f1092..e9a87e12aa 100644 --- a/src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs +++ b/src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs @@ -2,173 +2,159 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Diagnostics; -using Microsoft.PowerToys.Settings.UI.Library.Enumerations; +using System.Windows; using PowerAccent.Core.Services; using PowerAccent.Core.Tools; +using PowerToys.PowerAccentKeyboardService; namespace PowerAccent.Core; public class PowerAccent : IDisposable { - private readonly SettingsService _settingService = new SettingsService(); - private readonly KeyboardListener _keyboardListener = new KeyboardListener(); + private readonly SettingsService _settingService; - private LetterKey? letterPressed; private bool _visible; private char[] _characters = Array.Empty(); private int _selectedIndex = -1; - private Stopwatch _stopWatch; - private bool _triggeredWithSpace; public event Action OnChangeDisplay; public event Action OnSelectCharacter; + private KeyboardListener _keyboardListener; + public PowerAccent() { - _keyboardListener.KeyDown += PowerAccent_KeyDown; - _keyboardListener.KeyUp += PowerAccent_KeyUp; + _keyboardListener = new KeyboardListener(); + _keyboardListener.InitHook(); + _settingService = new SettingsService(_keyboardListener); + + SetEvents(); } - private bool PowerAccent_KeyDown(object sender, KeyboardListener.RawKeyEventArgs args) + private void SetEvents() { - if (Enum.IsDefined(typeof(LetterKey), (int)args.Key)) + _keyboardListener.SetShowToolbarEvent(new PowerToys.PowerAccentKeyboardService.ShowToolbar((LetterKey letterKey) => { - _stopWatch = Stopwatch.StartNew(); - letterPressed = (LetterKey)args.Key; - } - - TriggerKey? triggerPressed = null; - if (letterPressed.HasValue) - { - if (Enum.IsDefined(typeof(TriggerKey), (int)args.Key)) + Application.Current.Dispatcher.Invoke(() => { - triggerPressed = (TriggerKey)args.Key; + ShowToolbar(letterKey); + }); + })); - if ((triggerPressed == TriggerKey.Space && _settingService.ActivationKey == PowerAccentActivationKey.LeftRightArrow) || - ((triggerPressed == TriggerKey.Left || triggerPressed == TriggerKey.Right) && _settingService.ActivationKey == PowerAccentActivationKey.Space)) - { - triggerPressed = null; - } - } - } - - if (!_visible && letterPressed.HasValue && triggerPressed.HasValue) + _keyboardListener.SetHideToolbarEvent(new PowerToys.PowerAccentKeyboardService.HideToolbar((InputType inputType) => { - // Keep track if it was triggered with space so that it can be typed on false starts. - _triggeredWithSpace = triggerPressed.Value == TriggerKey.Space; - _visible = true; - _characters = WindowsFunctions.IsCapitalState() ? ToUpper(_settingService.GetLetterKey(letterPressed.Value)) : _settingService.GetLetterKey(letterPressed.Value); - Task.Delay(_settingService.InputTime).ContinueWith( - t => + Application.Current.Dispatcher.Invoke(() => + { + SendInputAndHideToolbar(inputType); + }); + })); + + _keyboardListener.SetNextCharEvent(new PowerToys.PowerAccentKeyboardService.NextChar((TriggerKey triggerKey) => + { + Application.Current.Dispatcher.Invoke(() => + { + ProcessNextChar(triggerKey); + }); + })); + } + + private void ShowToolbar(LetterKey letterKey) + { + _visible = true; + _characters = WindowsFunctions.IsCapitalState() ? ToUpper(SettingsService.GetDefaultLetterKey(letterKey)) : SettingsService.GetDefaultLetterKey(letterKey); + Task.Delay(_settingService.InputTime).ContinueWith( + t => + { + if (_visible) { - if (_visible) + OnChangeDisplay?.Invoke(true, _characters); + } + }, TaskScheduler.FromCurrentSynchronizationContext()); + } + + private void SendInputAndHideToolbar(InputType inputType) + { + switch (inputType) + { + case InputType.Space: + { + WindowsFunctions.Insert(' '); + break; + } + + case InputType.Char: + { + if (_selectedIndex != -1) { - OnChangeDisplay?.Invoke(true, _characters); + WindowsFunctions.Insert(_characters[_selectedIndex], true); } - }, TaskScheduler.FromCurrentSynchronizationContext()); + + break; + } } - if (_visible && triggerPressed.HasValue) + OnChangeDisplay?.Invoke(false, null); + _selectedIndex = -1; + _visible = false; + } + + private void ProcessNextChar(TriggerKey triggerKey) + { + if (_visible && _selectedIndex == -1) { - if (_selectedIndex == -1) + if (triggerKey == TriggerKey.Left) { - if (triggerPressed.Value == TriggerKey.Left) - { - _selectedIndex = (_characters.Length / 2) - 1; - } - - if (triggerPressed.Value == TriggerKey.Right) - { - _selectedIndex = _characters.Length / 2; - } - - if (triggerPressed.Value == TriggerKey.Space) - { - _selectedIndex = 0; - } - - if (_selectedIndex < 0) - { - _selectedIndex = 0; - } - - if (_selectedIndex > _characters.Length - 1) - { - _selectedIndex = _characters.Length - 1; - } - - OnSelectCharacter?.Invoke(_selectedIndex, _characters[_selectedIndex]); - return false; + _selectedIndex = (_characters.Length / 2) - 1; } - if (triggerPressed.Value == TriggerKey.Space) + if (triggerKey == TriggerKey.Right) { - if (_selectedIndex < _characters.Length - 1) - { - ++_selectedIndex; - } - else - { - _selectedIndex = 0; - } + _selectedIndex = _characters.Length / 2; } - if (triggerPressed.Value == TriggerKey.Left && _selectedIndex > 0) + if (triggerKey == TriggerKey.Space) { - --_selectedIndex; + _selectedIndex = 0; } - if (triggerPressed.Value == TriggerKey.Right && _selectedIndex < _characters.Length - 1) + if (_selectedIndex < 0) { - ++_selectedIndex; + _selectedIndex = 0; + } + + if (_selectedIndex > _characters.Length - 1) + { + _selectedIndex = _characters.Length - 1; } OnSelectCharacter?.Invoke(_selectedIndex, _characters[_selectedIndex]); - return false; + return; } - return true; - } - - private bool PowerAccent_KeyUp(object sender, KeyboardListener.RawKeyEventArgs args) - { - if (Enum.IsDefined(typeof(LetterKey), (int)args.Key)) + if (triggerKey == TriggerKey.Space) { - letterPressed = null; - _stopWatch.Stop(); - if (_visible) + if (_selectedIndex < _characters.Length - 1) { - if (_stopWatch.ElapsedMilliseconds < _settingService.InputTime) - { - /* Debug.WriteLine("Insert before inputTime - " + _stopWatch.ElapsedMilliseconds); */ - - // False start, we should output the space if it was the trigger. - if (_triggeredWithSpace) - { - WindowsFunctions.Insert(' '); - } - - OnChangeDisplay?.Invoke(false, null); - _selectedIndex = -1; - _visible = false; - return false; - } - - /* Debug.WriteLine("Insert after inputTime - " + _stopWatch.ElapsedMilliseconds); */ - OnChangeDisplay?.Invoke(false, null); - if (_selectedIndex != -1) - { - WindowsFunctions.Insert(_characters[_selectedIndex], true); - } - - _selectedIndex = -1; - _visible = false; + ++_selectedIndex; + } + else + { + _selectedIndex = 0; } } - return true; + if (triggerKey == TriggerKey.Left && _selectedIndex > 0) + { + --_selectedIndex; + } + + if (triggerKey == TriggerKey.Right && _selectedIndex < _characters.Length - 1) + { + ++_selectedIndex; + } + + OnSelectCharacter?.Invoke(_selectedIndex, _characters[_selectedIndex]); } public Point GetDisplayCoordinates(Size window) @@ -182,14 +168,9 @@ public class PowerAccent : IDisposable return Calculation.GetRawCoordinatesFromPosition(position, screen, window); } - public char[] GetLettersFromKey(LetterKey letter) - { - return _settingService.GetLetterKey(letter); - } - public void Dispose() { - _keyboardListener.Dispose(); + _keyboardListener.UnInitHook(); GC.SuppressFinalize(this); } diff --git a/src/modules/poweraccent/PowerAccent.Core/Services/SettingsService.cs b/src/modules/poweraccent/PowerAccent.Core/Services/SettingsService.cs index 4ed8cf9084..4a5ba06e4d 100644 --- a/src/modules/poweraccent/PowerAccent.Core/Services/SettingsService.cs +++ b/src/modules/poweraccent/PowerAccent.Core/Services/SettingsService.cs @@ -7,6 +7,7 @@ namespace PowerAccent.Core.Services; using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library.Enumerations; using Microsoft.PowerToys.Settings.UI.Library.Utilities; +using PowerToys.PowerAccentKeyboardService; using System.IO.Abstractions; using System.Text.Json; @@ -16,10 +17,12 @@ public class SettingsService private readonly ISettingsUtils _settingsUtils; private readonly IFileSystemWatcher _watcher; private readonly object _loadingSettingsLock = new object(); + private KeyboardListener _keyboardListener; - public SettingsService() + public SettingsService(KeyboardListener keyboardListener) { _settingsUtils = new SettingsUtils(); + _keyboardListener = keyboardListener; ReadSettings(); _watcher = Helper.GetFileWatcher(PowerAccentModuleName, "settings.json", () => { ReadSettings(); }); } @@ -48,7 +51,10 @@ public class SettingsService if (settings != null) { ActivationKey = settings.Properties.ActivationKey; + _keyboardListener.UpdateActivationKey((int)ActivationKey); + InputTime = settings.Properties.InputTime.Value; + _keyboardListener.UpdateInputTime(InputTime); switch (settings.Properties.ToolbarPosition.Value) { case "Top center": @@ -79,6 +85,8 @@ public class SettingsService Position = Position.Center; break; } + + _keyboardListener.UpdateInputTime(InputTime); } } catch (Exception ex) @@ -134,32 +142,27 @@ public class SettingsService } } - public char[] GetLetterKey(LetterKey letter) - { - return GetDefaultLetterKey(letter); - } - public static char[] GetDefaultLetterKey(LetterKey letter) { switch (letter) { - case LetterKey.A: + case LetterKey.VK_A: return new char[] { 'à', 'â', 'á', 'ä', 'ã', 'å', 'æ' }; - case LetterKey.C: + case LetterKey.VK_C: return new char[] { 'ć', 'ĉ', 'č', 'ċ', 'ç', 'ḉ' }; - case LetterKey.E: + case LetterKey.VK_E: return new char[] { 'é', 'è', 'ê', 'ë', 'ē', 'ė', '€' }; - case LetterKey.I: + case LetterKey.VK_I: return new char[] { 'î', 'ï', 'í', 'ì', 'ī' }; - case LetterKey.N: + case LetterKey.VK_N: return new char[] { 'ñ', 'ń' }; - case LetterKey.O: + case LetterKey.VK_O: return new char[] { 'ô', 'ö', 'ó', 'ò', 'õ', 'ø', 'œ' }; - case LetterKey.S: + case LetterKey.VK_S: return new char[] { 'š', 'ß', 'ś' }; - case LetterKey.U: + case LetterKey.VK_U: return new char[] { 'û', 'ù', 'ü', 'ú', 'ū' }; - case LetterKey.Y: + case LetterKey.VK_Y: return new char[] { 'ÿ', 'ý' }; } diff --git a/src/modules/poweraccent/PowerAccent.Core/Tools/KeyboardListener.cs b/src/modules/poweraccent/PowerAccent.Core/Tools/KeyboardListener.cs deleted file mode 100644 index f993dc1214..0000000000 --- a/src/modules/poweraccent/PowerAccent.Core/Tools/KeyboardListener.cs +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore - -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace PowerAccent.Core.Tools; - -internal class KeyboardListener : IDisposable -{ - /// - /// Initializes a new instance of the class. - /// Creates global keyboard listener. - /// - public KeyboardListener() - { - // We have to store the LowLevelKeyboardProc, so that it is not garbage collected by runtime - _hookedLowLevelKeyboardProc = LowLevelKeyboardProc; - - // Set the hook - _hookId = InterceptKeys.SetHook(_hookedLowLevelKeyboardProc); - - // Assign the asynchronous callback event - hookedKeyboardCallbackAsync = new KeyboardCallbackAsync(KeyboardListener_KeyboardCallbackAsync); - } - - /// - /// Fired when any of the keys is pressed down. - /// - public event RawKeyEventHandler KeyDown; - - /// - /// Fired when any of the keys is released. - /// - public event RawKeyEventHandler KeyUp; - - /// - /// Hook ID - /// - private readonly IntPtr _hookId = IntPtr.Zero; - - /// - /// Contains the hooked callback in runtime. - /// - private readonly InterceptKeys.LowLevelKeyboardProc _hookedLowLevelKeyboardProc; - - /// - /// Event to be invoked asynchronously (BeginInvoke) each time key is pressed. - /// - private KeyboardCallbackAsync hookedKeyboardCallbackAsync; - - /// - /// Raw keyevent handler. - /// - /// sender - /// raw keyevent arguments - public delegate bool RawKeyEventHandler(object sender, RawKeyEventArgs args); - - /// - /// Asynchronous callback hook. - /// - /// Keyboard event - /// VKCode - /// Character - private delegate bool KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode, string character); - - /// - /// Actual callback hook. - /// Calls asynchronously the asyncCallback. - /// - /// VKCode - /// wParam - /// lParam - [MethodImpl(MethodImplOptions.NoInlining)] - private IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam) - { - if (nCode >= 0) - { - if (wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYDOWN || - wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYUP) - { - // Captures the character(s) pressed only on WM_KEYDOWN - var chars = InterceptKeys.VKCodeToString( - (uint)Marshal.ReadInt32(lParam), - wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYDOWN); - - if (!hookedKeyboardCallbackAsync.Invoke((InterceptKeys.KeyEvent)wParam.ToUInt32(), Marshal.ReadInt32(lParam), chars)) - { - return (IntPtr)1; - } - } - } - - return InterceptKeys.CallNextHookEx(_hookId, nCode, wParam, lParam); - } - - /// - /// HookCallbackAsync procedure that calls accordingly the KeyDown or KeyUp events. - /// - /// Keyboard event - /// VKCode - /// Character as string. - private bool KeyboardListener_KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode, string character) - { - switch (keyEvent) - { - // KeyDown events - case InterceptKeys.KeyEvent.WM_KEYDOWN: - if (KeyDown != null) - { - return KeyDown.Invoke(this, new RawKeyEventArgs(vkCode, character)); - } - - break; - - // KeyUp events - case InterceptKeys.KeyEvent.WM_KEYUP: - if (KeyUp != null) - { - return KeyUp.Invoke(this, new RawKeyEventArgs(vkCode, character)); - } - - break; - default: - break; - } - - return true; - } - - public void Dispose() - { - InterceptKeys.UnhookWindowsHookEx(_hookId); - } - - /// - /// Raw KeyEvent arguments. - /// - public class RawKeyEventArgs : EventArgs - { - /// - /// WPF Key of the key. - /// -#pragma warning disable SA1401 // Fields should be private - public uint Key; -#pragma warning restore SA1401 // Fields should be private - - /// - /// Convert to string. - /// - /// Returns string representation of this key, if not possible empty string is returned. - public override string ToString() - { - return character; - } - - /// - /// Unicode character of key pressed. - /// - private string character; - - /// - /// Initializes a new instance of the class. - /// Create raw keyevent arguments. - /// - /// VKCode - /// Character - public RawKeyEventArgs(int vKCode, string character) - { - this.character = character; - Key = (uint)vKCode; // User32.MapVirtualKey((uint)VKCode, User32.MAPVK.MAPVK_VK_TO_VSC_EX); - } - } -} - -/// -/// Winapi Key interception helper class. -/// -internal static class InterceptKeys -{ - public delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam); - - private const int WH_KEYBOARD_LL = 13; - - /// - /// Key event - /// - public enum KeyEvent : int - { - /// - /// Key down - /// - WM_KEYDOWN = 256, - - /// - /// Key up - /// - WM_KEYUP = 257, - - /// - /// System key up - /// - WM_SYSKEYUP = 261, - - /// - /// System key down - /// - WM_SYSKEYDOWN = 260, - } - - public static IntPtr SetHook(LowLevelKeyboardProc proc) - { - using (Process curProcess = Process.GetCurrentProcess()) - using (ProcessModule curModule = curProcess.MainModule) - { - return SetWindowsHookEx(WH_KEYBOARD_LL, proc, (IntPtr)0, 0); - } - } - - [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] - public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId); - - [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool UnhookWindowsHookEx(IntPtr hhk); - - [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] - public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam); - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - public static extern IntPtr GetModuleHandle(string lpModuleName); - - // Note: Sometimes single VKCode represents multiple chars, thus string. - // E.g. typing "^1" (notice that when pressing 1 the both characters appear, - // because of this behavior, "^" is called dead key) - [DllImport("user32.dll")] -#pragma warning disable CA1838 // Éviter les paramètres 'StringBuilder' pour les P/Invoke - private static extern int ToUnicodeEx(uint wVirtKey, uint wScanCode, byte[] lpKeyState, [Out, MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pwszBuff, int cchBuff, uint wFlags, IntPtr dwhkl); -#pragma warning restore CA1838 // Éviter les paramètres 'StringBuilder' pour les P/Invoke - - [DllImport("user32.dll")] - private static extern bool GetKeyboardState(byte[] lpKeyState); - - [DllImport("user32.dll")] - private static extern uint MapVirtualKeyEx(uint uCode, uint uMapType, IntPtr dwhkl); - - [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] - private static extern IntPtr GetKeyboardLayout(uint dwLayout); - - [DllImport("User32.dll")] - private static extern IntPtr GetForegroundWindow(); - - [DllImport("User32.dll")] - private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); - - [DllImport("user32.dll")] - private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach); - - [DllImport("kernel32.dll")] - private static extern uint GetCurrentThreadId(); - - private static uint lastVKCode; - private static uint lastScanCode; - private static byte[] lastKeyState = new byte[255]; - - /// - /// Convert VKCode to Unicode. - /// isKeyDown is required for because of keyboard state inconsistencies! - /// - /// VKCode - /// Is the key down event? - /// String representing single unicode character. - public static string VKCodeToString(uint vKCode, bool isKeyDown) - { - // ToUnicodeEx needs StringBuilder, it populates that during execution. - System.Text.StringBuilder sbString = new System.Text.StringBuilder(5); - - byte[] bKeyState = new byte[255]; - bool bKeyStateStatus; - - // Gets the current windows window handle, threadID, processID - IntPtr currentHWnd = GetForegroundWindow(); - uint currentProcessID; - uint currentWindowThreadID = GetWindowThreadProcessId(currentHWnd, out currentProcessID); - - // This programs Thread ID - uint thisProgramThreadId = GetCurrentThreadId(); - - // Attach to active thread so we can get that keyboard state - if (AttachThreadInput(thisProgramThreadId, currentWindowThreadID, true)) - { - // Current state of the modifiers in keyboard - bKeyStateStatus = GetKeyboardState(bKeyState); - - // Detach - AttachThreadInput(thisProgramThreadId, currentWindowThreadID, false); - } - else - { - // Could not attach, perhaps it is this process? - bKeyStateStatus = GetKeyboardState(bKeyState); - } - - // On failure we return empty string. - if (!bKeyStateStatus) - { - return string.Empty; - } - - // Gets the layout of keyboard - IntPtr hkl = GetKeyboardLayout(currentWindowThreadID); - - // Maps the virtual keycode - uint lScanCode = MapVirtualKeyEx(vKCode, 0, hkl); - - // Keyboard state goes inconsistent if this is not in place. In other words, we need to call above commands in UP events also. - if (!isKeyDown) - { - return string.Empty; - } - - // Converts the VKCode to unicode - const uint wFlags = 1 << 2; // If bit 2 is set, keyboard state is not changed (Windows 10, version 1607 and newer) - int relevantKeyCountInBuffer = ToUnicodeEx(vKCode, lScanCode, bKeyState, sbString, sbString.Capacity, wFlags, hkl); - - string ret = string.Empty; - - switch (relevantKeyCountInBuffer) - { - // dead key - case -1: - break; - - case 0: - break; - - // Single character in buffer - case 1: - ret = sbString.Length == 0 ? string.Empty : sbString[0].ToString(); - break; - - // Two or more (only two of them is relevant) - case 2: - default: - ret = sbString.ToString().Substring(0, 2); - break; - } - - // Save these - lastScanCode = lScanCode; - lastVKCode = vKCode; - lastKeyState = (byte[])bKeyState.Clone(); - - return ret; - } -} diff --git a/src/modules/poweraccent/PowerAccent.Core/Tools/WindowsFunctions.cs b/src/modules/poweraccent/PowerAccent.Core/Tools/WindowsFunctions.cs index 535057ea48..21f7a44c86 100644 --- a/src/modules/poweraccent/PowerAccent.Core/Tools/WindowsFunctions.cs +++ b/src/modules/poweraccent/PowerAccent.Core/Tools/WindowsFunctions.cs @@ -27,9 +27,10 @@ internal static class WindowsFunctions } // Letter - var inputsInsert = new User32.INPUT[1] + var inputsInsert = new User32.INPUT[] { new User32.INPUT { type = User32.INPUTTYPE.INPUT_KEYBOARD, ki = new User32.KEYBDINPUT { wVk = 0, dwFlags = User32.KEYEVENTF.KEYEVENTF_UNICODE, wScan = c } }, + new User32.INPUT { type = User32.INPUTTYPE.INPUT_KEYBOARD, ki = new User32.KEYBDINPUT { wVk = 0, dwFlags = User32.KEYEVENTF.KEYEVENTF_UNICODE | User32.KEYEVENTF.KEYEVENTF_KEYUP, wScan = c } }, }; var temp2 = User32.SendInput((uint)inputsInsert.Length, inputsInsert, sizeof(User32.INPUT)); } diff --git a/src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj b/src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj index 9404816655..f444eccb06 100644 --- a/src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj +++ b/src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj @@ -2,7 +2,7 @@ WinExe - net6.0-windows + net6.0-windows10.0.19041.0 win-x64;win-arm64 disable true @@ -26,10 +26,11 @@ + - + diff --git a/src/modules/poweraccent/PowerAccent/PowerAccent.csproj b/src/modules/poweraccent/PowerAccent/PowerAccent.csproj index b18864839a..57df4ca93e 100644 --- a/src/modules/poweraccent/PowerAccent/PowerAccent.csproj +++ b/src/modules/poweraccent/PowerAccent/PowerAccent.csproj @@ -2,7 +2,7 @@ WinExe - net6.0-windows + net6.0-windows10.0.19041.0 win-x64;win-arm64 true disable @@ -12,11 +12,14 @@ false PowerToys.PowerAccent + + + - - - + + + diff --git a/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.cpp b/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.cpp new file mode 100644 index 0000000000..d0490a0327 --- /dev/null +++ b/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.cpp @@ -0,0 +1,207 @@ +#include "pch.h" +#include "KeyboardListener.h" +#include "KeyboardListener.g.cpp" + +#include +#include +#include + +namespace winrt::PowerToys::PowerAccentKeyboardService::implementation +{ + KeyboardListener::KeyboardListener() : + m_toolbarVisible(false), m_triggeredWithSpace(false) + { + s_instance = this; + LoggerHelpers::init_logger(L"PowerAccent", L"PowerAccentKeyboardService", "PowerAccent"); + } + + void KeyboardListener::InitHook() + { +#if defined(DISABLE_LOWLEVEL_HOOKS_WHEN_DEBUGGED) + const bool hook_disabled = IsDebuggerPresent(); +#else + const bool hook_disabled = false; +#endif + + if (!hook_disabled) + { + s_llKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), NULL); + if (!s_llKeyboardHook) + { + DWORD errorCode = GetLastError(); + show_last_error_message(L"SetWindowsHookEx", errorCode, L"PowerToys - PowerAccent"); + auto errorMessage = get_last_error_message(errorCode); + Logger::error(errorMessage.has_value() ? errorMessage.value() : L""); + } + } + } + + void KeyboardListener::UnInitHook() + { + if (s_llKeyboardHook) + { + if (UnhookWindowsHookEx(s_llKeyboardHook)) + { + s_llKeyboardHook = nullptr; + } + } + } + + void KeyboardListener::SetShowToolbarEvent(ShowToolbar showToolbarEvent) + { + m_showToolbarCb = [trigger = std::move(showToolbarEvent)](LetterKey key) { + trigger(key); + }; + } + + void KeyboardListener::SetHideToolbarEvent(HideToolbar hideToolbarEvent) + { + m_hideToolbarCb = [trigger = std::move(hideToolbarEvent)](InputType inputType) { + trigger(inputType); + }; + } + + void KeyboardListener::SetNextCharEvent(NextChar nextCharEvent) + { + m_nextCharCb = [trigger = std::move(nextCharEvent)](TriggerKey triggerKey) { + trigger(triggerKey); + }; + } + + void KeyboardListener::UpdateActivationKey(int32_t activationKey) + { + m_settings.activationKey = static_cast(activationKey); + } + + void KeyboardListener::UpdateInputTime(int32_t inputTime) + { + m_settings.inputTime = std::chrono::milliseconds(inputTime); + } + + bool KeyboardListener::OnKeyDown(KBDLLHOOKSTRUCT info) noexcept + { + if (std::find(std::begin(letters), end(letters), static_cast(info.vkCode)) != end(letters)) + { + m_stopwatch.reset(); + letterPressed = static_cast(info.vkCode); + } + + UINT triggerPressed = 0; + if (letterPressed != LetterKey::None) + { + if (std::find(std::begin(triggers), end(triggers), static_cast(info.vkCode)) != end(triggers)) + { + triggerPressed = info.vkCode; + + if ((triggerPressed == VK_SPACE && m_settings.activationKey == PowerAccentActivationKey::LeftRightArrow) || + ((triggerPressed == VK_LEFT || triggerPressed == VK_RIGHT) && m_settings.activationKey == PowerAccentActivationKey::Space)) + { + triggerPressed = 0; + Logger::info(L"Reset trigger key"); + } + } + } + + if (!m_toolbarVisible && letterPressed != LetterKey::None && triggerPressed) + { + Logger::info(L"Show toolbar. Letter: %d, Trigger: %d", letterPressed, triggerPressed); + + // Keep track if it was triggered with space so that it can be typed on false starts. + m_triggeredWithSpace = triggerPressed == VK_SPACE; + m_toolbarVisible = true; + + m_showToolbarCb(letterPressed); + } + + if (m_toolbarVisible && triggerPressed) + { + if (triggerPressed == VK_LEFT) + { + Logger::info(L"Next toolbar position - left"); + m_nextCharCb(TriggerKey::Left); + } + else if (triggerPressed == VK_RIGHT) + { + Logger::info(L"Next toolbar position - right"); + m_nextCharCb(TriggerKey::Right); + } + else if (triggerPressed == VK_SPACE) + { + Logger::info(L"Next toolbar position - space"); + m_nextCharCb(TriggerKey::Space); + } + + return true; + } + + return false; + } + + bool KeyboardListener::OnKeyUp(KBDLLHOOKSTRUCT info) noexcept + { + if (std::find(std::begin(letters), end(letters), static_cast(info.vkCode)) != end(letters)) + { + letterPressed = LetterKey::None; + + if (m_toolbarVisible) + { + if (m_stopwatch.elapsed() < m_settings.inputTime) + { + Logger::info(L"Activation too fast. Do nothing."); + + // False start, we should output the space if it was the trigger. + if (m_triggeredWithSpace) + { + m_hideToolbarCb(InputType::Space); + } + else + { + m_hideToolbarCb(InputType::None); + } + + m_toolbarVisible = false; + return true; + } + + Logger::info(L"Hide toolbar event and input char"); + + m_hideToolbarCb(InputType::Char); + + m_toolbarVisible = false; + } + } + + return false; + } + + LRESULT KeyboardListener::LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) + { + { + if (nCode == HC_ACTION && s_instance != nullptr) + { + KBDLLHOOKSTRUCT* key = reinterpret_cast(lParam); + switch (wParam) + { + case WM_KEYDOWN: + { + if (s_instance->OnKeyDown(*key)) + { + return true; + } + } + break; + case WM_KEYUP: + { + if (s_instance->OnKeyUp(*key)) + { + return true; + } + } + break; + } + } + + return CallNextHookEx(NULL, nCode, wParam, lParam); + } + } +} diff --git a/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.h b/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.h new file mode 100644 index 0000000000..a11b67766f --- /dev/null +++ b/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.h @@ -0,0 +1,75 @@ +#pragma once + +#include "KeyboardListener.g.h" + +#include + +namespace winrt::PowerToys::PowerAccentKeyboardService::implementation +{ + enum PowerAccentActivationKey + { + LeftRightArrow, + Space, + Both, + }; + + struct PowerAccentSettings + { + PowerAccentActivationKey activationKey{ PowerAccentActivationKey::Both }; + std::chrono::milliseconds inputTime{ 200 }; + }; + + struct KeyboardListener : KeyboardListenerT + { + using LetterKey = winrt::PowerToys::PowerAccentKeyboardService::LetterKey; + using TriggerKey = winrt::PowerToys::PowerAccentKeyboardService::TriggerKey; + using InputType = winrt::PowerToys::PowerAccentKeyboardService::InputType; + + KeyboardListener(); + + void KeyboardListener::InitHook(); + void KeyboardListener::UnInitHook(); + void SetShowToolbarEvent(ShowToolbar showToolbarEvent); + void SetHideToolbarEvent(HideToolbar hideToolbarEvent); + void SetNextCharEvent(NextChar NextCharEvent); + + void UpdateActivationKey(int32_t activationKey); + void UpdateInputTime(int32_t inputTime); + + static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam); + + private: + bool OnKeyDown(KBDLLHOOKSTRUCT info) noexcept; + bool OnKeyUp(KBDLLHOOKSTRUCT info) noexcept; + + static inline KeyboardListener* s_instance; + HHOOK s_llKeyboardHook = nullptr; + bool m_toolbarVisible; + PowerAccentSettings m_settings; + std::function m_showToolbarCb; + std::function m_hideToolbarCb; + std::function m_nextCharCb; + bool m_triggeredWithSpace; + spdlog::stopwatch m_stopwatch; + + static inline const std::vector letters = { LetterKey::VK_A, + LetterKey::VK_C, + LetterKey::VK_E, + LetterKey::VK_I, + LetterKey::VK_N, + LetterKey::VK_O, + LetterKey::VK_S, + LetterKey::VK_U, + LetterKey::VK_Y }; + LetterKey letterPressed{}; + + static inline const std::vector triggers = { TriggerKey::Right, TriggerKey::Left, TriggerKey::Space }; + }; +} + +namespace winrt::PowerToys::PowerAccentKeyboardService::factory_implementation +{ + struct KeyboardListener : KeyboardListenerT + { + }; +} diff --git a/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.idl b/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.idl new file mode 100644 index 0000000000..46a11a7ffa --- /dev/null +++ b/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.idl @@ -0,0 +1,48 @@ +namespace PowerToys +{ + namespace PowerAccentKeyboardService + { + enum LetterKey + { + None = 0x00, + VK_A = 0x41, + VK_C = 0x43, + VK_E = 0x45, + VK_I = 0x49, + VK_N = 0x4E, + VK_O = 0x4F, + VK_S = 0x53, + VK_U = 0x55, + VK_Y = 0x59 + }; + + enum TriggerKey + { + Right = 0x27, // VK_RIGHT + Left = 0x25, // VK_LEFT + Space = 0x20 // VK_SPACE + }; + + enum InputType + { + None, + Space, + Char + }; + + [version(1.0), uuid(37197089-5438-4479-af57-30ab3f3c8be4)] delegate void ShowToolbar(LetterKey key); + [version(1.0), uuid(8eb79d6b-1826-424f-9fbc-af21ae19725e)] delegate void HideToolbar(InputType inputType); + [version(1.0), uuid(db72d45c-a5a2-446f-bdc1-506e9121764a)] delegate void NextChar(TriggerKey inputSpace); + + [default_interface] runtimeclass KeyboardListener { + KeyboardListener(); + void InitHook(); + void UnInitHook(); + void SetShowToolbarEvent(event ShowToolbar showToolbarEvent); + void SetHideToolbarEvent(event HideToolbar hideToolbarEvent); + void SetNextCharEvent(event NextChar nextCharEvent); + void UpdateActivationKey(Int32 activationKey); + void UpdateInputTime(Int32 inputTime); + } + } +} \ No newline at end of file diff --git a/src/modules/poweraccent/PowerAccentKeyboardService/PowerAccentKeyboardService.def b/src/modules/poweraccent/PowerAccentKeyboardService/PowerAccentKeyboardService.def new file mode 100644 index 0000000000..24e7c1235c --- /dev/null +++ b/src/modules/poweraccent/PowerAccentKeyboardService/PowerAccentKeyboardService.def @@ -0,0 +1,3 @@ +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/src/modules/poweraccent/PowerAccentKeyboardService/PowerAccentKeyboardService.rc b/src/modules/poweraccent/PowerAccentKeyboardService/PowerAccentKeyboardService.rc new file mode 100644 index 0000000000..5fa3c8b90d --- /dev/null +++ b/src/modules/poweraccent/PowerAccentKeyboardService/PowerAccentKeyboardService.rc @@ -0,0 +1,40 @@ +#include +#include "resource.h" +#include "../../../common/version/version.h" + +#define APSTUDIO_READONLY_SYMBOLS +#include "winres.h" +#undef APSTUDIO_READONLY_SYMBOLS + +1 VERSIONINFO +FILEVERSION FILE_VERSION +PRODUCTVERSION PRODUCT_VERSION +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG +FILEFLAGS VS_FF_DEBUG +#else +FILEFLAGS 0x0L +#endif +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset + BEGIN + VALUE "CompanyName", COMPANY_NAME + VALUE "FileDescription", FILE_DESCRIPTION + VALUE "FileVersion", FILE_VERSION_STRING + VALUE "InternalName", INTERNAL_NAME + VALUE "LegalCopyright", COPYRIGHT_NOTE + VALUE "OriginalFilename", ORIGINAL_FILENAME + VALUE "ProductName", PRODUCT_NAME + VALUE "ProductVersion", PRODUCT_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset + END +END diff --git a/src/modules/poweraccent/PowerAccentKeyboardService/PowerAccentKeyboardService.vcxproj b/src/modules/poweraccent/PowerAccentKeyboardService/PowerAccentKeyboardService.vcxproj new file mode 100644 index 0000000000..98cf44e373 --- /dev/null +++ b/src/modules/poweraccent/PowerAccentKeyboardService/PowerAccentKeyboardService.vcxproj @@ -0,0 +1,139 @@ + + + + + true + true + true + true + {c97d9a5d-206c-454e-997e-009e227d7f02} + PowerAccentKeyboardService + PowerToys.PowerAccentKeyboardService + en-US + 14.0 + false + false + Windows Store + 10.0 + 10.0.19041.0 + 10.0.17134.0 + + + + DynamicLibrary + v143 + Unicode + false + + + true + true + + + false + true + false + + + + + + + + + + + + + + + PowerToys.PowerAccentKeyboardService + $(SolutionDir)$(Platform)\$(Configuration)\modules\PowerAccent\ + + + + Use + pch.h + $(IntDir)pch.pch + Level4 + %(AdditionalOptions) /bigobj + + /DWINRT_NO_MAKE_DETECTION %(AdditionalOptions) + _WINRT_DLL;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + ../../..;%(AdditionalIncludeDirectories) + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + + + Console + false + PowerAccentKeyboardService.def + Shell32.lib;%(AdditionalDependencies) + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + NDEBUG;%(PreprocessorDefinitions) + + + true + true + + + + + + KeyboardListener.idl + + + + + + Create + + + KeyboardListener.idl + + + + + + + + + + + + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {6955446d-23f7-4023-9bb3-8657f904af99} + + + {cc6e41ac-8174-4e8a-8d22-85dd7f4851df} + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/modules/poweraccent/PowerAccentKeyboardService/PowerAccentKeyboardService.vcxproj.filters b/src/modules/poweraccent/PowerAccentKeyboardService/PowerAccentKeyboardService.vcxproj.filters new file mode 100644 index 0000000000..ef68468742 --- /dev/null +++ b/src/modules/poweraccent/PowerAccentKeyboardService/PowerAccentKeyboardService.vcxproj.filters @@ -0,0 +1,35 @@ + + + + + accd3aa8-1ba0-4223-9bbe-0c431709210b + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tga;tiff;tif;png;wav;mfcribbon-ms + + + {926ab91d-31b4-48c3-b9a4-e681349f27f0} + + + + + + + + + + + + + + + + + + + + + + + Resources + + + \ No newline at end of file diff --git a/src/modules/poweraccent/PowerAccentKeyboardService/PropertySheet.props b/src/modules/poweraccent/PowerAccentKeyboardService/PropertySheet.props new file mode 100644 index 0000000000..e34141b019 --- /dev/null +++ b/src/modules/poweraccent/PowerAccentKeyboardService/PropertySheet.props @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/src/modules/poweraccent/PowerAccentKeyboardService/packages.config b/src/modules/poweraccent/PowerAccentKeyboardService/packages.config new file mode 100644 index 0000000000..fa024c0634 --- /dev/null +++ b/src/modules/poweraccent/PowerAccentKeyboardService/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/modules/poweraccent/PowerAccentKeyboardService/pch.cpp b/src/modules/poweraccent/PowerAccentKeyboardService/pch.cpp new file mode 100644 index 0000000000..bcb5590be1 --- /dev/null +++ b/src/modules/poweraccent/PowerAccentKeyboardService/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/src/modules/poweraccent/PowerAccentKeyboardService/pch.h b/src/modules/poweraccent/PowerAccentKeyboardService/pch.h new file mode 100644 index 0000000000..21199686d5 --- /dev/null +++ b/src/modules/poweraccent/PowerAccentKeyboardService/pch.h @@ -0,0 +1,4 @@ +#pragma once +#include +#include +#include diff --git a/src/modules/poweraccent/PowerAccentKeyboardService/resource.h b/src/modules/poweraccent/PowerAccentKeyboardService/resource.h new file mode 100644 index 0000000000..b853ed4ec1 --- /dev/null +++ b/src/modules/poweraccent/PowerAccentKeyboardService/resource.h @@ -0,0 +1,13 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by PowerToys.MeasureToolCore.rc + +////////////////////////////// +// Non-localizable + +#define FILE_DESCRIPTION "PowerToys PowerAccentKeyboardService" +#define INTERNAL_NAME "PowerToys.PowerAccentKeyboardService" +#define ORIGINAL_FILENAME "PowerToys.PowerAccentKeyboardService.dll" + +// Non-localizable +////////////////////////////// diff --git a/src/modules/poweraccent/PowerAccentModuleInterface/PowerAccentModuleInterface.vcxproj b/src/modules/poweraccent/PowerAccentModuleInterface/PowerAccentModuleInterface.vcxproj index 8ed1ab881a..bb24f487dc 100644 --- a/src/modules/poweraccent/PowerAccentModuleInterface/PowerAccentModuleInterface.vcxproj +++ b/src/modules/poweraccent/PowerAccentModuleInterface/PowerAccentModuleInterface.vcxproj @@ -25,7 +25,7 @@ $(SolutionDir)$(Platform)\$(Configuration)\modules\PowerAccent\ - + PowerToys.$(ProjectName)