mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 10:16:24 +02:00
[New PowerToy] PowerAccent (#19212)
* add poweraccent (draft) for PR * removing french text for Spell checking job * add 'poweraccent' to spell checker * add 'damienleroy' to spell checker file * adding RuntimeIdentifiers for PowerAccent project * duplicate image for settings * update commandline arguments for launch settings * Removing WndProc for testing with inter-process connection * add PowerAccent sources for PowerToys * fix spellcheck * fixing stylecop conventions * Remove StyleCop.Analyzers because of duplicate * fixing command line reference * Fixing CS8012 for PowerAccent. * ARM64 processor * - Modify PowerAccent fluenticon for dark mode - Try fix arm64 release * Remove taskbar * init Oobe view * - added POwerAccent to App.xaml.cs - change style to markdown in Oobe display * - fixing poweraccent crash - change Oobe LearnMore link * Installer and signing * Cleanup Add settings * Issue template * Add some more characters * Disabled by default * Proper ToUnicodeEx calling and remove hacks * Fix spellcheck * Remove CommandLine dependency and debug prints. Add logs * fix signing * Fix binary metadata with version * Fix the added space bug * Only type space if it was the trigger method * Take account of InputTime for displaying UI * Fix code styling * Remove the Trace WriteLine hack and add a delay instead * Reinstate logs * Better explanations * Add telemetry for showing the menu * Update src/settings-ui/Settings.UI/Strings/en-us/Resources.resw * Update src/settings-ui/Settings.UI/Strings/en-us/Resources.resw * Update src/modules/poweraccent/PowerAccent.Core/Tools/KeyboardListener.cs * Update src/modules/poweraccent/PowerAccent.Core/Services/SettingsService.cs * Add accented characters for S * Default to both activation methods * Update src/modules/poweraccent/PowerAccent.Core/Services/SettingsService.cs * Update src/modules/poweraccent/PowerAccent.Core/Services/SettingsService.cs * Update src/modules/poweraccent/PowerAccent.Core/Services/SettingsService.cs * Update src/modules/poweraccent/PowerAccent.Core/Services/SettingsService.cs * Update src/modules/poweraccent/PowerAccent.Core/Services/SettingsService.cs * Update src/modules/poweraccent/PowerAccent.Core/Services/SettingsService.cs Co-authored-by: Damien LEROY <dleroy@veepee.com>
This commit is contained in:
27
src/modules/poweraccent/PowerAccent.Core/Models/KeysEnums.cs
Normal file
27
src/modules/poweraccent/PowerAccent.Core/Models/KeysEnums.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
// 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.
|
||||
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace PowerAccent.Core;
|
||||
|
||||
public enum LetterKey
|
||||
{
|
||||
A = User32.VK.VK_A,
|
||||
C = User32.VK.VK_C,
|
||||
E = User32.VK.VK_E,
|
||||
I = User32.VK.VK_I,
|
||||
N = User32.VK.VK_N,
|
||||
O = User32.VK.VK_O,
|
||||
S = User32.VK.VK_S,
|
||||
U = User32.VK.VK_U,
|
||||
Y = User32.VK.VK_Y,
|
||||
}
|
||||
|
||||
public enum TriggerKey
|
||||
{
|
||||
Left = User32.VK.VK_LEFT,
|
||||
Right = User32.VK.VK_RIGHT,
|
||||
Space = User32.VK.VK_SPACE,
|
||||
}
|
||||
58
src/modules/poweraccent/PowerAccent.Core/Models/Point.cs
Normal file
58
src/modules/poweraccent/PowerAccent.Core/Models/Point.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
// 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.
|
||||
|
||||
namespace PowerAccent.Core;
|
||||
|
||||
public struct Point
|
||||
{
|
||||
public Point()
|
||||
{
|
||||
X = 0;
|
||||
Y = 0;
|
||||
}
|
||||
|
||||
public Point(double x, double y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public Point(int x, int y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public Point(System.Drawing.Point point)
|
||||
{
|
||||
X = point.X;
|
||||
Y = point.Y;
|
||||
}
|
||||
|
||||
public double X { get; init; }
|
||||
|
||||
public double Y { get; init; }
|
||||
|
||||
public static implicit operator Point(System.Drawing.Point point) => new Point(point.X, point.Y);
|
||||
|
||||
public static Point operator /(Point point, double divider)
|
||||
{
|
||||
if (divider == 0)
|
||||
{
|
||||
throw new DivideByZeroException();
|
||||
}
|
||||
|
||||
return new Point(point.X / divider, point.Y / divider);
|
||||
}
|
||||
|
||||
public static Point operator /(Point point, Point divider)
|
||||
{
|
||||
if (divider.X == 0 || divider.Y == 0)
|
||||
{
|
||||
throw new DivideByZeroException();
|
||||
}
|
||||
|
||||
return new Point(point.X / divider.X, point.Y / divider.Y);
|
||||
}
|
||||
}
|
||||
68
src/modules/poweraccent/PowerAccent.Core/Models/Rect.cs
Normal file
68
src/modules/poweraccent/PowerAccent.Core/Models/Rect.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
// 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.
|
||||
|
||||
namespace PowerAccent.Core;
|
||||
|
||||
public struct Rect
|
||||
{
|
||||
public Rect()
|
||||
{
|
||||
X = 0;
|
||||
Y = 0;
|
||||
Width = 0;
|
||||
Height = 0;
|
||||
}
|
||||
|
||||
public Rect(int x, int y, int width, int height)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
public Rect(double x, double y, double width, double height)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
public Rect(Point coord, Size size)
|
||||
{
|
||||
X = coord.X;
|
||||
Y = coord.Y;
|
||||
Width = size.Width;
|
||||
Height = size.Height;
|
||||
}
|
||||
|
||||
public double X { get; init; }
|
||||
|
||||
public double Y { get; init; }
|
||||
|
||||
public double Width { get; init; }
|
||||
|
||||
public double Height { get; init; }
|
||||
|
||||
public static Rect operator /(Rect rect, double divider)
|
||||
{
|
||||
if (divider == 0)
|
||||
{
|
||||
throw new DivideByZeroException();
|
||||
}
|
||||
|
||||
return new Rect(rect.X / divider, rect.Y / divider, rect.Width / divider, rect.Height / divider);
|
||||
}
|
||||
|
||||
public static Rect operator /(Rect rect, Rect divider)
|
||||
{
|
||||
if (divider.X == 0 || divider.Y == 0)
|
||||
{
|
||||
throw new DivideByZeroException();
|
||||
}
|
||||
|
||||
return new Rect(rect.X / divider.X, rect.Y / divider.Y, rect.Width / divider.Width, rect.Height / divider.Height);
|
||||
}
|
||||
}
|
||||
52
src/modules/poweraccent/PowerAccent.Core/Models/Size.cs
Normal file
52
src/modules/poweraccent/PowerAccent.Core/Models/Size.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
// 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.
|
||||
|
||||
namespace PowerAccent.Core;
|
||||
|
||||
public struct Size
|
||||
{
|
||||
public Size()
|
||||
{
|
||||
Width = 0;
|
||||
Height = 0;
|
||||
}
|
||||
|
||||
public Size(double width, double height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
public Size(int width, int height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
public double Width { get; init; }
|
||||
|
||||
public double Height { get; init; }
|
||||
|
||||
public static implicit operator Size(System.Drawing.Size size) => new Size(size.Width, size.Height);
|
||||
|
||||
public static Size operator /(Size size, double divider)
|
||||
{
|
||||
if (divider == 0)
|
||||
{
|
||||
throw new DivideByZeroException();
|
||||
}
|
||||
|
||||
return new Size(size.Width / divider, size.Height / divider);
|
||||
}
|
||||
|
||||
public static Size operator /(Size size, Size divider)
|
||||
{
|
||||
if (divider.Width == 0 || divider.Height == 0 || divider.Width == 0 || divider.Height == 0)
|
||||
{
|
||||
throw new DivideByZeroException();
|
||||
}
|
||||
|
||||
return new Size(size.Width / divider.Width, size.Height / divider.Height);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\Version.props" />
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
||||
<PackageReference Include="Vanara.PInvoke.User32" Version="3.3.15" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
206
src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs
Normal file
206
src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
// 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.
|
||||
|
||||
using System.Diagnostics;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Enumerations;
|
||||
using PowerAccent.Core.Services;
|
||||
using PowerAccent.Core.Tools;
|
||||
|
||||
namespace PowerAccent.Core;
|
||||
|
||||
public class PowerAccent : IDisposable
|
||||
{
|
||||
private readonly SettingsService _settingService = new SettingsService();
|
||||
private readonly KeyboardListener _keyboardListener = new KeyboardListener();
|
||||
|
||||
private LetterKey? letterPressed;
|
||||
private bool _visible;
|
||||
private char[] _characters = Array.Empty<char>();
|
||||
private int _selectedIndex = -1;
|
||||
private Stopwatch _stopWatch;
|
||||
private bool _triggeredWithSpace;
|
||||
|
||||
public event Action<bool, char[]> OnChangeDisplay;
|
||||
|
||||
public event Action<int, char> OnSelectCharacter;
|
||||
|
||||
public PowerAccent()
|
||||
{
|
||||
_keyboardListener.KeyDown += PowerAccent_KeyDown;
|
||||
_keyboardListener.KeyUp += PowerAccent_KeyUp;
|
||||
}
|
||||
|
||||
private bool PowerAccent_KeyDown(object sender, KeyboardListener.RawKeyEventArgs args)
|
||||
{
|
||||
if (Enum.IsDefined(typeof(LetterKey), (int)args.Key))
|
||||
{
|
||||
_stopWatch = Stopwatch.StartNew();
|
||||
letterPressed = (LetterKey)args.Key;
|
||||
}
|
||||
|
||||
TriggerKey? triggerPressed = null;
|
||||
if (letterPressed.HasValue)
|
||||
{
|
||||
if (Enum.IsDefined(typeof(TriggerKey), (int)args.Key))
|
||||
{
|
||||
triggerPressed = (TriggerKey)args.Key;
|
||||
|
||||
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)
|
||||
{
|
||||
// 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 =>
|
||||
{
|
||||
if (_visible)
|
||||
{
|
||||
OnChangeDisplay?.Invoke(true, _characters);
|
||||
}
|
||||
}, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
|
||||
if (_visible && triggerPressed.HasValue)
|
||||
{
|
||||
if (_selectedIndex == -1)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (triggerPressed.Value == TriggerKey.Space)
|
||||
{
|
||||
if (_selectedIndex < _characters.Length - 1)
|
||||
{
|
||||
++_selectedIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (triggerPressed.Value == TriggerKey.Left && _selectedIndex > 0)
|
||||
{
|
||||
--_selectedIndex;
|
||||
}
|
||||
|
||||
if (triggerPressed.Value == TriggerKey.Right && _selectedIndex < _characters.Length - 1)
|
||||
{
|
||||
++_selectedIndex;
|
||||
}
|
||||
|
||||
OnSelectCharacter?.Invoke(_selectedIndex, _characters[_selectedIndex]);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool PowerAccent_KeyUp(object sender, KeyboardListener.RawKeyEventArgs args)
|
||||
{
|
||||
if (Enum.IsDefined(typeof(LetterKey), (int)args.Key))
|
||||
{
|
||||
letterPressed = null;
|
||||
_stopWatch.Stop();
|
||||
if (_visible)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Point GetDisplayCoordinates(Size window)
|
||||
{
|
||||
var activeDisplay = WindowsFunctions.GetActiveDisplay();
|
||||
Rect screen = new Rect(activeDisplay.Location, activeDisplay.Size) / activeDisplay.Dpi;
|
||||
Position position = _settingService.Position;
|
||||
|
||||
/* Debug.WriteLine("Dpi: " + activeDisplay.Dpi); */
|
||||
|
||||
return Calculation.GetRawCoordinatesFromPosition(position, screen, window);
|
||||
}
|
||||
|
||||
public char[] GetLettersFromKey(LetterKey letter)
|
||||
{
|
||||
return _settingService.GetLetterKey(letter);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_keyboardListener.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public static char[] ToUpper(char[] array)
|
||||
{
|
||||
char[] result = new char[array.Length];
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
result[i] = char.ToUpper(array[i], System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
// 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.
|
||||
|
||||
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 System.IO.Abstractions;
|
||||
using System.Text.Json;
|
||||
|
||||
public class SettingsService
|
||||
{
|
||||
private const string PowerAccentModuleName = "PowerAccent";
|
||||
private readonly ISettingsUtils _settingsUtils;
|
||||
private readonly IFileSystemWatcher _watcher;
|
||||
private readonly object _loadingSettingsLock = new object();
|
||||
|
||||
public SettingsService()
|
||||
{
|
||||
_settingsUtils = new SettingsUtils();
|
||||
ReadSettings();
|
||||
_watcher = Helper.GetFileWatcher(PowerAccentModuleName, "settings.json", () => { ReadSettings(); });
|
||||
}
|
||||
|
||||
private void ReadSettings()
|
||||
{
|
||||
// TODO this IO call should by Async, update GetFileWatcher helper to support async
|
||||
lock (_loadingSettingsLock)
|
||||
{
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_settingsUtils.SettingsExists(PowerAccentModuleName))
|
||||
{
|
||||
Logger.LogInfo("PowerAccent settings.json was missing, creating a new one");
|
||||
var defaultSettings = new PowerAccentSettings();
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
};
|
||||
|
||||
_settingsUtils.SaveSettings(JsonSerializer.Serialize(this, options), PowerAccentModuleName);
|
||||
}
|
||||
|
||||
var settings = _settingsUtils.GetSettingsOrDefault<PowerAccentSettings>(PowerAccentModuleName);
|
||||
if (settings != null)
|
||||
{
|
||||
ActivationKey = settings.Properties.ActivationKey;
|
||||
switch (settings.Properties.ToolbarPosition.Value)
|
||||
{
|
||||
case "Top center":
|
||||
Position = Position.Top;
|
||||
break;
|
||||
case "Bottom center":
|
||||
Position = Position.Bottom;
|
||||
break;
|
||||
case "Left":
|
||||
Position = Position.Left;
|
||||
break;
|
||||
case "Right":
|
||||
Position = Position.Right;
|
||||
break;
|
||||
case "Top right corner":
|
||||
Position = Position.TopRight;
|
||||
break;
|
||||
case "Top left corner":
|
||||
Position = Position.TopLeft;
|
||||
break;
|
||||
case "Bottom right corner":
|
||||
Position = Position.BottomRight;
|
||||
break;
|
||||
case "Bottom left corner":
|
||||
Position = Position.BottomLeft;
|
||||
break;
|
||||
case "Center":
|
||||
Position = Position.Center;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to read changed settings", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PowerAccentActivationKey _activationKey = PowerAccentActivationKey.Both;
|
||||
|
||||
public PowerAccentActivationKey ActivationKey
|
||||
{
|
||||
get
|
||||
{
|
||||
return _activationKey;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_activationKey = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Position _position = Position.Top;
|
||||
|
||||
public Position Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return _position;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_position = value;
|
||||
}
|
||||
}
|
||||
|
||||
private int _inputTime = 200;
|
||||
|
||||
public int InputTime
|
||||
{
|
||||
get
|
||||
{
|
||||
return _inputTime;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_inputTime = value;
|
||||
}
|
||||
}
|
||||
|
||||
public char[] GetLetterKey(LetterKey letter)
|
||||
{
|
||||
return GetDefaultLetterKey(letter);
|
||||
}
|
||||
|
||||
public static char[] GetDefaultLetterKey(LetterKey letter)
|
||||
{
|
||||
switch (letter)
|
||||
{
|
||||
case LetterKey.A:
|
||||
return new char[] { 'à', 'â', 'á', 'ä', 'ã', 'å', 'æ' };
|
||||
case LetterKey.C:
|
||||
return new char[] { 'ć', 'ĉ', 'č', 'ċ', 'ç', 'ḉ' };
|
||||
case LetterKey.E:
|
||||
return new char[] { 'é', 'è', 'ê', 'ë', 'ē', 'ė', '€' };
|
||||
case LetterKey.I:
|
||||
return new char[] { 'î', 'ï', 'í', 'ì', 'ī' };
|
||||
case LetterKey.N:
|
||||
return new char[] { 'ñ', 'ń' };
|
||||
case LetterKey.O:
|
||||
return new char[] { 'ô', 'ö', 'ó', 'ò', 'õ', 'ø', 'œ' };
|
||||
case LetterKey.S:
|
||||
return new char[] { 'š', 'ß', 'ś' };
|
||||
case LetterKey.U:
|
||||
return new char[] { 'û', 'ù', 'ü', 'ú', 'ū' };
|
||||
case LetterKey.Y:
|
||||
return new char[] { 'ÿ', 'ý' };
|
||||
}
|
||||
|
||||
throw new ArgumentException("Letter {0} is missing", letter.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public enum Position
|
||||
{
|
||||
Top,
|
||||
Bottom,
|
||||
Left,
|
||||
Right,
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomLeft,
|
||||
BottomRight,
|
||||
Center,
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// 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.
|
||||
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace PowerAccent.Core.Telemetry
|
||||
{
|
||||
[EventData]
|
||||
public class PowerAccentShowAccentMenuEvent : EventBase, IEvent
|
||||
{
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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.
|
||||
|
||||
using PowerAccent.Core.Services;
|
||||
|
||||
namespace PowerAccent.Core.Tools
|
||||
{
|
||||
internal static class Calculation
|
||||
{
|
||||
public static Point GetRawCoordinatesFromCaret(Point caret, Rect screen, Size window)
|
||||
{
|
||||
var left = caret.X - (window.Width / 2);
|
||||
var top = caret.Y - window.Height - 20;
|
||||
|
||||
return new Point(
|
||||
left < screen.X ? screen.X : (left + window.Width > (screen.X + screen.Width) ? (screen.X + screen.Width) - window.Width : left),
|
||||
top < screen.Y ? caret.Y + 20 : top);
|
||||
}
|
||||
|
||||
public static Point GetRawCoordinatesFromPosition(Position position, Rect screen, Size window)
|
||||
{
|
||||
int offset = 10;
|
||||
|
||||
double pointX = position switch
|
||||
{
|
||||
Position.Top or Position.Bottom or Position.Center
|
||||
=> screen.X + (screen.Width / 2) - (window.Width / 2),
|
||||
Position.TopLeft or Position.Left or Position.BottomLeft
|
||||
=> screen.X + offset,
|
||||
Position.TopRight or Position.Right or Position.BottomRight
|
||||
=> screen.X + screen.Width - (window.Width + offset),
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
double pointY = position switch
|
||||
{
|
||||
Position.TopLeft or Position.Top or Position.TopRight
|
||||
=> screen.Y + offset,
|
||||
Position.Left or Position.Center or Position.Right
|
||||
=> screen.Y + (screen.Height / 2) - (window.Height / 2),
|
||||
Position.BottomLeft or Position.Bottom or Position.BottomRight
|
||||
=> screen.Y + screen.Height - (window.Height + offset),
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
return new Point(pointX, pointY);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="KeyboardListener"/> class.
|
||||
/// Creates global keyboard listener.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fired when any of the keys is pressed down.
|
||||
/// </summary>
|
||||
public event RawKeyEventHandler KeyDown;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when any of the keys is released.
|
||||
/// </summary>
|
||||
public event RawKeyEventHandler KeyUp;
|
||||
|
||||
/// <summary>
|
||||
/// Hook ID
|
||||
/// </summary>
|
||||
private readonly IntPtr _hookId = IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the hooked callback in runtime.
|
||||
/// </summary>
|
||||
private readonly InterceptKeys.LowLevelKeyboardProc _hookedLowLevelKeyboardProc;
|
||||
|
||||
/// <summary>
|
||||
/// Event to be invoked asynchronously (BeginInvoke) each time key is pressed.
|
||||
/// </summary>
|
||||
private KeyboardCallbackAsync hookedKeyboardCallbackAsync;
|
||||
|
||||
/// <summary>
|
||||
/// Raw keyevent handler.
|
||||
/// </summary>
|
||||
/// <param name="sender">sender</param>
|
||||
/// <param name="args">raw keyevent arguments</param>
|
||||
public delegate bool RawKeyEventHandler(object sender, RawKeyEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronous callback hook.
|
||||
/// </summary>
|
||||
/// <param name="keyEvent">Keyboard event</param>
|
||||
/// <param name="vkCode">VKCode</param>
|
||||
/// <param name="character">Character</param>
|
||||
private delegate bool KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode, string character);
|
||||
|
||||
/// <summary>
|
||||
/// Actual callback hook.
|
||||
/// <remarks>Calls asynchronously the asyncCallback.</remarks>
|
||||
/// </summary>
|
||||
/// <param name="nCode">VKCode</param>
|
||||
/// <param name="wParam">wParam</param>
|
||||
/// <param name="lParam">lParam</param>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HookCallbackAsync procedure that calls accordingly the KeyDown or KeyUp events.
|
||||
/// </summary>
|
||||
/// <param name="keyEvent">Keyboard event</param>
|
||||
/// <param name="vkCode">VKCode</param>
|
||||
/// <param name="character">Character as string.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raw KeyEvent arguments.
|
||||
/// </summary>
|
||||
public class RawKeyEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// WPF Key of the key.
|
||||
/// </summary>
|
||||
#pragma warning disable SA1401 // Fields should be private
|
||||
public uint Key;
|
||||
#pragma warning restore SA1401 // Fields should be private
|
||||
|
||||
/// <summary>
|
||||
/// Convert to string.
|
||||
/// </summary>
|
||||
/// <returns>Returns string representation of this key, if not possible empty string is returned.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return character;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unicode character of key pressed.
|
||||
/// </summary>
|
||||
private string character;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RawKeyEventArgs"/> class.
|
||||
/// Create raw keyevent arguments.
|
||||
/// </summary>
|
||||
/// <param name="vKCode">VKCode</param>
|
||||
/// <param name="character">Character</param>
|
||||
public RawKeyEventArgs(int vKCode, string character)
|
||||
{
|
||||
this.character = character;
|
||||
Key = (uint)vKCode; // User32.MapVirtualKey((uint)VKCode, User32.MAPVK.MAPVK_VK_TO_VSC_EX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Winapi Key interception helper class.
|
||||
/// </summary>
|
||||
internal static class InterceptKeys
|
||||
{
|
||||
public delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam);
|
||||
|
||||
private const int WH_KEYBOARD_LL = 13;
|
||||
|
||||
/// <summary>
|
||||
/// Key event
|
||||
/// </summary>
|
||||
public enum KeyEvent : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Key down
|
||||
/// </summary>
|
||||
WM_KEYDOWN = 256,
|
||||
|
||||
/// <summary>
|
||||
/// Key up
|
||||
/// </summary>
|
||||
WM_KEYUP = 257,
|
||||
|
||||
/// <summary>
|
||||
/// System key up
|
||||
/// </summary>
|
||||
WM_SYSKEYUP = 261,
|
||||
|
||||
/// <summary>
|
||||
/// System key down
|
||||
/// </summary>
|
||||
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];
|
||||
|
||||
/// <summary>
|
||||
/// Convert VKCode to Unicode.
|
||||
/// <remarks>isKeyDown is required for because of keyboard state inconsistencies!</remarks>
|
||||
/// </summary>
|
||||
/// <param name="vKCode">VKCode</param>
|
||||
/// <param name="isKeyDown">Is the key down event?</param>
|
||||
/// <returns>String representing single unicode character.</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
79
src/modules/poweraccent/PowerAccent.Core/Tools/Logger.cs
Normal file
79
src/modules/poweraccent/PowerAccent.Core/Tools/Logger.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using interop;
|
||||
|
||||
namespace PowerAccent.Core.Tools
|
||||
{
|
||||
public static class Logger
|
||||
{
|
||||
private static readonly IFileSystem _fileSystem = new FileSystem();
|
||||
private static readonly string ApplicationLogPath = Path.Combine(Constants.AppDataPath(), "PowerAccent\\Logs");
|
||||
|
||||
static Logger()
|
||||
{
|
||||
if (!_fileSystem.Directory.Exists(ApplicationLogPath))
|
||||
{
|
||||
_fileSystem.Directory.CreateDirectory(ApplicationLogPath);
|
||||
}
|
||||
|
||||
// Using InvariantCulture since this is used for a log file name
|
||||
var logFilePath = _fileSystem.Path.Combine(ApplicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".txt");
|
||||
|
||||
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));
|
||||
|
||||
Trace.AutoFlush = true;
|
||||
}
|
||||
|
||||
public static void LogError(string message)
|
||||
{
|
||||
Log(message, "ERROR");
|
||||
}
|
||||
|
||||
public static void LogError(string message, Exception ex)
|
||||
{
|
||||
Log(
|
||||
message + Environment.NewLine +
|
||||
ex?.Message + Environment.NewLine +
|
||||
"Inner exception: " + Environment.NewLine +
|
||||
ex?.InnerException?.Message + Environment.NewLine +
|
||||
"Stack trace: " + Environment.NewLine +
|
||||
ex?.StackTrace,
|
||||
"ERROR");
|
||||
}
|
||||
|
||||
public static void LogWarning(string message)
|
||||
{
|
||||
Log(message, "WARNING");
|
||||
}
|
||||
|
||||
public static void LogInfo(string message)
|
||||
{
|
||||
Log(message, "INFO");
|
||||
}
|
||||
|
||||
private static void Log(string message, string type)
|
||||
{
|
||||
Trace.WriteLine(type + ": " + DateTime.Now.TimeOfDay);
|
||||
Trace.Indent();
|
||||
Trace.WriteLine(GetCallerInfo());
|
||||
Trace.WriteLine(message);
|
||||
Trace.Unindent();
|
||||
}
|
||||
|
||||
private static string GetCallerInfo()
|
||||
{
|
||||
StackTrace stackTrace = new StackTrace();
|
||||
|
||||
var methodName = stackTrace.GetFrame(3)?.GetMethod();
|
||||
var className = methodName?.DeclaringType?.Name;
|
||||
return "[Method]: " + methodName?.Name + " [Class]: " + className;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// 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.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace PowerAccent.Core.Tools;
|
||||
|
||||
internal static class WindowsFunctions
|
||||
{
|
||||
public static void Insert(char c, bool back = false)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (back)
|
||||
{
|
||||
// Split in 2 different SendInput (Powershell doesn't take back issue)
|
||||
var inputsBack = new User32.INPUT[]
|
||||
{
|
||||
new User32.INPUT { type = User32.INPUTTYPE.INPUT_KEYBOARD, ki = new User32.KEYBDINPUT { wVk = (ushort)User32.VK.VK_BACK } },
|
||||
new User32.INPUT { type = User32.INPUTTYPE.INPUT_KEYBOARD, ki = new User32.KEYBDINPUT { wVk = (ushort)User32.VK.VK_BACK, dwFlags = User32.KEYEVENTF.KEYEVENTF_KEYUP } },
|
||||
};
|
||||
|
||||
var temp1 = User32.SendInput((uint)inputsBack.Length, inputsBack, sizeof(User32.INPUT));
|
||||
System.Threading.Thread.Sleep(1); // Some apps, like Terminal, need a little wait to process the sent backspace or they'll ignore it.
|
||||
}
|
||||
|
||||
// Letter
|
||||
var inputsInsert = new User32.INPUT[1]
|
||||
{
|
||||
new User32.INPUT { type = User32.INPUTTYPE.INPUT_KEYBOARD, ki = new User32.KEYBDINPUT { wVk = 0, dwFlags = User32.KEYEVENTF.KEYEVENTF_UNICODE, wScan = c } },
|
||||
};
|
||||
var temp2 = User32.SendInput((uint)inputsInsert.Length, inputsInsert, sizeof(User32.INPUT));
|
||||
}
|
||||
}
|
||||
|
||||
public static Point GetCaretPosition()
|
||||
{
|
||||
User32.GUITHREADINFO guiInfo = new ();
|
||||
guiInfo.cbSize = (uint)Marshal.SizeOf(guiInfo);
|
||||
User32.GetGUIThreadInfo(0, ref guiInfo);
|
||||
System.Drawing.Point caretPosition = new System.Drawing.Point(guiInfo.rcCaret.left, guiInfo.rcCaret.top);
|
||||
User32.ClientToScreen(guiInfo.hwndCaret, ref caretPosition);
|
||||
|
||||
if (caretPosition.X == 0)
|
||||
{
|
||||
System.Drawing.Point testPoint;
|
||||
User32.GetCaretPos(out testPoint);
|
||||
return testPoint;
|
||||
}
|
||||
|
||||
return caretPosition;
|
||||
}
|
||||
|
||||
public static (Point Location, Size Size, double Dpi) GetActiveDisplay()
|
||||
{
|
||||
User32.GUITHREADINFO guiInfo = new ();
|
||||
guiInfo.cbSize = (uint)Marshal.SizeOf(guiInfo);
|
||||
User32.GetGUIThreadInfo(0, ref guiInfo);
|
||||
var res = User32.MonitorFromWindow(guiInfo.hwndActive, User32.MonitorFlags.MONITOR_DEFAULTTONEAREST);
|
||||
|
||||
User32.MONITORINFO monitorInfo = new ();
|
||||
monitorInfo.cbSize = (uint)Marshal.SizeOf(monitorInfo);
|
||||
User32.GetMonitorInfo(res, ref monitorInfo);
|
||||
|
||||
double dpi = User32.GetDpiForWindow(guiInfo.hwndActive) / 96d;
|
||||
|
||||
return (monitorInfo.rcWork.Location, monitorInfo.rcWork.Size, dpi);
|
||||
}
|
||||
|
||||
public static bool IsCapitalState()
|
||||
{
|
||||
var capital = User32.GetKeyState((int)User32.VK.VK_CAPITAL);
|
||||
var shift = User32.GetKeyState((int)User32.VK.VK_SHIFT);
|
||||
return capital != 0 || shift < 0;
|
||||
}
|
||||
}
|
||||
17
src/modules/poweraccent/PowerAccent.UI/App.xaml
Normal file
17
src/modules/poweraccent/PowerAccent.UI/App.xaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<Application x:Class="PowerAccent.UI.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PowerAccent"
|
||||
StartupUri="Selector.xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!-- MahApps.Metro resource dictionaries. Make sure that all file names are Case Sensitive! -->
|
||||
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
|
||||
<!-- Theme setting -->
|
||||
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
33
src/modules/poweraccent/PowerAccent.UI/App.xaml.cs
Normal file
33
src/modules/poweraccent/PowerAccent.UI/App.xaml.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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.
|
||||
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
|
||||
namespace PowerAccent.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
private static Mutex _mutex;
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
const string appName = "PowerAccent";
|
||||
bool createdNew;
|
||||
|
||||
_mutex = new Mutex(true, appName, out createdNew);
|
||||
|
||||
if (!createdNew)
|
||||
{
|
||||
// app is already running! Exiting the application
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
base.OnStartup(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/modules/poweraccent/PowerAccent.UI/AssemblyInfo.cs
Normal file
10
src/modules/poweraccent/PowerAccent.UI/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
// 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.
|
||||
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, // where theme specific resource dictionaries are located (used if a resource is not found in the page, or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly) // where the generic resource dictionary is locate (used if a resource is not found in the page, app, or any theme specific resource dictionaries)
|
||||
]
|
||||
35
src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj
Normal file
35
src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj
Normal file
@@ -0,0 +1,35 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\Version.props" />
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<Nullable>disable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<ApplicationIcon>a-icon.ico</ApplicationIcon>
|
||||
<AssemblyName>PowerAccent</AssemblyName>
|
||||
<XamlDebuggingInformation>True</XamlDebuggingInformation>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="win11desktop.jpg" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="a-icon.ico">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="win11desktop.jpg" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="gong-wpf-dragdrop" Version="3.1.1" />
|
||||
<PackageReference Include="MahApps.Metro" Version="2.4.9" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PowerAccent.Core\PowerAccent.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
55
src/modules/poweraccent/PowerAccent.UI/Selector.xaml
Normal file
55
src/modules/poweraccent/PowerAccent.UI/Selector.xaml
Normal file
@@ -0,0 +1,55 @@
|
||||
<Window x:Class="PowerAccent.UI.Selector"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:PowerAccent"
|
||||
mc:Ignorable="d" SizeToContent="WidthAndHeight" ShowInTaskbar="False" ResizeMode="NoResize"
|
||||
Title="MainWindow" Height="50" Width="50" Visibility="Collapsed" WindowStyle="None">
|
||||
<Grid>
|
||||
<ListBox x:Name="characters" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" IsHitTestVisible="False"
|
||||
BorderThickness="1" BorderBrush="SlateGray">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel IsItemsHost="False" Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock TextAlignment="Center" VerticalAlignment="Center" Text="{Binding}" FontSize="18" />
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style x:Name="ItemStyle" TargetType="ListBoxItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ListBoxItem}">
|
||||
<Border x:Name="myBorder"
|
||||
Padding="0" Margin="0"
|
||||
SnapsToDevicePixels="true"
|
||||
Style="{DynamicResource borderContent}">
|
||||
<ContentPresenter />
|
||||
</Border>
|
||||
<ControlTemplate.Resources>
|
||||
<Style x:Key="borderContent" TargetType="Border">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="Background" Value="#ECECEC"/>
|
||||
<Setter Property="Width" Value="50"/>
|
||||
<Setter Property="Height" Value="50"/>
|
||||
</Style>
|
||||
</ControlTemplate.Resources>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSelected" Value="true">
|
||||
<Setter TargetName="myBorder" Property="Background" Value="White"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</Window>
|
||||
71
src/modules/poweraccent/PowerAccent.UI/Selector.xaml.cs
Normal file
71
src/modules/poweraccent/PowerAccent.UI/Selector.xaml.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Windows;
|
||||
using Point = PowerAccent.Core.Point;
|
||||
using Size = PowerAccent.Core.Size;
|
||||
|
||||
namespace PowerAccent.UI;
|
||||
|
||||
public partial class Selector : Window, IDisposable
|
||||
{
|
||||
private Core.PowerAccent _powerAccent = new Core.PowerAccent();
|
||||
|
||||
public Selector()
|
||||
{
|
||||
InitializeComponent();
|
||||
Application.Current.MainWindow.ShowActivated = false;
|
||||
Application.Current.MainWindow.Topmost = true;
|
||||
}
|
||||
|
||||
protected override void OnSourceInitialized(EventArgs e)
|
||||
{
|
||||
base.OnSourceInitialized(e);
|
||||
_powerAccent.OnChangeDisplay += PowerAccent_OnChangeDisplay;
|
||||
_powerAccent.OnSelectCharacter += PowerAccent_OnSelectionCharacter;
|
||||
this.Visibility = Visibility.Hidden;
|
||||
}
|
||||
|
||||
private void PowerAccent_OnSelectionCharacter(int index, char character)
|
||||
{
|
||||
characters.SelectedIndex = index;
|
||||
}
|
||||
|
||||
private void PowerAccent_OnChangeDisplay(bool isActive, char[] chars)
|
||||
{
|
||||
this.Visibility = isActive ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (isActive)
|
||||
{
|
||||
CenterWindow();
|
||||
characters.ItemsSource = chars;
|
||||
Microsoft.PowerToys.Telemetry.PowerToysTelemetry.Log.WriteEvent(new PowerAccent.Core.Telemetry.PowerAccentShowAccentMenuEvent());
|
||||
}
|
||||
}
|
||||
|
||||
private void MenuExit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
private void CenterWindow()
|
||||
{
|
||||
UpdateLayout();
|
||||
Size window = new Size(((System.Windows.Controls.Panel)Application.Current.MainWindow.Content).ActualWidth, ((System.Windows.Controls.Panel)Application.Current.MainWindow.Content).ActualHeight);
|
||||
Point position = _powerAccent.GetDisplayCoordinates(window);
|
||||
this.Left = position.X;
|
||||
this.Top = position.Y;
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
_powerAccent.Dispose();
|
||||
base.OnClosed(e);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
BIN
src/modules/poweraccent/PowerAccent.UI/a-icon.ico
Normal file
BIN
src/modules/poweraccent/PowerAccent.UI/a-icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
BIN
src/modules/poweraccent/PowerAccent.UI/win11desktop.jpg
Normal file
BIN
src/modules/poweraccent/PowerAccent.UI/win11desktop.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
22
src/modules/poweraccent/PowerAccent/PowerAccent.csproj
Normal file
22
src/modules/poweraccent/PowerAccent/PowerAccent.csproj
Normal file
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\Version.props" />
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<UseWPF>true</UseWPF>
|
||||
<Nullable>disable</Nullable>
|
||||
<StartupObject>PowerAccent.Program</StartupObject>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\modules\PowerAccent</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<AssemblyName>PowerToys.PowerAccent</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
<ProjectReference Include="..\PowerAccent.UI\PowerAccent.UI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
96
src/modules/poweraccent/PowerAccent/Program.cs
Normal file
96
src/modules/poweraccent/PowerAccent/Program.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
// 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;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using interop;
|
||||
using ManagedCommon;
|
||||
using PowerAccent.Core.Tools;
|
||||
using PowerAccent.UI;
|
||||
|
||||
namespace PowerAccent;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
private const string PROGRAM_NAME = "PowerAccent";
|
||||
private const string PROGRAM_APP_NAME = "PowerToys.PowerAccent";
|
||||
private static App _application;
|
||||
private static int _powerToysRunnerPid;
|
||||
private static CancellationTokenSource _tokenSource = new CancellationTokenSource();
|
||||
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
_ = new Mutex(true, PROGRAM_APP_NAME, out bool instantiated);
|
||||
|
||||
if (instantiated)
|
||||
{
|
||||
Arguments(args);
|
||||
|
||||
InitEvents();
|
||||
|
||||
_application = new App();
|
||||
_application.InitializeComponent();
|
||||
_application.Run();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning("Another running PowerAccent instance was detected. Exiting PowerAccent");
|
||||
}
|
||||
}
|
||||
|
||||
private static void InitEvents()
|
||||
{
|
||||
Task.Run(
|
||||
() =>
|
||||
{
|
||||
EventWaitHandle eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.PowerAccentExitEvent());
|
||||
if (eventHandle.WaitOne())
|
||||
{
|
||||
Terminate();
|
||||
}
|
||||
}, _tokenSource.Token);
|
||||
}
|
||||
|
||||
private static void Arguments(string[] args)
|
||||
{
|
||||
if (args?.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = int.TryParse(args[0], out _powerToysRunnerPid);
|
||||
|
||||
Logger.LogInfo($"PowerAccent started from the PowerToys Runner. Runner pid={_powerToysRunnerPid}");
|
||||
|
||||
RunnerHelper.WaitForPowerToysRunner(_powerToysRunnerPid, () =>
|
||||
{
|
||||
Logger.LogInfo("PowerToys Runner exited. Exiting PowerAccent");
|
||||
Terminate();
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInfo($"PowerAccent started detached from PowerToys Runner.");
|
||||
_powerToysRunnerPid = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Terminate()
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
_tokenSource.Cancel();
|
||||
Application.Current.Shutdown();
|
||||
});
|
||||
}
|
||||
}
|
||||
BIN
src/modules/poweraccent/PowerAccent/win11desktop.jpg
Normal file
BIN
src/modules/poweraccent/PowerAccent/win11desktop.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
@@ -0,0 +1,7 @@
|
||||
#include <string>
|
||||
|
||||
namespace PowerAccentConstants
|
||||
{
|
||||
// Name of the powertoy module.
|
||||
inline const std::wstring ModuleKey = L"PowerAccent";
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// Microsoft Visual C++ generated resource script.
|
||||
//
|
||||
#include <windows.h>
|
||||
#include "resource.h"
|
||||
#include "../../../common/version/version.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#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
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// English (United States) resources
|
||||
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
#pragma code_page(1252)
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TEXTINCLUDE
|
||||
//
|
||||
|
||||
1 TEXTINCLUDE
|
||||
BEGIN
|
||||
"resource.h\0"
|
||||
END
|
||||
|
||||
2 TEXTINCLUDE
|
||||
BEGIN
|
||||
"#include ""winres.h""\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
3 TEXTINCLUDE
|
||||
BEGIN
|
||||
"\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// String Table
|
||||
//
|
||||
|
||||
STRINGTABLE
|
||||
BEGIN
|
||||
IDS_POWERACCENT_NAME "PowerAccent"
|
||||
END
|
||||
|
||||
#endif // English (United States) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
#ifndef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 3 resource.
|
||||
//
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#endif // not APSTUDIO_INVOKED
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{34A354C5-23C7-4343-916C-C52DAF4FC39D}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>PowerAccent</RootNamespace>
|
||||
<ProjectName>PowerAccentModuleInterface</ProjectName>
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\PowerAccent\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup >
|
||||
<TargetName>PowerToys.$(ProjectName)</TargetName>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="PowerAccentConstants.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="trace.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<ClCompile Include="trace.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
|
||||
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
|
||||
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="PowerAccentModuleInterface.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="..\..\..\..\deps\spdlog.props" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220201.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220201.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>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}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220201.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220201.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClCompile Include="trace.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="PowerAccentConstants.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="trace.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="resource.h">
|
||||
<Filter>Generated Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{e8ef1c4e-cc50-4ce5-b00d-4e3ac5c1a7db}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{fbd9cdd2-e7d5-4417-9b52-25e345ae9562}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Generated Files">
|
||||
<UniqueIdentifier>{c2a23a2b-5846-440f-b29e-eea748dba12d}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{77f1702b-da7f-4ff6-90a3-19db515cf963}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="PowerAccentModuleInterface.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
186
src/modules/poweraccent/PowerAccentModuleInterface/dllmain.cpp
Normal file
186
src/modules/poweraccent/PowerAccentModuleInterface/dllmain.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
#include "pch.h"
|
||||
#include <interface/powertoy_module_interface.h>
|
||||
#include <common/SettingsAPI/settings_objects.h>
|
||||
#include <common/interop/shared_constants.h>
|
||||
#include "trace.h"
|
||||
#include "resource.h"
|
||||
#include "PowerAccentConstants.h"
|
||||
#include <common/logger/logger.h>
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
|
||||
#include <common/utils/elevation.h>
|
||||
#include <common/utils/process_path.h>
|
||||
#include <common/utils/resources.h>
|
||||
#include <common/utils/os-detect.h>
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <set>
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
Trace::RegisterProvider();
|
||||
break;
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
Trace::UnregisterProvider();
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
const static wchar_t* MODULE_NAME = L"PowerAccent";
|
||||
const static wchar_t* MODULE_DESC = L"A module that keeps your computer PowerAccent on-demand.";
|
||||
|
||||
class PowerAccent : public PowertoyModuleIface
|
||||
{
|
||||
std::wstring app_name;
|
||||
std::wstring app_key;
|
||||
|
||||
private:
|
||||
bool m_enabled = false;
|
||||
HANDLE m_hInvokeEvent;
|
||||
PROCESS_INFORMATION p_info;
|
||||
|
||||
bool is_process_running()
|
||||
{
|
||||
return WaitForSingleObject(p_info.hProcess, 0) == WAIT_TIMEOUT;
|
||||
}
|
||||
|
||||
void launch_process()
|
||||
{
|
||||
Logger::trace(L"Launching PowerToys PowerAccent process");
|
||||
unsigned long powertoys_pid = GetCurrentProcessId();
|
||||
|
||||
std::wstring executable_args = L"" + std::to_wstring(powertoys_pid);
|
||||
std::wstring application_path = L"modules\\PowerAccent\\PowerToys.PowerAccent.exe";
|
||||
std::wstring full_command_path = application_path + L" " + executable_args.data();
|
||||
Logger::trace(L"PowerToys PowerAccent launching: " + full_command_path);
|
||||
|
||||
STARTUPINFO info = { sizeof(info) };
|
||||
|
||||
if (!CreateProcess(application_path.c_str(), full_command_path.data(), NULL, NULL, true, NULL, NULL, NULL, &info, &p_info))
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
std::wstring message = L"PowerToys PowerAccent failed to start with error: ";
|
||||
message += std::to_wstring(error);
|
||||
Logger::error(message);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
PowerAccent()
|
||||
{
|
||||
app_name = MODULE_NAME;
|
||||
app_key = PowerAccentConstants::ModuleKey;
|
||||
LoggerHelpers::init_logger(app_key, L"ModuleInterface", "PowerAccent");
|
||||
Logger::info("Launcher object is constructing");
|
||||
};
|
||||
|
||||
virtual void destroy() override
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
virtual const wchar_t* get_name() override
|
||||
{
|
||||
return MODULE_NAME;
|
||||
}
|
||||
|
||||
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
|
||||
{
|
||||
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
||||
|
||||
PowerToysSettings::Settings settings(hinstance, get_name());
|
||||
settings.set_description(MODULE_DESC);
|
||||
|
||||
return settings.serialize_to_buffer(buffer, buffer_size);
|
||||
}
|
||||
|
||||
virtual const wchar_t* get_key() override
|
||||
{
|
||||
return app_key.c_str();
|
||||
}
|
||||
|
||||
virtual void set_config(const wchar_t* config) override
|
||||
{
|
||||
try
|
||||
{
|
||||
// Parse the input JSON string.
|
||||
PowerToysSettings::PowerToyValues values =
|
||||
PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
|
||||
|
||||
// If you don't need to do any custom processing of the settings, proceed
|
||||
// to persists the values.
|
||||
values.save_to_settings_file();
|
||||
}
|
||||
catch (std::exception&)
|
||||
{
|
||||
// Improper JSON.
|
||||
}
|
||||
}
|
||||
|
||||
virtual void enable()
|
||||
{
|
||||
ResetEvent(m_hInvokeEvent);
|
||||
launch_process();
|
||||
m_enabled = true;
|
||||
Trace::EnablePowerAccent(true);
|
||||
};
|
||||
|
||||
virtual void disable()
|
||||
{
|
||||
if (m_enabled)
|
||||
{
|
||||
Logger::trace(L"Disabling PowerAccent... {}", m_enabled);
|
||||
ResetEvent(m_hInvokeEvent);
|
||||
|
||||
auto exitEvent = CreateEvent(nullptr, false, false, CommonSharedConstants::POWERACCENT_EXIT_EVENT);
|
||||
if (!exitEvent)
|
||||
{
|
||||
Logger::warn(L"Failed to create exit event for PowerToys PowerAccent. {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::trace(L"Signaled exit event for PowerToys PowerAccent.");
|
||||
if (!SetEvent(exitEvent))
|
||||
{
|
||||
Logger::warn(L"Failed to signal exit event for PowerToys PowerAccent. {}", get_last_error_or_default(GetLastError()));
|
||||
|
||||
// For some reason, we couldn't process the signal correctly, so we still
|
||||
// need to terminate the PowerAccent process.
|
||||
TerminateProcess(p_info.hProcess, 1);
|
||||
}
|
||||
|
||||
ResetEvent(exitEvent);
|
||||
CloseHandle(exitEvent);
|
||||
CloseHandle(p_info.hProcess);
|
||||
}
|
||||
}
|
||||
|
||||
m_enabled = false;
|
||||
Trace::EnablePowerAccent(false);
|
||||
}
|
||||
|
||||
virtual bool is_enabled() override
|
||||
{
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
// Returns whether the PowerToys should be enabled by default
|
||||
virtual bool is_enabled_by_default() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
|
||||
{
|
||||
return new PowerAccent();
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.200729.8" targetFramework="native" />
|
||||
</packages>
|
||||
@@ -0,0 +1 @@
|
||||
#include "pch.h"
|
||||
7
src/modules/poweraccent/PowerAccentModuleInterface/pch.h
Normal file
7
src/modules/poweraccent/PowerAccentModuleInterface/pch.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <ProjectTelemetry.h>
|
||||
#include <shellapi.h>
|
||||
#include <Shlwapi.h>
|
||||
@@ -0,0 +1,26 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by PowerAccent.rc
|
||||
//
|
||||
#define IDS_POWERACCENT_NAME 101
|
||||
|
||||
//////////////////////////////
|
||||
// Non-localizable
|
||||
|
||||
#define FILE_DESCRIPTION "PowerToys PowerAccent Module"
|
||||
#define INTERNAL_NAME "PowerToys.PowerAccentModuleInterface"
|
||||
#define ORIGINAL_FILENAME "PowerToys.PowerAccentModuleInterface.dll"
|
||||
|
||||
// Non-localizable
|
||||
//////////////////////////////
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 102
|
||||
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||
#define _APS_NEXT_CONTROL_VALUE 1001
|
||||
#define _APS_NEXT_SYMED_VALUE 101
|
||||
#endif
|
||||
#endif
|
||||
29
src/modules/poweraccent/PowerAccentModuleInterface/trace.cpp
Normal file
29
src/modules/poweraccent/PowerAccentModuleInterface/trace.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "pch.h"
|
||||
#include "trace.h"
|
||||
|
||||
TRACELOGGING_DEFINE_PROVIDER(
|
||||
g_hProvider,
|
||||
"Microsoft.PowerToys",
|
||||
// {38e8889b-9731-53f5-e901-e8a7c1753074}
|
||||
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
|
||||
TraceLoggingOptionProjectTelemetry());
|
||||
|
||||
void Trace::RegisterProvider()
|
||||
{
|
||||
TraceLoggingRegister(g_hProvider);
|
||||
}
|
||||
|
||||
void Trace::UnregisterProvider()
|
||||
{
|
||||
TraceLoggingUnregister(g_hProvider);
|
||||
}
|
||||
|
||||
void Trace::EnablePowerAccent(const bool enabled) noexcept
|
||||
{
|
||||
TraceLoggingWrite(
|
||||
g_hProvider,
|
||||
"PowerAccent_EnablePowerAccent",
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
|
||||
TraceLoggingBoolean(enabled, "Enabled"));
|
||||
}
|
||||
11
src/modules/poweraccent/PowerAccentModuleInterface/trace.h
Normal file
11
src/modules/poweraccent/PowerAccentModuleInterface/trace.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
class Trace
|
||||
{
|
||||
public:
|
||||
static void RegisterProvider();
|
||||
static void UnregisterProvider();
|
||||
|
||||
// Log if the user has PowerAccent enabled or disabled
|
||||
static void EnablePowerAccent(const bool enabled) noexcept;
|
||||
};
|
||||
Reference in New Issue
Block a user