├── .github ├── Recording.gif └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature-request.md ├── PowerAccent.UI ├── Resources │ ├── Flags │ │ ├── BG.jpg │ │ ├── CY.jpg │ │ ├── CZ.jpg │ │ ├── DE.jpg │ │ ├── FI.jpg │ │ ├── FR.jpg │ │ ├── HR.jpg │ │ ├── HU.jpg │ │ ├── IS.jpg │ │ ├── IT.jpg │ │ ├── JP.jpg │ │ ├── MI.jpg │ │ ├── NL.jpg │ │ ├── PI.jpg │ │ ├── PL.jpg │ │ ├── PT.jpg │ │ ├── RO.jpg │ │ ├── RS.jpg │ │ ├── SA.jpg │ │ ├── SK.jpg │ │ ├── SP.jpg │ │ ├── SV.jpg │ │ ├── TK.jpg │ │ ├── ALL.jpg │ │ └── CUR.jpg │ ├── a-icon.ico │ └── win11desktop.jpg ├── Themes │ ├── CustomLibraryThemeProvider.cs │ ├── HighContrast1.xaml │ ├── HighContrast2.xaml │ ├── HighContrastBlack.xaml │ ├── HighContrastWhite.xaml │ ├── Dark.xaml │ ├── Light.xaml │ └── ThemeManager.cs ├── AssemblyInfo.cs ├── App.xaml ├── MainWindow.xaml ├── Selector.xaml.cs ├── App.xaml.cs ├── Settings.xaml.cs ├── SettingsPage │ ├── OptionsPage.xaml.cs │ ├── PositionPage.xaml.cs │ ├── CountriesPage.xaml.cs │ ├── CountriesPage.xaml │ ├── SortPage.xaml.cs │ ├── PositionPage.xaml │ ├── OptionsPage.xaml │ └── SortPage.xaml ├── Settings.xaml ├── MainWindow.xaml.cs ├── PowerAccent.UI.csproj └── Selector.xaml ├── PowerAccent.Core ├── Models │ ├── TriggerEnum.cs │ ├── Country.cs │ ├── Point.cs │ ├── Size.cs │ ├── KeysEnums.cs │ └── Rect.cs ├── Extensions │ └── CharExtensions.cs ├── ModuleHandlers │ ├── CustomModules │ │ ├── PauseModuleHandler.cs │ │ ├── CheckGameModeModuleHandler.cs │ │ ├── DisplaySelectorModuleHandler.cs │ │ ├── StrokeArrowsModuleHandler.cs │ │ ├── StrokeLetterModuleHandler.cs │ │ └── StrokeSpaceModuleHandler.cs │ ├── ModuleHandler.cs │ └── ModuleDirector.cs ├── PowerAccent.Core.csproj ├── Tools │ ├── Calculation.cs │ ├── WindowsFunctions.cs │ └── KeyboardListener.cs ├── PowerAccent.cs ├── Services │ └── SettingsService.cs └── Languages.cs ├── .editorconfig ├── .gitignore ├── LICENSE ├── PowerAccent.Wix ├── Product.wxs └── PowerAccent.Wix.SelfContained.wixproj ├── README.md └── PowerAccent.sln /.github/Recording.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/.github/Recording.gif -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/BG.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/BG.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/CY.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/CY.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/CZ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/CZ.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/DE.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/DE.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/FI.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/FI.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/FR.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/FR.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/HR.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/HR.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/HU.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/HU.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/IS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/IS.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/IT.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/IT.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/JP.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/JP.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/MI.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/MI.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/NL.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/NL.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/PI.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/PI.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/PL.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/PL.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/PT.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/PT.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/RO.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/RO.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/RS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/RS.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/SA.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/SA.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/SK.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/SK.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/SP.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/SP.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/SV.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/SV.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/TK.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/TK.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/a-icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/a-icon.ico -------------------------------------------------------------------------------- /PowerAccent.Core/Models/TriggerEnum.cs: -------------------------------------------------------------------------------- 1 | public enum TriggerEnum 2 | { 3 | Space = 0, 4 | Arrows = 1, 5 | LongPress = 2, 6 | } -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/ALL.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/ALL.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/Flags/CUR.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/Flags/CUR.jpg -------------------------------------------------------------------------------- /PowerAccent.UI/Resources/win11desktop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damienleroy/PowerAccent/HEAD/PowerAccent.UI/Resources/win11desktop.jpg -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CS8846: L'expression switch ne prend pas en charge toutes les valeurs possibles de son type d'entrée (elle n'est pas exhaustive). 4 | dotnet_diagnostic.CS8846.severity = none 5 | -------------------------------------------------------------------------------- /PowerAccent.Core/Models/Country.cs: -------------------------------------------------------------------------------- 1 | namespace PowerAccent.Core; 2 | 3 | public class Country 4 | { 5 | public string Name { get; set; } 6 | public string ImageUrl { get; set; } 7 | public bool IsChecked { get; set; } 8 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Additional context** 11 | - Version: x.x.x 12 | - Which tool: PowerAccent / PowerToys 13 | 14 | **Describe the bug and how to reproduce it** 15 | A clear and concise description of what the bug is. 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What is your problem** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | -------------------------------------------------------------------------------- /PowerAccent.Core/Extensions/CharExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace PowerAccent.Core.Extensions 2 | { 3 | internal static class CharExtensions 4 | { 5 | public static char[] ToUpper(this char[] array) 6 | { 7 | char[] result = new char[array.Length]; 8 | for (int i = 0; i < array.Length; i++) 9 | { 10 | result[i] = Char.ToUpper(array[i]); 11 | } 12 | return result; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.*~ 3 | project.lock.json 4 | .DS_Store 5 | *.pyc 6 | nupkg/ 7 | 8 | # Visual Studio Code 9 | .vscode 10 | 11 | # Rider 12 | .idea 13 | 14 | # User-specific files 15 | *.suo 16 | *.user 17 | *.userosscache 18 | *.sln.docstates 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | build/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Oo]ut/ 32 | msbuild.log 33 | msbuild.err 34 | msbuild.wrn 35 | 36 | # Visual Studio 2015 37 | .vs/ 38 | -------------------------------------------------------------------------------- /PowerAccent.Core/ModuleHandlers/CustomModules/PauseModuleHandler.cs: -------------------------------------------------------------------------------- 1 | using PowerAccent.Core.Services; 2 | 3 | namespace PowerAccent.Core.ModuleHandlers.CustomModules; 4 | 5 | internal class PauseModuleHandler : ModuleHandler 6 | { 7 | public PauseModuleHandler(PowerAccent powerAccent, SettingsService settingsService, KeyOptions options) : base(powerAccent, settingsService, options) 8 | { 9 | } 10 | 11 | public override bool InvokeKeyDown(uint key) 12 | { 13 | if (PowerAccent.IsPaused) 14 | return true; 15 | 16 | return base.InvokeKeyDown(key); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /PowerAccent.Core/PowerAccent.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | disable 7 | True 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /PowerAccent.UI/Themes/CustomLibraryThemeProvider.cs: -------------------------------------------------------------------------------- 1 | using ControlzEx.Theming; 2 | using System.Collections.Generic; 3 | 4 | namespace PowerAccent.UI.Themes; 5 | 6 | public class CustomLibraryThemeProvider : LibraryThemeProvider 7 | { 8 | public static readonly CustomLibraryThemeProvider DefaultInstance = new CustomLibraryThemeProvider(); 9 | 10 | public CustomLibraryThemeProvider() 11 | : base(true) 12 | { 13 | } 14 | 15 | /// 16 | public override void FillColorSchemeValues(Dictionary values, RuntimeThemeColorValues colorValues) 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PowerAccent.UI/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /PowerAccent.UI/App.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /PowerAccent.UI/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /PowerAccent.Core/ModuleHandlers/CustomModules/CheckGameModeModuleHandler.cs: -------------------------------------------------------------------------------- 1 | using PowerAccent.Core.Services; 2 | using PowerAccent.Core.Tools; 3 | 4 | namespace PowerAccent.Core.ModuleHandlers.CustomModules; 5 | 6 | internal class CheckGameModeModuleHandler : ModuleHandler 7 | { 8 | public CheckGameModeModuleHandler(PowerAccent powerAccent, SettingsService settingsService, KeyOptions options) : base(powerAccent, settingsService, options) 9 | { 10 | } 11 | 12 | public override bool InvokeKeyDown(uint key) 13 | { 14 | if (SettingsService.DisableInFullScreen 15 | && !Options.IsVisible && Options.LetterPressed.HasValue && Options.TriggerPressed.HasValue 16 | && WindowsFunctions.IsGameMode()) 17 | { 18 | Options.Reset(); 19 | return true; 20 | } 21 | 22 | return base.InvokeKeyDown(key); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PowerAccent.UI/Selector.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace PowerAccent.UI; 4 | 5 | public partial class Selector : Window 6 | { 7 | public Selector(char[] selectedCharacters) 8 | { 9 | InitializeComponent(); 10 | this.ShowActivated = false; 11 | this.Topmost = true; 12 | characters.ItemsSource = selectedCharacters; 13 | characters.SelectedIndex = 0; 14 | } 15 | 16 | public void SetIndex(int index) 17 | { 18 | characters.SelectedIndex = index; 19 | } 20 | 21 | public void SetPosition(double left, double top) 22 | { 23 | this.Left = left; 24 | this.Top = top; 25 | } 26 | 27 | public void SetBorderWindowAlignment(bool? isLeft) 28 | { 29 | gridBorder.HorizontalAlignment = isLeft switch 30 | { 31 | true => HorizontalAlignment.Left, 32 | false => HorizontalAlignment.Right, 33 | _ => HorizontalAlignment.Center, 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /PowerAccent.Core/ModuleHandlers/ModuleHandler.cs: -------------------------------------------------------------------------------- 1 | using PowerAccent.Core.Services; 2 | 3 | namespace PowerAccent.Core.ModuleHandlers; 4 | 5 | internal abstract class ModuleHandler 6 | { 7 | public ModuleHandler(PowerAccent powerAccent, SettingsService settingsService, KeyOptions options) 8 | { 9 | PowerAccent = powerAccent; 10 | SettingsService = settingsService; 11 | Options = options; 12 | } 13 | 14 | protected ModuleHandler _next; 15 | 16 | public PowerAccent PowerAccent { get; } 17 | public SettingsService SettingsService { get; } 18 | public KeyOptions Options { get; } 19 | 20 | public ModuleHandler SetNext(ModuleHandler next) 21 | { 22 | _next = next; 23 | return next; 24 | } 25 | 26 | public virtual bool InvokeKeyDown(uint key) 27 | { 28 | return _next?.InvokeKeyDown(key) ?? true; 29 | } 30 | 31 | public virtual bool InvokeKeyUp(uint key) 32 | { 33 | return _next?.InvokeKeyUp(key) ?? true; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Damien Leroy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PowerAccent.Core/Models/Point.cs: -------------------------------------------------------------------------------- 1 | namespace PowerAccent.Core; 2 | 3 | public struct Point 4 | { 5 | public Point() 6 | { 7 | X = 0; 8 | Y = 0; 9 | } 10 | 11 | public Point(double x, double y) 12 | { 13 | X = x; 14 | Y = y; 15 | } 16 | 17 | public Point(int x, int y) 18 | { 19 | X = x; 20 | Y = y; 21 | } 22 | 23 | public Point(System.Drawing.Point point) 24 | { 25 | X = point.X; 26 | Y = point.Y; 27 | } 28 | 29 | public double X { get; init; } 30 | public double Y { get; init; } 31 | 32 | public static implicit operator Point(System.Drawing.Point point) => new Point(point.X, point.Y); 33 | 34 | public static Point operator /(Point point, double divider) 35 | { 36 | if (divider == 0) 37 | { 38 | throw new DivideByZeroException(); 39 | } 40 | return new Point(point.X / divider, point.Y / divider); 41 | } 42 | 43 | public static Point operator /(Point point, Point divider) 44 | { 45 | if (divider.X == 0 || divider.Y == 0) 46 | { 47 | throw new DivideByZeroException(); 48 | } 49 | return new Point(point.X / divider.X, point.Y / divider.Y); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /PowerAccent.UI/Themes/HighContrast1.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | HighContrast.Accent2 8 | PowerAccent 9 | Accent2 (HighContrast) 10 | HighContrast 11 | Accent2 12 | White 13 | 14 | 15 | 19 | 20 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /PowerAccent.UI/Themes/HighContrast2.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | HighContrast.Accent3 8 | PowerAccent 9 | Accent3 (HighContrast) 10 | HighContrast 11 | Accent3 12 | White 13 | 14 | 15 | 19 | 20 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /PowerAccent.UI/Themes/HighContrastBlack.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | HighContrast.Accent4 8 | PowerAccent 9 | Accent4 (HighContrast) 10 | HighContrast 11 | Accent4 12 | White 13 | 14 | 15 | 19 | 20 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /PowerAccent.UI/Themes/HighContrastWhite.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | HighContrast.Accent5 8 | PowerAccent 9 | Accent5 (HighContrast) 10 | HighContrast 11 | Accent5 12 | White 13 | 14 | 15 | 19 | 20 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /PowerAccent.UI/Themes/Dark.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Dark.Accent1 8 | PowerAccent 9 | Accent1 (Dark) 10 | Dark 11 | Accent1 12 | Black 13 | 14 | 15 | 19 | 23 | 27 | 28 | 32 | -------------------------------------------------------------------------------- /PowerAccent.Core/Models/Size.cs: -------------------------------------------------------------------------------- 1 | namespace PowerAccent.Core; 2 | 3 | public struct Size 4 | { 5 | public Size() 6 | { 7 | Width = 0; 8 | Height = 0; 9 | } 10 | 11 | public Size(double width, double height) 12 | { 13 | Width = width; 14 | Height = height; 15 | } 16 | 17 | public Size(int width, int height) 18 | { 19 | Width = width; 20 | Height = height; 21 | } 22 | 23 | public double Width { get; init; } 24 | public double Height { get; init; } 25 | 26 | public static implicit operator Size(System.Drawing.Size size) => new Size(size.Width, size.Height); 27 | 28 | public static Size operator /(Size size, double divider) 29 | { 30 | if (divider == 0) 31 | { 32 | throw new DivideByZeroException(); 33 | } 34 | return new Size(size.Width / divider, size.Height / divider); 35 | } 36 | 37 | public static Size operator /(Size size, Size divider) 38 | { 39 | if (divider.Width == 0 || divider.Height == 0 || divider.Width == 0 || divider.Height == 0) 40 | { 41 | throw new DivideByZeroException(); 42 | } 43 | return new Size(size.Width / divider.Width, size.Height / divider.Height); 44 | } 45 | 46 | public override string ToString() 47 | { 48 | return $"Width: {Width}, Height: {Height}"; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /PowerAccent.UI/Themes/Light.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Light.Accent1 8 | PowerAccent 9 | Accent1 (Light) 10 | Light 11 | Accent1 12 | White 13 | 14 | 15 | 19 | 23 | 27 | 31 | 35 | -------------------------------------------------------------------------------- /PowerAccent.Core/Models/KeysEnums.cs: -------------------------------------------------------------------------------- 1 | using Vanara.PInvoke; 2 | 3 | namespace PowerAccent.Core; 4 | 5 | public enum LetterKey : uint 6 | { 7 | _0 = User32.VK.VK_0, 8 | _1 = User32.VK.VK_1, 9 | _2 = User32.VK.VK_2, 10 | _3 = User32.VK.VK_3, 11 | _4 = User32.VK.VK_4, 12 | _5 = User32.VK.VK_5, 13 | _6 = User32.VK.VK_6, 14 | _7 = User32.VK.VK_7, 15 | _8 = User32.VK.VK_8, 16 | _9 = User32.VK.VK_9, 17 | A = User32.VK.VK_A, 18 | B = User32.VK.VK_B, 19 | C = User32.VK.VK_C, 20 | D = User32.VK.VK_D, 21 | E = User32.VK.VK_E, 22 | F = User32.VK.VK_F, 23 | G = User32.VK.VK_G, 24 | H = User32.VK.VK_H, 25 | I = User32.VK.VK_I, 26 | J = User32.VK.VK_J, 27 | K = User32.VK.VK_K, 28 | L = User32.VK.VK_L, 29 | M = User32.VK.VK_M, 30 | N = User32.VK.VK_N, 31 | O = User32.VK.VK_O, 32 | P = User32.VK.VK_P, 33 | R = User32.VK.VK_R, 34 | S = User32.VK.VK_S, 35 | T = User32.VK.VK_T, 36 | U = User32.VK.VK_U, 37 | V = User32.VK.VK_V, 38 | W = User32.VK.VK_W, 39 | X = User32.VK.VK_X, 40 | Y = User32.VK.VK_Y, 41 | Z = User32.VK.VK_Z, 42 | _ = User32.VK.VK_OEM_COMMA, 43 | } 44 | 45 | public enum TriggerKey : uint 46 | { 47 | Left = User32.VK.VK_LEFT, 48 | Right = User32.VK.VK_RIGHT, 49 | Space = User32.VK.VK_SPACE, 50 | } 51 | 52 | public enum BackwardKey : uint 53 | { 54 | LeftShift = User32.VK.VK_LSHIFT, 55 | RightShift = User32.VK.VK_RSHIFT, 56 | } -------------------------------------------------------------------------------- /PowerAccent.Core/ModuleHandlers/CustomModules/DisplaySelectorModuleHandler.cs: -------------------------------------------------------------------------------- 1 | using PowerAccent.Core.Services; 2 | using PowerAccent.Core.Tools; 3 | using System.Diagnostics; 4 | 5 | namespace PowerAccent.Core.ModuleHandlers.CustomModules; 6 | 7 | internal class DisplaySelectorModuleHandler : ModuleHandler 8 | { 9 | public DisplaySelectorModuleHandler(PowerAccent powerAccent, SettingsService settingsService, KeyOptions options) : base(powerAccent, settingsService, options) 10 | { 11 | } 12 | 13 | public override bool InvokeKeyDown(uint key) 14 | { 15 | if (!Options.IsVisible && Options.LetterPressed.HasValue && Options.TriggerPressed.HasValue) 16 | { 17 | Debug.WriteLine($"InvokeKeyDown DisplaySelectorModuleHandler - Begin delay {SettingsService.InputTime}"); 18 | Options.IsVisible = true; 19 | Task.Delay(SettingsService.InputTime).ContinueWith(t => 20 | { 21 | Debug.WriteLine($"InvokeKeyDown DisplaySelectorModuleHandler - End delay. Visible: {Options.IsVisible}"); 22 | if (Options.IsVisible && WindowsFunctions.IsKeyPressed(Options.LetterPressed.Value)) 23 | { 24 | Options.IsDelayOk = true; 25 | PowerAccent.ChangeDisplay(true, Options.Characters); 26 | } 27 | 28 | }, TaskScheduler.FromCurrentSynchronizationContext()); 29 | } 30 | 31 | if (Options.CancelTrigger) 32 | { 33 | Options.CancelTrigger = false; 34 | return false; 35 | } 36 | 37 | return base.InvokeKeyDown(key); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /PowerAccent.Core/Models/Rect.cs: -------------------------------------------------------------------------------- 1 | namespace PowerAccent.Core; 2 | 3 | public struct Rect 4 | { 5 | public Rect() 6 | { 7 | X = 0; 8 | Y = 0; 9 | Width = 0; 10 | Height = 0; 11 | } 12 | 13 | public Rect(int x, int y, int width, int height) 14 | { 15 | X = x; 16 | Y = y; 17 | Width = width; 18 | Height = height; 19 | } 20 | 21 | public Rect(double x, double y, double width, double height) 22 | { 23 | X = x; 24 | Y = y; 25 | Width = width; 26 | Height = height; 27 | } 28 | 29 | public Rect(Point coord, Size size) 30 | { 31 | X = coord.X; 32 | Y = coord.Y; 33 | Width = size.Width; 34 | Height = size.Height; 35 | } 36 | 37 | public double X { get; init; } 38 | public double Y { get; init; } 39 | public double Width { get; init; } 40 | public double Height { get; init; } 41 | 42 | public static Rect operator /(Rect rect, double divider) 43 | { 44 | if (divider == 0) 45 | { 46 | throw new DivideByZeroException(); 47 | } 48 | return new Rect(rect.X / divider, rect.Y / divider, rect.Width / divider, rect.Height / divider); 49 | } 50 | 51 | public static Rect operator /(Rect rect, Rect divider) 52 | { 53 | if (divider.X == 0 || divider.Y == 0) 54 | { 55 | throw new DivideByZeroException(); 56 | } 57 | return new Rect(rect.X / divider.X, rect.Y / divider.Y, rect.Width / divider.Width, rect.Height / divider.Height); 58 | } 59 | 60 | public override string ToString() 61 | { 62 | return $"X: {X}, Y: {Y}, Width: {Width}, Height: {Height}"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /PowerAccent.UI/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using PowerAccent.UI.Themes; 2 | using System; 3 | using System.Threading; 4 | using System.Windows; 5 | 6 | namespace PowerAccent.UI 7 | { 8 | /// 9 | /// Interaction logic for App.xaml 10 | /// 11 | public partial class App : Application 12 | { 13 | private static Mutex _mutex = null; 14 | private bool _disposed; 15 | private ThemeManager _themeManager; 16 | 17 | protected override void OnStartup(StartupEventArgs e) 18 | { 19 | const string appName = "PowerAccent"; 20 | bool createdNew; 21 | 22 | _mutex = new Mutex(true, appName, out createdNew); 23 | 24 | if (!createdNew) 25 | { 26 | //app is already running! Exiting the application 27 | Application.Current.Shutdown(); 28 | } 29 | 30 | _themeManager = new ThemeManager(); 31 | 32 | base.OnStartup(e); 33 | } 34 | 35 | // dispose 36 | protected override void OnExit(ExitEventArgs e) 37 | { 38 | _mutex.ReleaseMutex(); 39 | base.OnExit(e); 40 | } 41 | 42 | protected virtual void Dispose(bool disposing) 43 | { 44 | if (_disposed) 45 | { 46 | return; 47 | } 48 | 49 | if (disposing) 50 | { 51 | _mutex?.Dispose(); 52 | _themeManager?.Dispose(); 53 | } 54 | 55 | _disposed = true; 56 | } 57 | 58 | public void Dispose() 59 | { 60 | Dispose(disposing: true); 61 | GC.SuppressFinalize(this); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /PowerAccent.UI/Settings.xaml.cs: -------------------------------------------------------------------------------- 1 | using PowerAccent.UI.SettingsPage; 2 | using System; 3 | using System.Windows; 4 | using System.Windows.Input; 5 | 6 | namespace PowerAccent.UI 7 | { 8 | /// 9 | /// Logique d'interaction pour Settings.xaml 10 | /// 11 | public partial class Settings : Window 12 | { 13 | public Settings() 14 | { 15 | InitializeComponent(); 16 | } 17 | 18 | protected override void OnInitialized(EventArgs e) 19 | { 20 | base.OnInitialized(e); 21 | Countries.IsChecked = true; 22 | } 23 | 24 | private void Countries_Checked(object sender, RoutedEventArgs e) 25 | { 26 | Position.IsChecked = false; 27 | Options.IsChecked = false; 28 | Sort.IsChecked = false; 29 | this.ParentFrame.Navigate(new CountriesPage()); 30 | } 31 | 32 | private void Position_Checked(object sender, RoutedEventArgs e) 33 | { 34 | Countries.IsChecked = false; 35 | Options.IsChecked = false; 36 | Sort.IsChecked = false; 37 | this.ParentFrame.Navigate(new PositionPage()); 38 | } 39 | 40 | private void Options_Checked(object sender, RoutedEventArgs e) 41 | { 42 | Countries.IsChecked = false; 43 | Position.IsChecked = false; 44 | Sort.IsChecked = false; 45 | this.ParentFrame.Navigate(new OptionsPage()); 46 | } 47 | 48 | private void Sort_Checked(object sender, RoutedEventArgs e) 49 | { 50 | Countries.IsChecked = false; 51 | Options.IsChecked = false; 52 | Position.IsChecked = false; 53 | this.ParentFrame.Navigate(new SortPage()); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /PowerAccent.Core/ModuleHandlers/CustomModules/StrokeArrowsModuleHandler.cs: -------------------------------------------------------------------------------- 1 | using PowerAccent.Core.Extensions; 2 | using PowerAccent.Core.Services; 3 | using PowerAccent.Core.Tools; 4 | using System.Diagnostics; 5 | 6 | namespace PowerAccent.Core.ModuleHandlers.CustomModules; 7 | 8 | internal class StrokeArrowsModuleHandler : ModuleHandler 9 | { 10 | public StrokeArrowsModuleHandler(PowerAccent powerAccent, SettingsService settingsService, KeyOptions options) : base(powerAccent, settingsService, options) 11 | { 12 | } 13 | 14 | public override bool InvokeKeyDown(uint key) 15 | { 16 | if (Options.LetterPressed.HasValue && (key == (uint)TriggerKey.Left || key == (uint)TriggerKey.Right)) 17 | { 18 | Options.TriggerPressed = (TriggerKey)key; 19 | Debug.WriteLine($"InvokeKeyDown StrokeArrowsModuleHandler - Key: {(TriggerKey)key}"); 20 | if (!Options.IsVisible) 21 | { 22 | if (!WindowsFunctions.IsKeyPressed(Options.LetterPressed.Value)) 23 | { 24 | Options.Reset(); 25 | return true; 26 | } 27 | Options.Characters = WindowsFunctions.IsCapitalState() 28 | ? SettingsService.GetLetterKey(Options.LetterPressed.Value).ToUpper() 29 | : SettingsService.GetLetterKey(Options.LetterPressed.Value); 30 | } 31 | 32 | if (Options.Characters == Array.Empty()) 33 | { 34 | Debug.WriteLine($"InvokeKeyDown StrokeArrowsModuleHandler - No characters for key: {Options.LetterPressed.Value}"); 35 | Options.Reset(); 36 | return true; 37 | } 38 | 39 | Options.SelectedIndex += key == (uint)TriggerKey.Left ? -1 : 1; 40 | 41 | if (Options.SelectedIndex < 0) Options.SelectedIndex = Options.Characters.Length - 1; 42 | if (Options.SelectedIndex > Options.Characters.Length - 1) Options.SelectedIndex = 0; 43 | Debug.WriteLine($"InvokeKeyDown StrokeArrowsModuleHandler - SelectedIndex: {Options.SelectedIndex}"); 44 | PowerAccent.SelectCharacter(Options.SelectedIndex); 45 | Options.CancelTrigger = true; 46 | } 47 | 48 | return base.InvokeKeyDown(key); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /PowerAccent.Core/ModuleHandlers/CustomModules/StrokeLetterModuleHandler.cs: -------------------------------------------------------------------------------- 1 | using PowerAccent.Core.Services; 2 | using PowerAccent.Core.Tools; 3 | using System.Diagnostics; 4 | 5 | namespace PowerAccent.Core.ModuleHandlers.CustomModules 6 | { 7 | internal class StrokeLetterModuleHandler : ModuleHandler 8 | { 9 | public StrokeLetterModuleHandler(PowerAccent powerAccent, SettingsService settingsService, KeyOptions options) : base(powerAccent, settingsService, options) 10 | { 11 | } 12 | 13 | public override bool InvokeKeyDown(uint key) 14 | { 15 | if (Enum.IsDefined(typeof(LetterKey), key)) 16 | { 17 | Options.LetterPressed = (LetterKey)key; 18 | Debug.WriteLine($"InvokeKeyDown StrokeLetterModuleHandler - Key: {(LetterKey)key}"); 19 | } 20 | 21 | return base.InvokeKeyDown(key); 22 | } 23 | 24 | public override bool InvokeKeyUp(uint key) 25 | { 26 | Debug.WriteLine($"InvokeKeyUp StrokeLetterModuleHandler - KeyUp: {(LetterKey)key}"); 27 | if (Enum.IsDefined(typeof(LetterKey), key)) 28 | { 29 | Options.LetterPressed = null; 30 | if (Options.IsVisible) 31 | { 32 | PowerAccent.ChangeDisplay(false, null); 33 | 34 | if (!Options.IsDelayOk) 35 | { 36 | Debug.WriteLine("InvokeKeyUp StrokeLetterModuleHandler - Delay not ok"); 37 | if (Options.TriggerPressed == TriggerKey.Space) 38 | WindowsFunctions.Insert(' '); 39 | Options.Reset(); 40 | return false; 41 | } 42 | 43 | Debug.WriteLine($"InvokeKeyUp StrokeLetterModuleHandler - KeyUp: {(LetterKey)key} - {Options.SelectedIndex} - {Options.Characters.Length}"); 44 | if (Options.SelectedIndex != -1) 45 | WindowsFunctions.Insert(Options.Characters[Options.SelectedIndex], true); 46 | if (SettingsService.InsertSpaceAfterSelection) 47 | WindowsFunctions.Insert(' ', false); 48 | 49 | Options.Reset(); 50 | } 51 | } 52 | 53 | return base.InvokeKeyUp(key); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /PowerAccent.Core/Tools/Calculation.cs: -------------------------------------------------------------------------------- 1 | using PowerAccent.Core.Services; 2 | 3 | namespace PowerAccent.Core.Tools 4 | { 5 | internal static class Calculation 6 | { 7 | public static Point GetRawCoordinatesFromCaret(Point caret, Rect screen, Size window) 8 | { 9 | var left = caret.X - window.Width / 2; 10 | var top = caret.Y - window.Height - 20; 11 | 12 | return new Point(left < screen.X ? screen.X : (left + window.Width > (screen.X + screen.Width) ? (screen.X + screen.Width) - window.Width : left) 13 | , top < screen.Y ? caret.Y + 20 : top); 14 | } 15 | 16 | public static Point GetRawCoordinatesFromPosition(Position position, Rect screen, Size window) 17 | { 18 | int offset = 10; 19 | 20 | double pointX = position switch 21 | { 22 | var x when 23 | x == Position.Top || 24 | x == Position.Bottom || 25 | x == Position.Center 26 | => screen.X + screen.Width / 2 - window.Width / 2, 27 | var x when 28 | x == Position.TopLeft || 29 | x == Position.Left || 30 | x == Position.BottomLeft 31 | => screen.X + offset, 32 | var x when 33 | x == Position.TopRight || 34 | x == Position.Right || 35 | x == Position.BottomRight 36 | => screen.X + screen.Width - (window.Width + offset), 37 | }; 38 | 39 | double pointY = position switch 40 | { 41 | var x when 42 | x == Position.TopLeft || 43 | x == Position.Top || 44 | x == Position.TopRight 45 | => screen.Y + offset, 46 | var x when 47 | x == Position.Left || 48 | x == Position.Center || 49 | x == Position.Right 50 | => screen.Y + screen.Height / 2 - window.Height / 2, 51 | var x when 52 | x == Position.BottomLeft || 53 | x == Position.Bottom || 54 | x == Position.BottomRight 55 | => screen.Y + screen.Height - (window.Height + offset), 56 | }; 57 | 58 | return new Point(pointX, pointY); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /PowerAccent.UI/SettingsPage/OptionsPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using ModernWpf.Controls; 2 | using PowerAccent.Core; 3 | using PowerAccent.Core.Services; 4 | using System; 5 | using System.Globalization; 6 | using System.Linq; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | using System.Windows.Media.Imaging; 11 | 12 | namespace PowerAccent.UI.SettingsPage; 13 | 14 | /// 15 | /// Logique d'interaction pour OptionsPage.xaml 16 | /// 17 | public partial class OptionsPage : System.Windows.Controls.Page 18 | { 19 | private readonly SettingsService _settingService = new SettingsService(); 20 | 21 | public OptionsPage() 22 | { 23 | InitializeComponent(); 24 | } 25 | 26 | protected override void OnInitialized(EventArgs e) 27 | { 28 | base.OnInitialized(e); 29 | 30 | IsUseCaretPosition.IsOn = _settingService.UseCaretPosition; 31 | IsSpaceBarActive.IsOn = _settingService.IsSpaceBarActive; 32 | DisableInFullScreen.IsOn = _settingService.DisableInFullScreen; 33 | InputTime.Value = _settingService.InputTime; 34 | } 35 | 36 | private void UseCaretPosition_Checked(object sender, RoutedEventArgs e) 37 | { 38 | _settingService.UseCaretPosition = ((ToggleSwitch)sender).IsOn; 39 | (Application.Current.MainWindow as MainWindow).RefreshSettings(); 40 | } 41 | 42 | private void SpaceBarActive_Checked(object sender, RoutedEventArgs e) 43 | { 44 | _settingService.IsSpaceBarActive = ((ToggleSwitch)sender).IsOn; 45 | (Application.Current.MainWindow as MainWindow).RefreshSettings(); 46 | } 47 | 48 | private void DisableInFullScreen_Toggled(object sender, RoutedEventArgs e) 49 | { 50 | _settingService.DisableInFullScreen = ((ToggleSwitch)sender).IsOn; 51 | (Application.Current.MainWindow as MainWindow).RefreshSettings(); 52 | } 53 | 54 | private void InsertSpaceAfterSelection_Toggled(object sender, RoutedEventArgs e) 55 | { 56 | _settingService.InsertSpaceAfterSelection = ((ToggleSwitch)sender).IsOn; 57 | (Application.Current.MainWindow as MainWindow).RefreshSettings(); 58 | } 59 | 60 | private void InputTime_ValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args) 61 | { 62 | int value = (int)args.NewValue; 63 | _settingService.InputTime = value >= 0 ? value : 200; 64 | (Application.Current.MainWindow as MainWindow).RefreshSettings(); 65 | } 66 | } -------------------------------------------------------------------------------- /PowerAccent.Wix/Product.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | NOT REMOVE 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # How it works 2 | 3 | Press and hold a letter that supports diacritic marks, then press space bar or arrow key to select the accent. For example E + Space. With spacebar, repeat presses to change accent mark. When releasing the letter key, the accented letter is inserted. 4 | 5 | ![](.github/Recording.gif) 6 | > Note: Some characters (like `¡` for Spanish are available with the comma (`,`) key). 7 | 8 | The software is currently working with the most of accents for several (Latin script based) countries, including **Czech**, **German**, **France**, **Maori** and many others. The list can be found in [Languages.cs](/PowerAccent.Core/Languages.cs). 9 | If any language is missing, don't hesitate to [create an issue][newissue]. 10 | 11 | All letters are sorted by usage frequency by default, as found on [Wikipedia](https://wikipedia.org/wiki/Letter_frequency). 12 | 13 | # Download 14 | 15 | See the [Releases][releases] page. 16 | 17 | # Known problems 18 | 19 | - Some keys can have interference with some actions or some tools (for example: AutoHotkey) 20 | - (Experimental feature) Inside browsers and some other software, the tool can't detect the caret position. Default position is applied, meaning the one you choose in the position settings. 21 | - Mathematical symbols are often requested. But for several reasons (technical and functional), this feature can't be implemented now in the tool. So currently mathematical symbol will not be accepted (cf: [issue](https://github.com/damienleroy/PowerAccent/issues/23#issuecomment-1301532208)). 22 | 23 | # The future 24 | 25 | - More letters and accents 26 | - Theme and design improvement 27 | - Some other [asked features][issues] 28 | 29 | # PowerToys 30 | PowerAccent has been implemented in [PowerToys](https://github.com/microsoft/PowerToys) and renamed as [QuickAccent](https://aka.ms/PowerToysOverview_QuickAccent). Both have globally the same features and offer different experiences in the usage (taskbar icon, and different settings view). 31 | 32 | # If you like it 33 | 34 | Buy me a coffee. ☕ 35 | https://www.buymeacoffee.com/dams 36 | 37 | # Thanks to 38 | 39 | - [Ciantic](https://gist.github.com/Ciantic/471698) for the implementation of the Keyboard Listener. 40 | - [Saurabh Singh](https://www.codeproject.com/Articles/34520/Getting-Caret-Position-Inside-Any-Application) to share how get the caret position. 41 | - [PowerToys team](https://github.com/microsoft/PowerToys) to integrated PowerAccent in PowerToys (named as Quick Accent). 42 | 43 | [issues]: https://github.com/damienleroy/PowerAccent/issues 44 | [newissue]: https://github.com/damienleroy/PowerAccent/issues/new 45 | [releases]: https://github.com/damienleroy/PowerAccent/releases 46 | -------------------------------------------------------------------------------- /PowerAccent.Core/ModuleHandlers/ModuleDirector.cs: -------------------------------------------------------------------------------- 1 | using PowerAccent.Core.ModuleHandlers.CustomModules; 2 | using PowerAccent.Core.Services; 3 | using System.Diagnostics; 4 | 5 | namespace PowerAccent.Core.ModuleHandlers; 6 | 7 | // Chain of responsability pattern 8 | internal class ModuleDirector 9 | { 10 | private ModuleHandler _moduleHandler; 11 | 12 | public ModuleDirector(PowerAccent powerAccent, SettingsService settingsService, KeyOptions options) 13 | { 14 | _moduleHandler = new PauseModuleHandler(powerAccent, settingsService, options); 15 | _moduleHandler 16 | .SetNext(new CheckGameModeModuleHandler(powerAccent, settingsService, options)) 17 | .SetNext(new StrokeLetterModuleHandler(powerAccent, settingsService, options)) 18 | .SetNext(new StrokeArrowsModuleHandler(powerAccent, settingsService, options)) 19 | .SetNext(new StrokeSpaceModuleHandler(powerAccent, settingsService, options)) 20 | .SetNext(new DisplaySelectorModuleHandler(powerAccent, settingsService, options)) 21 | ; 22 | } 23 | 24 | public bool InvokeKeyDown(uint key) 25 | { 26 | try 27 | { 28 | return _moduleHandler.InvokeKeyDown(key); 29 | } 30 | catch (Exception e) 31 | { 32 | Console.WriteLine($"Error in InvokeKeyDown: {e.Message}"); 33 | Console.WriteLine(e.StackTrace); 34 | throw; 35 | } 36 | } 37 | 38 | public bool InvokeKeyUp(uint key) 39 | { 40 | try 41 | { 42 | return _moduleHandler.InvokeKeyUp(key); 43 | } 44 | catch (Exception e) 45 | { 46 | Console.WriteLine($"Error in InvokeKeyUp: {e.Message}"); 47 | Console.WriteLine(e.StackTrace); 48 | throw; 49 | } 50 | } 51 | } 52 | 53 | internal record KeyOptions 54 | { 55 | public LetterKey? LetterPressed { get; set; } = default!; 56 | public TriggerKey? TriggerPressed { get; set; } = default!; 57 | public bool IsVisible { get; set; } = default!; 58 | public bool IsDelayOk { get; set; } = default!; 59 | public bool IsBackwardShiftPressed { get; set; } = default!; 60 | public int SelectedIndex { get; set; } = -1; 61 | public char[] Characters { get; set; } = Array.Empty(); 62 | public bool CancelTrigger { get; set; } = default!; 63 | 64 | public void Reset() 65 | { 66 | Debug.WriteLine("Reset"); 67 | LetterPressed = null; 68 | TriggerPressed = null; 69 | IsVisible = false; 70 | IsDelayOk = false; 71 | IsBackwardShiftPressed = false; 72 | SelectedIndex = -1; 73 | Characters = Array.Empty(); 74 | CancelTrigger = false; 75 | } 76 | } -------------------------------------------------------------------------------- /PowerAccent.Core/ModuleHandlers/CustomModules/StrokeSpaceModuleHandler.cs: -------------------------------------------------------------------------------- 1 | using PowerAccent.Core.Extensions; 2 | using PowerAccent.Core.Services; 3 | using PowerAccent.Core.Tools; 4 | using System.Diagnostics; 5 | 6 | namespace PowerAccent.Core.ModuleHandlers.CustomModules; 7 | 8 | internal class StrokeSpaceModuleHandler : ModuleHandler 9 | { 10 | public StrokeSpaceModuleHandler(PowerAccent powerAccent, SettingsService settingsService, KeyOptions options) : base(powerAccent, settingsService, options) 11 | { 12 | } 13 | 14 | public override bool InvokeKeyDown(uint key) 15 | { 16 | if (Options.LetterPressed.HasValue && SettingsService.IsSpaceBarActive && key == (uint)TriggerKey.Space) 17 | { 18 | Options.TriggerPressed = (TriggerKey)key; 19 | 20 | if (Enum.IsDefined(typeof(BackwardKey), key) && !Options.IsBackwardShiftPressed) 21 | Options.IsBackwardShiftPressed = true; 22 | Debug.WriteLine($"InvokeKeyDown StrokeSpaceModuleHandler - Key: {(TriggerKey)key}, Backward: {Options.IsBackwardShiftPressed}"); 23 | 24 | if (!Options.IsVisible) 25 | { 26 | if (!WindowsFunctions.IsKeyPressed(Options.LetterPressed.Value)) 27 | { 28 | Options.Reset(); 29 | return true; 30 | } 31 | Options.Characters = WindowsFunctions.IsCapitalState() 32 | ? SettingsService.GetLetterKey(Options.LetterPressed.Value).ToUpper() 33 | : SettingsService.GetLetterKey(Options.LetterPressed.Value); 34 | } 35 | 36 | if (Options.Characters.Length == 0) 37 | { 38 | Debug.WriteLine($"InvokeKeyDown StrokeSpaceModuleHandler - No characters found for {Options.LetterPressed.Value}"); 39 | Options.Reset(); 40 | return true; 41 | } 42 | 43 | Options.SelectedIndex += Options.IsBackwardShiftPressed ? -1 : 1; 44 | 45 | if (Options.SelectedIndex < 0) Options.SelectedIndex = Options.Characters.Length - 1; 46 | if (Options.SelectedIndex > Options.Characters.Length - 1) Options.SelectedIndex = 0; 47 | Debug.WriteLine($"InvokeKeyDown StrokeSpaceModuleHandler - SelectedIndex: {Options.SelectedIndex}"); 48 | PowerAccent.SelectCharacter(Options.SelectedIndex); 49 | Options.CancelTrigger = true; 50 | } 51 | 52 | if (Options.IsVisible && Enum.IsDefined(typeof(BackwardKey), key)) 53 | Options.IsBackwardShiftPressed = true; 54 | 55 | return base.InvokeKeyDown(key); 56 | } 57 | 58 | public override bool InvokeKeyUp(uint key) 59 | { 60 | if (Options.IsBackwardShiftPressed && Enum.IsDefined(typeof(BackwardKey), key)) 61 | Options.IsBackwardShiftPressed = false; 62 | 63 | return base.InvokeKeyUp(key); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /PowerAccent.UI/Settings.xaml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /PowerAccent.UI/SettingsPage/PositionPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using PowerAccent.Core.Services; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | 5 | namespace PowerAccent.UI.SettingsPage 6 | { 7 | /// 8 | /// Logique d'interaction pour Position.xaml 9 | /// 10 | public partial class PositionPage : Page 11 | { 12 | private readonly SettingsService _settingService = new SettingsService(); 13 | 14 | public PositionPage() 15 | { 16 | InitializeComponent(); 17 | RefreshPosition(); 18 | } 19 | 20 | private void Position_Up_Checked(object sender, RoutedEventArgs e) 21 | { 22 | _settingService.Position = Position.Top; 23 | RefreshPosition(); 24 | } 25 | private void Position_Down_Checked(object sender, RoutedEventArgs e) 26 | { 27 | _settingService.Position = Position.Bottom; 28 | RefreshPosition(); 29 | } 30 | private void Position_Left_Checked(object sender, RoutedEventArgs e) 31 | { 32 | _settingService.Position = Position.Left; 33 | RefreshPosition(); 34 | } 35 | private void Position_Right_Checked(object sender, RoutedEventArgs e) 36 | { 37 | _settingService.Position = Position.Right; 38 | RefreshPosition(); 39 | } 40 | private void Position_UpLeft_Checked(object sender, RoutedEventArgs e) 41 | { 42 | _settingService.Position = Position.TopLeft; 43 | RefreshPosition(); 44 | } 45 | private void Position_UpRight_Checked(object sender, RoutedEventArgs e) 46 | { 47 | _settingService.Position = Position.TopRight; 48 | RefreshPosition(); 49 | } 50 | private void Position_DownLeft_Checked(object sender, RoutedEventArgs e) 51 | { 52 | _settingService.Position = Position.BottomLeft; 53 | RefreshPosition(); 54 | } 55 | private void Position_DownRight_Checked(object sender, RoutedEventArgs e) 56 | { 57 | _settingService.Position = Position.BottomRight; 58 | RefreshPosition(); 59 | } 60 | private void Position_Center_Checked(object sender, RoutedEventArgs e) 61 | { 62 | _settingService.Position = Position.Center; 63 | RefreshPosition(); 64 | } 65 | 66 | private void RefreshPosition() 67 | { 68 | var position = _settingService.Position; 69 | Position_Up.IsChecked = position == Position.Top; 70 | Position_Down.IsChecked = position == Position.Bottom; 71 | Position_Left.IsChecked = position == Position.Left; 72 | Position_Right.IsChecked = position == Position.Right; 73 | Position_UpRight.IsChecked = position == Position.TopRight; 74 | Position_UpLeft.IsChecked = position == Position.TopLeft; 75 | Position_DownRight.IsChecked = position == Position.BottomRight; 76 | Position_DownLeft.IsChecked = position == Position.BottomLeft; 77 | Position_Center.IsChecked = position == Position.Center; 78 | 79 | (Application.Current.MainWindow as MainWindow).RefreshSettings(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /PowerAccent.UI/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | using System.Windows.Forms; 5 | using Point = PowerAccent.Core.Point; 6 | using Size = PowerAccent.Core.Size; 7 | using Application = System.Windows.Application; 8 | using System.Collections.Generic; 9 | 10 | namespace PowerAccent.UI; 11 | 12 | public partial class MainWindow : Window 13 | { 14 | private Core.PowerAccent _powerAccent = new Core.PowerAccent(); 15 | private Selector _selector; 16 | private Stack _selectorStack = new Stack(); 17 | 18 | public MainWindow() 19 | { 20 | InitializeComponent(); 21 | } 22 | 23 | protected override void OnSourceInitialized(EventArgs e) 24 | { 25 | base.OnSourceInitialized(e); 26 | _powerAccent.OnChangeDisplay += PowerAccent_OnChangeDisplay; 27 | _powerAccent.OnSelectCharacter += PowerAccent_OnSelectionCharacter; 28 | this.Visibility = Visibility.Hidden; 29 | _powerAccent.CheckVersion(); 30 | } 31 | 32 | private void PowerAccent_OnSelectionCharacter(int index) 33 | { 34 | _selector?.SetIndex(index); 35 | } 36 | 37 | private void PowerAccent_OnChangeDisplay(bool isActive, char[] chars) 38 | { 39 | if (isActive) 40 | { 41 | Selector selector = new Selector(chars); 42 | selector.Show(); 43 | CenterWindow(selector); 44 | _selectorStack.Push(selector); 45 | _selector = selector; 46 | } 47 | else 48 | { 49 | while (_selectorStack.Count > 0) 50 | { 51 | _selectorStack.Pop().Close(); 52 | } 53 | } 54 | } 55 | 56 | private void CenterWindow(Selector selector) 57 | { 58 | Size window = new Size(((System.Windows.Controls.Panel)selector.Content).ActualWidth, ((System.Windows.Controls.Panel)selector.Content).ActualHeight); 59 | double primaryDPI = Screen.PrimaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth; 60 | Point position = _powerAccent.GetDisplayCoordinates(window, primaryDPI); 61 | selector.SetPosition(position.X, position.Y); 62 | selector.SetBorderWindowAlignment(_powerAccent.IsLeftPosition()); 63 | } 64 | 65 | #region TaskBar 66 | private void Settings_Click(object sender, RoutedEventArgs e) 67 | { 68 | Settings settings = new Settings(); 69 | settings.Show(); 70 | } 71 | 72 | private void MenuExit_Click(object sender, RoutedEventArgs e) 73 | { 74 | Application.Current.Shutdown(); 75 | } 76 | 77 | private void Pause_Click(object sender, RoutedEventArgs e) 78 | { 79 | _powerAccent.IsPaused = !_powerAccent.IsPaused; 80 | ((MenuItem)sender).FontWeight = _powerAccent.IsPaused ? FontWeights.Bold : FontWeights.Thin; 81 | } 82 | 83 | #endregion 84 | 85 | public void RefreshSettings() 86 | { 87 | _powerAccent.ReloadSettings(); 88 | } 89 | 90 | protected override void OnClosed(EventArgs e) 91 | { 92 | _powerAccent.Dispose(); 93 | base.OnClosed(e); 94 | } 95 | 96 | public void Dispose() 97 | { 98 | _powerAccent.Dispose(); 99 | GC.SuppressFinalize(this); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /PowerAccent.UI/SettingsPage/CountriesPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using PowerAccent.Core; 2 | using PowerAccent.Core.Services; 3 | using System; 4 | using System.Globalization; 5 | using System.Linq; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Media.Imaging; 10 | 11 | namespace PowerAccent.UI.SettingsPage; 12 | 13 | /// 14 | /// Logique d'interaction pour CountriesPage.xaml 15 | /// 16 | public partial class CountriesPage : Page 17 | { 18 | private readonly SettingsService _settingService = new SettingsService(); 19 | 20 | 21 | public CountriesPage() 22 | { 23 | InitializeComponent(); 24 | } 25 | 26 | protected override void OnInitialized(EventArgs e) 27 | { 28 | base.OnInitialized(e); 29 | 30 | RefreshData(); 31 | } 32 | 33 | private void RefreshData() 34 | { 35 | Countries.ItemsSource = Enum.GetNames().Select(l => new Country 36 | { 37 | Name = l, 38 | ImageUrl = $"/Resources/Flags/{l}.jpg", 39 | IsChecked = _settingService.SelectedLanguages.Any(s => s.ToString() == l) 40 | }).ToList(); 41 | } 42 | 43 | private void CheckBox_OnChanged(object sender, RoutedEventArgs e) 44 | { 45 | var selectedCountry = ((CheckBox)sender).DataContext as Country; 46 | var doRefresh = false; 47 | var selectedLanguages = Countries.Items.Cast().Where(c => c.IsChecked).Select(c => Enum.Parse(c.Name)).ToArray(); 48 | if (selectedCountry.Name == Core.Language.ALL.ToString() && selectedCountry.IsChecked) 49 | { 50 | selectedLanguages = new[] { Core.Language.ALL }; 51 | Countries.ItemsSource.Cast().ToList().ForEach(c => c.IsChecked = false); 52 | Countries.ItemsSource.Cast().First(c => c.Name == Core.Language.ALL.ToString()).IsChecked = true; 53 | doRefresh = true; 54 | } 55 | else if (selectedLanguages.Length == 0) 56 | { 57 | selectedLanguages = new[] { Core.Language.ALL }; 58 | Countries.ItemsSource.Cast().First(c => c.Name == Core.Language.ALL.ToString()).IsChecked = true; 59 | doRefresh = true; 60 | } 61 | else if (selectedLanguages.Length > 1 && selectedLanguages.Any(s => s == Core.Language.ALL)) 62 | { 63 | selectedLanguages = selectedLanguages.Where(s => s != Core.Language.ALL).ToArray(); 64 | Countries.ItemsSource.Cast().First(c => c.Name == Core.Language.ALL.ToString()).IsChecked = false; 65 | doRefresh = true; 66 | } 67 | 68 | _settingService.SelectedLanguages = selectedLanguages; 69 | (Application.Current.MainWindow as MainWindow).RefreshSettings(); 70 | if (doRefresh) 71 | RefreshData(); 72 | } 73 | } 74 | 75 | public class StringToImageSourceConverter : IValueConverter 76 | { 77 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 78 | { 79 | return new BitmapImage(new Uri("pack://application:,,,/PowerAccent;component/" + value.ToString(), UriKind.Absolute)); 80 | } 81 | 82 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 83 | { 84 | throw new NotImplementedException(); 85 | } 86 | } -------------------------------------------------------------------------------- /PowerAccent.Wix/PowerAccent.Wix.SelfContained.wixproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | x86 6 | 3.10 7 | 0c31f068-83e6-48ed-ab58-4e8ebfe51d9a 8 | 2.0 9 | PowerAccent 10 | Package 11 | x64 12 | x64 13 | 14 | 15 | bin\$(Configuration)\ 16 | obj\$(Configuration)\ 17 | Debug 18 | 19 | 20 | bin\$(Configuration)\ 21 | obj\$(Configuration)\ 22 | 23 | 24 | 25 | 26 | 27 | 28 | PowerAccent 29 | {8f823b66-7b84-4d2e-b9f8-06846ddc5951} 30 | True 31 | True 32 | Binaries;Content;Satellites 33 | INSTALLFOLDER 34 | net7.0-windows10.0.19041.0 35 | win-x64 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /PowerAccent.UI/SettingsPage/CountriesPage.xaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /PowerAccent.Core/PowerAccent.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using PowerAccent.Core.ModuleHandlers; 3 | using PowerAccent.Core.Services; 4 | using PowerAccent.Core.Tools; 5 | 6 | namespace PowerAccent.Core; 7 | 8 | public class PowerAccent : IDisposable 9 | { 10 | internal readonly SettingsService _settingService = new SettingsService(); 11 | private readonly KeyboardListener _keyboardListener = new KeyboardListener(); 12 | private readonly KeyOptions _moduleOptions = new KeyOptions(); 13 | private readonly ModuleDirector _moduleDirector; 14 | 15 | public event Action OnChangeDisplay; 16 | public event Action OnSelectCharacter; 17 | 18 | public bool IsPaused { get; set; } 19 | 20 | public PowerAccent() 21 | { 22 | _moduleDirector = new ModuleDirector(this, _settingService, _moduleOptions); 23 | _keyboardListener.KeyDown += PowerAccent_KeyDown; 24 | _keyboardListener.KeyUp += PowerAccent_KeyUp; 25 | } 26 | 27 | private bool PowerAccent_KeyDown(object sender, KeyboardListener.RawKeyEventArgs args) 28 | { 29 | return _moduleDirector.InvokeKeyDown(args.Key); 30 | } 31 | 32 | private bool PowerAccent_KeyUp(object sender, KeyboardListener.RawKeyEventArgs args) 33 | { 34 | return _moduleDirector.InvokeKeyUp(args.Key); 35 | } 36 | 37 | public void ChangeDisplay(bool visible, char[] characters) 38 | { 39 | OnChangeDisplay?.Invoke(visible, characters); 40 | } 41 | 42 | public void SelectCharacter(int index) 43 | { 44 | OnSelectCharacter?.Invoke(index); 45 | } 46 | 47 | public Point GetDisplayCoordinates(Size window, double primaryDpi) 48 | { 49 | var activeDisplay = WindowsFunctions.GetActiveDisplay(); 50 | Rect screen = new Rect(activeDisplay.Location, activeDisplay.Size) / primaryDpi; 51 | Position position = _settingService.Position; 52 | 53 | Debug.WriteLine($"Window {window} - Screen {screen}"); 54 | Debug.WriteLine($"Primary Dpi: {primaryDpi} - Screen Dpi: {activeDisplay.Dpi}"); 55 | 56 | if (!_settingService.UseCaretPosition) 57 | { 58 | return Calculation.GetRawCoordinatesFromPosition(position, screen, window); 59 | } 60 | 61 | Point carretPixel = WindowsFunctions.GetCaretPosition(); 62 | if (carretPixel.X == 0 && carretPixel.Y == 0) 63 | { 64 | return Calculation.GetRawCoordinatesFromPosition(position, screen, window); 65 | } 66 | 67 | Point caret = new Point(carretPixel.X, carretPixel.Y) / primaryDpi; 68 | return Calculation.GetRawCoordinatesFromCaret(caret, screen, window); 69 | } 70 | 71 | public char[] GetLettersFromKey(LetterKey letter) 72 | { 73 | return _settingService.GetLetterKey(letter); 74 | } 75 | 76 | public bool? IsLeftPosition() 77 | { 78 | return _settingService.Position switch 79 | { 80 | Position.Left or Position.TopLeft or Position.BottomLeft => true, 81 | Position.Right or Position.TopRight or Position.BottomRight => false, 82 | _ => null 83 | }; 84 | } 85 | 86 | public void ReloadSettings() 87 | { 88 | _settingService.Reload(); 89 | } 90 | 91 | public void CheckVersion() 92 | { 93 | _settingService.UpdateSettingsVersion(); 94 | } 95 | 96 | public void Dispose() 97 | { 98 | _keyboardListener.Dispose(); 99 | GC.SuppressFinalize(this); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /PowerAccent.UI/PowerAccent.UI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net9.0-windows10.0.19041.0 6 | disable 7 | true 8 | True 9 | Resources/a-icon.ico 10 | PowerAccent 11 | True 12 | False 13 | $(NoWarn);CA1416 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | $(DefaultXamlRuntime) 89 | Designer 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /PowerAccent.UI/SettingsPage/SortPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using PowerAccent.Core; 2 | using PowerAccent.Core.Services; 3 | using System; 4 | using System.Collections.ObjectModel; 5 | using System.ComponentModel; 6 | using System.Globalization; 7 | using System.Linq; 8 | using System.Windows; 9 | using System.Windows.Controls; 10 | using System.Windows.Data; 11 | using Application = System.Windows.Application; 12 | using ListBox = System.Windows.Controls.ListBox; 13 | 14 | namespace PowerAccent.UI.SettingsPage; 15 | 16 | /// 17 | /// Logique d'interaction pour SortPage.xaml 18 | /// 19 | public partial class SortPage : Page, INotifyPropertyChanged 20 | { 21 | private readonly SettingsService _settingService = new SettingsService(); 22 | 23 | public SortPage() 24 | { 25 | InitializeComponent(); 26 | } 27 | 28 | protected override void OnInitialized(EventArgs e) 29 | { 30 | base.OnInitialized(e); 31 | Letters.ItemsSource = Enum.GetValues(typeof(LetterKey)).Cast() 32 | .Where(k => k >= LetterKey.A && k <= LetterKey.Z) 33 | .Where(k => _settingService.GetLetterKey(k).Length > 0); 34 | CharacterList.DataContext = this; 35 | } 36 | 37 | private ObservableCollection _characters; 38 | 39 | public event PropertyChangedEventHandler PropertyChanged; 40 | private void NotifyPropertyChanged(string property) 41 | { 42 | if (PropertyChanged != null) 43 | { 44 | PropertyChanged(this, new PropertyChangedEventArgs(property)); 45 | } 46 | } 47 | 48 | public ObservableCollection Characters 49 | { 50 | get 51 | { 52 | if (_characters == null) 53 | _characters = new ObservableCollection(); 54 | return _characters; 55 | } 56 | set 57 | { 58 | _characters = value; 59 | NotifyPropertyChanged("Characters"); 60 | } 61 | } 62 | 63 | private void Letters_SelectionChanged(object sender, SelectionChangedEventArgs e) 64 | { 65 | LetterKey key = (LetterKey)((ListBox)sender).SelectedItem; 66 | Characters = new ObservableCollection(_settingService.GetLetterKey(key)); 67 | CharacterList.Visibility = Visibility.Visible; 68 | } 69 | 70 | private void Back_Click(object sender, RoutedEventArgs e) 71 | { 72 | LetterKey key = (LetterKey)Letters.SelectedItem; 73 | Characters = new ObservableCollection(_settingService.GetDefaultLetterKey(key)); 74 | } 75 | 76 | private void Save_Click(object sender, RoutedEventArgs e) 77 | { 78 | LetterKey key = (LetterKey)Letters.SelectedItem; 79 | _settingService.SetLetterKey(key, Characters.ToArray()); 80 | _settingService.Save(); 81 | (Application.Current.MainWindow as MainWindow).RefreshSettings(); 82 | } 83 | } 84 | 85 | class VisibilityNullConverter : IValueConverter 86 | { 87 | #region IValueConverter Members 88 | 89 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 90 | { 91 | return value == null ? Visibility.Collapsed : Visibility.Visible; 92 | } 93 | 94 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 95 | { 96 | throw new NotImplementedException(); 97 | } 98 | 99 | #endregion 100 | } 101 | 102 | class BooleanNullConverter : IValueConverter 103 | { 104 | #region IValueConverter Members 105 | 106 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 107 | { 108 | return value != null; 109 | } 110 | 111 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 112 | { 113 | return value == null; 114 | } 115 | 116 | #endregion 117 | } -------------------------------------------------------------------------------- /PowerAccent.Core/Tools/WindowsFunctions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.InteropServices; 3 | using Vanara.PInvoke; 4 | 5 | namespace PowerAccent.Core.Tools; 6 | 7 | internal static class WindowsFunctions 8 | { 9 | public static void Insert(char c, bool back = false) 10 | { 11 | Debug.WriteLine($"Insert character: '{c}'. Back: {back}"); 12 | unsafe 13 | { 14 | if (back) 15 | { 16 | // Split in 2 different SendInput (Powershell doesn't take back issue) 17 | var inputsBack = new User32.INPUT[] 18 | { 19 | new User32.INPUT {type = User32.INPUTTYPE.INPUT_KEYBOARD, ki = new User32.KEYBDINPUT {wVk = (ushort) User32.VK.VK_BACK}}, 20 | new User32.INPUT {type = User32.INPUTTYPE.INPUT_KEYBOARD, ki = new User32.KEYBDINPUT {wVk = (ushort) User32.VK.VK_BACK, dwFlags = User32.KEYEVENTF.KEYEVENTF_KEYUP}} 21 | }; 22 | 23 | _ = User32.SendInput((uint)inputsBack.Length, inputsBack, sizeof(User32.INPUT)); 24 | System.Threading.Thread.Sleep(1); // Some apps, like Terminal, need a little wait to process the sent backspace or they'll ignore it. 25 | } 26 | 27 | // Letter 28 | var inputsInsert = new User32.INPUT[1] 29 | { 30 | new User32.INPUT {type = User32.INPUTTYPE.INPUT_KEYBOARD, ki = new User32.KEYBDINPUT {wVk = 0, dwFlags = User32.KEYEVENTF.KEYEVENTF_UNICODE, wScan = c}} 31 | }; 32 | _ = User32.SendInput((uint)inputsInsert.Length, inputsInsert, sizeof(User32.INPUT)); 33 | System.Threading.Thread.Sleep(1); 34 | } 35 | } 36 | 37 | public static Point GetCaretPosition() 38 | { 39 | User32.GUITHREADINFO guiInfo = new User32.GUITHREADINFO(); 40 | guiInfo.cbSize = (uint)Marshal.SizeOf(guiInfo); 41 | User32.GetGUIThreadInfo(0, ref guiInfo); 42 | System.Drawing.Point caretPosition = new System.Drawing.Point(guiInfo.rcCaret.left, guiInfo.rcCaret.top); 43 | User32.ClientToScreen(guiInfo.hwndCaret, ref caretPosition); 44 | 45 | if (caretPosition.X == 0) 46 | { 47 | System.Drawing.Point testPoint; 48 | User32.GetCaretPos(out testPoint); 49 | return testPoint; 50 | } 51 | 52 | return caretPosition; 53 | } 54 | 55 | public static (Point Location, Size Size, double Dpi) GetActiveDisplay() 56 | { 57 | User32.GUITHREADINFO guiInfo = new User32.GUITHREADINFO(); 58 | guiInfo.cbSize = (uint)Marshal.SizeOf(guiInfo); 59 | User32.GetGUIThreadInfo(0, ref guiInfo); 60 | var res = User32.MonitorFromWindow(guiInfo.hwndActive, User32.MonitorFlags.MONITOR_DEFAULTTONEAREST); 61 | 62 | User32.MONITORINFO monitorInfo = new User32.MONITORINFO(); 63 | monitorInfo.cbSize = (uint)Marshal.SizeOf(monitorInfo); 64 | User32.GetMonitorInfo(res, ref monitorInfo); 65 | 66 | double dpi = User32.GetDpiForWindow(guiInfo.hwndActive) / 96d; 67 | 68 | return (monitorInfo.rcWork.Location, monitorInfo.rcWork.Size, dpi); 69 | } 70 | 71 | public static bool IsCapitalState() 72 | { 73 | var capital = User32.GetKeyState((int)User32.VK.VK_CAPITAL); 74 | var shift = User32.GetKeyState((int)User32.VK.VK_SHIFT); 75 | return capital != 0 || shift < 0; 76 | } 77 | 78 | public static bool IsKeyPressed(LetterKey key) 79 | { 80 | var result = User32.GetAsyncKeyState((int)key); 81 | Debug.WriteLine("Result - " + result); 82 | return result != 0; 83 | } 84 | 85 | public static bool IsGameMode() 86 | { 87 | Shell32.QUERY_USER_NOTIFICATION_STATE state; 88 | if (Shell32.SHQueryUserNotificationState(out state) != 0) 89 | { 90 | return false; 91 | } 92 | 93 | return state == Shell32.QUERY_USER_NOTIFICATION_STATE.QUNS_BUSY || state == Shell32.QUERY_USER_NOTIFICATION_STATE.QUNS_RUNNING_D3D_FULL_SCREEN; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /PowerAccent.UI/SettingsPage/PositionPage.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /PowerAccent.UI/Selector.xaml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 27 | 28 | 61 | 62 | 63 | 64 | 65 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /PowerAccent.Core/Services/SettingsService.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | namespace PowerAccent.Core.Services; 4 | 5 | public class SettingsService : ApplicationSettingsBase 6 | { 7 | 8 | [UserScopedSetting] 9 | [DefaultSettingValue("ALL")] 10 | public Language SelectedLanguage 11 | { 12 | get { return (Language)this["SelectedLanguage"]; } 13 | set { this["SelectedLanguage"] = value; Save(); } 14 | } 15 | 16 | [UserScopedSetting] 17 | [DefaultSettingValue("ALL")] 18 | public Language[] SelectedLanguages 19 | { 20 | get { return (Language[])this["SelectedLanguages"]; } 21 | set { this["SelectedLanguages"] = value; Save(); } 22 | } 23 | 24 | [UserScopedSetting] 25 | [DefaultSettingValue("Top")] 26 | public Position Position 27 | { 28 | get { return (Position)this["Position"]; } 29 | set { this["Position"] = value; Save(); } 30 | } 31 | 32 | [UserScopedSetting] 33 | [DefaultSettingValue("False")] 34 | public bool UseCaretPosition 35 | { 36 | get { return (bool)this["UseCaretPosition"]; } 37 | set { this["UseCaretPosition"] = value; Save(); } 38 | } 39 | 40 | [UserScopedSetting] 41 | [DefaultSettingValue("True")] 42 | public bool IsSpaceBarActive 43 | { 44 | get { return (bool)this["IsSpaceBarActive"]; } 45 | set { this["IsSpaceBarActive"] = value; Save(); } 46 | } 47 | 48 | [UserScopedSetting] 49 | [DefaultSettingValue("200")] 50 | public int InputTime 51 | { 52 | get { return (int)this["InputTime"]; } 53 | set { this["InputTime"] = value; Save(); } 54 | } 55 | 56 | [UserScopedSetting] 57 | [DefaultSettingValue("True")] 58 | public bool DisableInFullScreen 59 | { 60 | get { return (bool)this["DisableInFullScreen"]; } 61 | set { this["DisableInFullScreen"] = value; Save(); } 62 | } 63 | 64 | [UserScopedSetting] 65 | [DefaultSettingValue("False")] 66 | public bool InsertSpaceAfterSelection 67 | { 68 | get { return (bool)this["InsertSpaceAfterSelection"]; } 69 | set { this["InsertSpaceAfterSelection"] = value; Save(); } 70 | } 71 | 72 | [UserScopedSetting] 73 | [DefaultSettingValue("0")] 74 | public int SettingsVersion 75 | { 76 | get { return (int)this["SettingsVersion"]; } 77 | set { this["SettingsVersion"] = value; Save(); } 78 | } 79 | 80 | #region LetterKey 81 | 82 | public void SetLetterKey(LetterKey letter, char[] value) 83 | { 84 | string key = $"LetterKey{letter}_{SelectedLanguage}"; 85 | AddingProperty(key); 86 | 87 | this[key] = value; 88 | this.Save(); 89 | } 90 | 91 | public char[] GetLetterKey(LetterKey letter) 92 | { 93 | string key = $"LetterKey{letter}_{SelectedLanguage}"; 94 | AddingProperty(key); 95 | if (this.PropertyValues.Cast().Any(s => s.Name == key) && this[key] != null) 96 | return (char[])this[key]; 97 | 98 | return Languages.GetMultipleLetterKey(letter, SelectedLanguages); 99 | } 100 | 101 | public char[] GetDefaultLetterKey(LetterKey key) 102 | { 103 | return Languages.GetMultipleLetterKey(key, SelectedLanguages); 104 | } 105 | 106 | private void AddingProperty(string key) 107 | { 108 | if (!this.PropertyValues.Cast().Any(s => s.Name == key)) 109 | { 110 | SettingsProvider sp = this.Providers["LocalFileSettingsProvider"]; 111 | SettingsProperty p = new SettingsProperty(key); 112 | p.PropertyType = typeof(char[]); 113 | p.Attributes.Add(typeof(UserScopedSettingAttribute), new UserScopedSettingAttribute()); 114 | p.Provider = sp; 115 | p.SerializeAs = SettingsSerializeAs.Xml; 116 | SettingsPropertyValue v = new SettingsPropertyValue(p); 117 | this.Properties.Add(p); 118 | this.Reload(); 119 | } 120 | } 121 | 122 | // Check and update properties from previous version 123 | public void UpdateSettingsVersion() 124 | { 125 | if (this.SettingsVersion == 1) 126 | return; 127 | 128 | if (this.SettingsVersion == 0) 129 | { 130 | SelectedLanguages = new[] { SelectedLanguage }; 131 | } 132 | 133 | SettingsVersion = 1; 134 | this.Save(); 135 | } 136 | 137 | #endregion 138 | } 139 | 140 | public enum Position 141 | { 142 | Top, 143 | Bottom, 144 | Left, 145 | Right, 146 | TopLeft, 147 | TopRight, 148 | BottomLeft, 149 | BottomRight, 150 | Center 151 | } 152 | -------------------------------------------------------------------------------- /PowerAccent.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32014.148 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerAccent.UI", "PowerAccent.UI\PowerAccent.UI.csproj", "{8F823B66-7B84-4D2E-B9F8-06846DDC5951}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerAccent.Core", "PowerAccent.Core\PowerAccent.Core.csproj", "{0124DDDD-22C0-482C-B96A-92B8F9B25464}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0A101495-C7E7-4A21-A6F1-8BD3A6204EFA}" 11 | ProjectSection(SolutionItems) = preProject 12 | .editorconfig = .editorconfig 13 | .gitignore = .gitignore 14 | .github\workflows\publish.yml = .github\workflows\publish.yml 15 | README.md = README.md 16 | EndProjectSection 17 | EndProject 18 | Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PowerAccent.Wix.SelfContained", "PowerAccent.Wix\PowerAccent.Wix.SelfContained.wixproj", "{0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Debug|ARM = Debug|ARM 24 | Debug|ARM64 = Debug|ARM64 25 | Debug|x64 = Debug|x64 26 | Debug|x86 = Debug|x86 27 | Release|Any CPU = Release|Any CPU 28 | Release|ARM = Release|ARM 29 | Release|ARM64 = Release|ARM64 30 | Release|x64 = Release|x64 31 | Release|x86 = Release|x86 32 | EndGlobalSection 33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 34 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Debug|ARM.ActiveCfg = Debug|Any CPU 37 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Debug|ARM.Build.0 = Debug|Any CPU 38 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Debug|ARM64.ActiveCfg = Debug|Any CPU 39 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Debug|ARM64.Build.0 = Debug|Any CPU 40 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Debug|x64.ActiveCfg = Debug|Any CPU 41 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Debug|x64.Build.0 = Debug|Any CPU 42 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Debug|x86.ActiveCfg = Debug|Any CPU 43 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Debug|x86.Build.0 = Debug|Any CPU 44 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Release|ARM.ActiveCfg = Release|Any CPU 47 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Release|ARM.Build.0 = Release|Any CPU 48 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Release|ARM64.ActiveCfg = Release|Any CPU 49 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Release|ARM64.Build.0 = Release|Any CPU 50 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Release|x64.ActiveCfg = Release|Any CPU 51 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Release|x64.Build.0 = Release|Any CPU 52 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Release|x86.ActiveCfg = Release|Any CPU 53 | {8F823B66-7B84-4D2E-B9F8-06846DDC5951}.Release|x86.Build.0 = Release|Any CPU 54 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Debug|ARM.ActiveCfg = Debug|Any CPU 57 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Debug|ARM.Build.0 = Debug|Any CPU 58 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Debug|ARM64.ActiveCfg = Debug|Any CPU 59 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Debug|ARM64.Build.0 = Debug|Any CPU 60 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Debug|x64.ActiveCfg = Debug|Any CPU 61 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Debug|x64.Build.0 = Debug|Any CPU 62 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Debug|x86.ActiveCfg = Debug|Any CPU 63 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Debug|x86.Build.0 = Debug|Any CPU 64 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Release|Any CPU.Build.0 = Release|Any CPU 66 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Release|ARM.ActiveCfg = Release|Any CPU 67 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Release|ARM.Build.0 = Release|Any CPU 68 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Release|ARM64.ActiveCfg = Release|Any CPU 69 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Release|ARM64.Build.0 = Release|Any CPU 70 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Release|x64.ActiveCfg = Release|Any CPU 71 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Release|x64.Build.0 = Release|Any CPU 72 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Release|x86.ActiveCfg = Release|Any CPU 73 | {0124DDDD-22C0-482C-B96A-92B8F9B25464}.Release|x86.Build.0 = Release|Any CPU 74 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Debug|Any CPU.ActiveCfg = Debug|x86 75 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Debug|ARM.ActiveCfg = Debug|x86 76 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Debug|ARM.Build.0 = Debug|x86 77 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Debug|ARM64.ActiveCfg = Debug|x86 78 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Debug|ARM64.Build.0 = Debug|x86 79 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Debug|x64.ActiveCfg = Debug|x86 80 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Debug|x64.Build.0 = Debug|x86 81 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Debug|x86.ActiveCfg = Debug|x86 82 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Debug|x86.Build.0 = Debug|x86 83 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Release|Any CPU.ActiveCfg = Release|x86 84 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Release|Any CPU.Build.0 = Release|x86 85 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Release|ARM.ActiveCfg = Release|x86 86 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Release|ARM.Build.0 = Release|x86 87 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Release|ARM64.ActiveCfg = Release|x86 88 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Release|ARM64.Build.0 = Release|x86 89 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Release|x64.ActiveCfg = Release|x86 90 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Release|x64.Build.0 = Release|x86 91 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Release|x86.ActiveCfg = Release|x86 92 | {0C31F068-83E6-48ED-AB58-4E8EBFE51D9A}.Release|x86.Build.0 = Release|x86 93 | EndGlobalSection 94 | GlobalSection(SolutionProperties) = preSolution 95 | HideSolutionNode = FALSE 96 | EndGlobalSection 97 | GlobalSection(ExtensibilityGlobals) = postSolution 98 | SolutionGuid = {46E35B45-7B0B-4B86-BA3A-AF4B406CF608} 99 | EndGlobalSection 100 | EndGlobal 101 | -------------------------------------------------------------------------------- /PowerAccent.UI/SettingsPage/OptionsPage.xaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 89 | 92 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /PowerAccent.UI/Themes/ThemeManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System; 6 | using System.Linq; 7 | using System.Windows; 8 | using ControlzEx.Theming; 9 | using Microsoft.Win32; 10 | 11 | namespace PowerAccent.UI.Themes 12 | { 13 | public enum Theme 14 | { 15 | System, 16 | Light, 17 | Dark, 18 | HighContrastOne, 19 | HighContrastTwo, 20 | HighContrastBlack, 21 | HighContrastWhite, 22 | } 23 | 24 | public class ThemeManager : IDisposable 25 | { 26 | private const string LightTheme = "Light.Accent1"; 27 | private const string DarkTheme = "Dark.Accent1"; 28 | private const string HighContrastOneTheme = "HighContrast.Accent2"; 29 | private const string HighContrastTwoTheme = "HighContrast.Accent3"; 30 | private const string HighContrastBlackTheme = "HighContrast.Accent4"; 31 | private const string HighContrastWhiteTheme = "HighContrast.Accent5"; 32 | 33 | private Theme _currentTheme; 34 | private Theme _settingsTheme; 35 | private bool _disposed; 36 | 37 | public event ThemeChangedHandler ThemeChanged; 38 | 39 | public ThemeManager() 40 | { 41 | Uri highContrastOneThemeUri = new Uri("pack://application:,,,/Themes/HighContrast1.xaml"); 42 | Uri highContrastTwoThemeUri = new Uri("pack://application:,,,/Themes/HighContrast2.xaml"); 43 | Uri highContrastBlackThemeUri = new Uri("pack://application:,,,/Themes/HighContrastWhite.xaml"); 44 | Uri highContrastWhiteThemeUri = new Uri("pack://application:,,,/Themes/HighContrastBlack.xaml"); 45 | Uri lightThemeUri = new Uri("pack://application:,,,/Themes/Light.xaml"); 46 | Uri darkThemeUri = new Uri("pack://application:,,,/Themes/Dark.xaml"); 47 | 48 | ControlzEx.Theming.ThemeManager.Current.AddLibraryTheme( 49 | new LibraryTheme( 50 | highContrastOneThemeUri, 51 | CustomLibraryThemeProvider.DefaultInstance)); 52 | ControlzEx.Theming.ThemeManager.Current.AddLibraryTheme( 53 | new LibraryTheme( 54 | highContrastTwoThemeUri, 55 | CustomLibraryThemeProvider.DefaultInstance)); 56 | ControlzEx.Theming.ThemeManager.Current.AddLibraryTheme( 57 | new LibraryTheme( 58 | highContrastBlackThemeUri, 59 | CustomLibraryThemeProvider.DefaultInstance)); 60 | ControlzEx.Theming.ThemeManager.Current.AddLibraryTheme( 61 | new LibraryTheme( 62 | highContrastWhiteThemeUri, 63 | CustomLibraryThemeProvider.DefaultInstance)); 64 | ControlzEx.Theming.ThemeManager.Current.AddLibraryTheme( 65 | new LibraryTheme( 66 | lightThemeUri, 67 | CustomLibraryThemeProvider.DefaultInstance)); 68 | ControlzEx.Theming.ThemeManager.Current.AddLibraryTheme( 69 | new LibraryTheme( 70 | darkThemeUri, 71 | CustomLibraryThemeProvider.DefaultInstance)); 72 | 73 | ResetTheme(); 74 | ControlzEx.Theming.ThemeManager.Current.ThemeSyncMode = ThemeSyncMode.SyncWithAppMode; 75 | ControlzEx.Theming.ThemeManager.Current.ThemeChanged += Current_ThemeChanged; 76 | SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged; 77 | } 78 | 79 | private void SystemParameters_StaticPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) 80 | { 81 | if (e.PropertyName == nameof(SystemParameters.HighContrast)) 82 | { 83 | ResetTheme(); 84 | } 85 | } 86 | 87 | public Theme GetCurrentTheme() 88 | { 89 | return _currentTheme; 90 | } 91 | 92 | private static Theme GetHighContrastBaseType() 93 | { 94 | string registryKey = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes"; 95 | string theme = (string)Registry.GetValue(registryKey, "CurrentTheme", string.Empty); 96 | theme = theme.Split('\\').Last().Split('.').First().ToString(); 97 | 98 | switch (theme) 99 | { 100 | case "hc1": 101 | return Theme.HighContrastOne; 102 | case "hc2": 103 | return Theme.HighContrastTwo; 104 | case "hcwhite": 105 | return Theme.HighContrastWhite; 106 | case "hcblack": 107 | return Theme.HighContrastBlack; 108 | default: 109 | return Theme.HighContrastOne; 110 | } 111 | } 112 | 113 | private void ResetTheme() 114 | { 115 | ChangeTheme(_settingsTheme == Theme.System ? Theme.System : _currentTheme); 116 | } 117 | 118 | public static string GetWindowsBaseColor() 119 | { 120 | return WindowsThemeHelper.GetWindowsBaseColor(); 121 | } 122 | 123 | public void ChangeTheme(Theme theme, bool fromSettings = false) 124 | { 125 | if (fromSettings) 126 | { 127 | _settingsTheme = theme; 128 | } 129 | 130 | Theme oldTheme = _currentTheme; 131 | 132 | if (theme == Theme.System) 133 | { 134 | _currentTheme = Theme.System; 135 | if (WindowsThemeHelper.IsHighContrastEnabled()) 136 | { 137 | Theme highContrastBaseType = GetHighContrastBaseType(); 138 | ChangeTheme(highContrastBaseType, false); 139 | } 140 | else 141 | { 142 | string baseColor = WindowsThemeHelper.GetWindowsBaseColor(); 143 | ChangeTheme((Theme)Enum.Parse(typeof(Theme), baseColor)); 144 | } 145 | } 146 | else if (theme == Theme.HighContrastOne) 147 | { 148 | _currentTheme = Theme.HighContrastOne; 149 | ControlzEx.Theming.ThemeManager.Current.ChangeTheme(App.Current, HighContrastOneTheme); 150 | } 151 | else if (theme == Theme.HighContrastTwo) 152 | { 153 | _currentTheme = Theme.HighContrastTwo; 154 | ControlzEx.Theming.ThemeManager.Current.ChangeTheme(App.Current, HighContrastTwoTheme); 155 | } 156 | else if (theme == Theme.HighContrastWhite) 157 | { 158 | _currentTheme = Theme.HighContrastWhite; 159 | ControlzEx.Theming.ThemeManager.Current.ChangeTheme(App.Current, HighContrastWhiteTheme); 160 | } 161 | else if (theme == Theme.HighContrastBlack) 162 | { 163 | _currentTheme = Theme.HighContrastBlack; 164 | ControlzEx.Theming.ThemeManager.Current.ChangeTheme(App.Current, HighContrastBlackTheme); 165 | } 166 | else if (theme == Theme.Light) 167 | { 168 | _currentTheme = Theme.Light; 169 | ControlzEx.Theming.ThemeManager.Current.ChangeTheme(App.Current, LightTheme); 170 | } 171 | else if (theme == Theme.Dark) 172 | { 173 | _currentTheme = Theme.Dark; 174 | ControlzEx.Theming.ThemeManager.Current.ChangeTheme(App.Current, DarkTheme); 175 | } 176 | 177 | ThemeChanged?.Invoke(oldTheme, _currentTheme); 178 | } 179 | 180 | private void Current_ThemeChanged(object sender, ThemeChangedEventArgs e) 181 | { 182 | ControlzEx.Theming.ThemeManager.Current.ThemeChanged -= Current_ThemeChanged; 183 | try 184 | { 185 | ResetTheme(); 186 | } 187 | finally 188 | { 189 | ControlzEx.Theming.ThemeManager.Current.ThemeChanged += Current_ThemeChanged; 190 | } 191 | } 192 | 193 | protected virtual void Dispose(bool disposing) 194 | { 195 | if (!_disposed) 196 | { 197 | if (disposing) 198 | { 199 | ControlzEx.Theming.ThemeManager.Current.ThemeChanged -= Current_ThemeChanged; 200 | _disposed = true; 201 | } 202 | } 203 | } 204 | 205 | public void Dispose() 206 | { 207 | Dispose(disposing: true); 208 | GC.SuppressFinalize(this); 209 | } 210 | } 211 | 212 | public delegate void ThemeChangedHandler(Theme oldTheme, Theme newTheme); 213 | } -------------------------------------------------------------------------------- /PowerAccent.UI/SettingsPage/SortPage.xaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 |