├── km_ui.png ├── scancode.doc ├── kmheader800w.png ├── km_numbers_2008.png ├── keymapper ├── Images │ ├── arrow.png │ ├── blank.png │ ├── keymapper.ico │ ├── tallblank.png │ ├── doublewideblank.png │ ├── mediumwideblank.png │ ├── triplewideblank.png │ └── quadruplewideblank.png ├── Project │ ├── KeyMapper.pfx │ └── app.config ├── Forms │ ├── ColourEditor.Designer.cs │ ├── about.cs │ ├── KMBaseForm.cs │ ├── Help.cs │ ├── ColourMap.Designer.cs │ ├── about.Designer.cs │ ├── ColourEditor.resx │ ├── ColourMap.resx │ ├── MappingList.resx │ ├── ColourEditor.cs │ ├── MappingList.cs │ ├── MappingList.Designer.cs │ ├── AddEditMapping.resx │ └── Help.Designer.cs ├── Classes │ ├── Mappings.cs │ ├── MappingFilter.cs │ ├── MapLocation.cs │ ├── KeyboardLayoutType.cs │ ├── KeyMapperKeyPressedEventArgs.cs │ ├── IRegistryTimestampService.cs │ ├── KeyboardRow.cs │ ├── DpiInfo.cs │ ├── UndoRedoMappingStack.cs │ ├── KeyHasher.cs │ ├── KeyboardLayoutElement.cs │ ├── AppMutex.cs │ ├── FontSize.cs │ ├── Key.cs │ ├── Main.cs │ ├── LocalizedKeyset.cs │ ├── RegistryTimestampService.cs │ ├── SeparatorComboDecorator.cs │ ├── KeyMapping.cs │ ├── UserColours.cs │ ├── Interop │ │ └── NativeMethods.cs │ ├── KeySniffer.cs │ └── keydataxml.cs ├── Controls │ ├── KMPictureBox.cs │ ├── KeyboardListCombo.cs │ ├── PanelFader1.cs │ ├── PanelFader1.resx │ ├── KeyPictureBox.resx │ └── KeyPictureBox.cs ├── Interfaces │ └── IKeyData.cs ├── Properties │ ├── app.manifest │ ├── Settings.cs │ ├── AssemblyInfo.cs │ ├── Settings.settings │ ├── Resources.Designer.cs │ ├── Resources.resx │ └── Settings.Designer.cs ├── KeyMapper.csproj.user ├── license.txt ├── KeyMapper.sln ├── Providers │ ├── ConfigFileProvider.cs │ ├── LogProvider.cs │ └── RegistryProvider.cs ├── app.config └── UnitTests │ └── KeyMapper.UnitTests.csproj ├── README.md └── .gitignore /km_ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartd/keymapper/HEAD/km_ui.png -------------------------------------------------------------------------------- /scancode.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartd/keymapper/HEAD/scancode.doc -------------------------------------------------------------------------------- /kmheader800w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartd/keymapper/HEAD/kmheader800w.png -------------------------------------------------------------------------------- /km_numbers_2008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartd/keymapper/HEAD/km_numbers_2008.png -------------------------------------------------------------------------------- /keymapper/Images/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartd/keymapper/HEAD/keymapper/Images/arrow.png -------------------------------------------------------------------------------- /keymapper/Images/blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartd/keymapper/HEAD/keymapper/Images/blank.png -------------------------------------------------------------------------------- /keymapper/Images/keymapper.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartd/keymapper/HEAD/keymapper/Images/keymapper.ico -------------------------------------------------------------------------------- /keymapper/Images/tallblank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartd/keymapper/HEAD/keymapper/Images/tallblank.png -------------------------------------------------------------------------------- /keymapper/Project/KeyMapper.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartd/keymapper/HEAD/keymapper/Project/KeyMapper.pfx -------------------------------------------------------------------------------- /keymapper/Images/doublewideblank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartd/keymapper/HEAD/keymapper/Images/doublewideblank.png -------------------------------------------------------------------------------- /keymapper/Images/mediumwideblank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartd/keymapper/HEAD/keymapper/Images/mediumwideblank.png -------------------------------------------------------------------------------- /keymapper/Images/triplewideblank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartd/keymapper/HEAD/keymapper/Images/triplewideblank.png -------------------------------------------------------------------------------- /keymapper/Forms/ColourEditor.Designer.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartd/keymapper/HEAD/keymapper/Forms/ColourEditor.Designer.cs -------------------------------------------------------------------------------- /keymapper/Images/quadruplewideblank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartd/keymapper/HEAD/keymapper/Images/quadruplewideblank.png -------------------------------------------------------------------------------- /keymapper/Classes/Mappings.cs: -------------------------------------------------------------------------------- 1 | namespace KeyMapper.Classes 2 | { 3 | public enum Mappings 4 | { 5 | CurrentMappings, 6 | SavedMappings 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /keymapper/Classes/MappingFilter.cs: -------------------------------------------------------------------------------- 1 | namespace KeyMapper.Classes 2 | { 3 | public enum MappingFilter 4 | { 5 | Current, 6 | Set, 7 | Cleared 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /keymapper/Classes/MapLocation.cs: -------------------------------------------------------------------------------- 1 | namespace KeyMapper.Classes 2 | { 3 | public enum MapLocation 4 | { 5 | LocalMachineKeyboardLayout, 6 | KeyMapperLocalMachineKeyboardLayout, 7 | KeyMapperMappingsCache 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /keymapper/Classes/KeyboardLayoutType.cs: -------------------------------------------------------------------------------- 1 | namespace KeyMapper.Classes 2 | { 3 | public enum KeyboardLayoutType 4 | { 5 | // These are used in keyboards.xml as the layout IDs 6 | // US = 0, European = 1, Punjabi = 2 7 | 8 | US = 0, European = 1, Punjabi = 2 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /keymapper/Classes/KeyMapperKeyPressedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace KeyMapper.Classes 4 | { 5 | public class KeyMapperKeyPressedEventArgs : EventArgs 6 | { 7 | public KBHookStruct Key { get; } 8 | 9 | public KeyMapperKeyPressedEventArgs(KBHookStruct key) 10 | { 11 | Key = key; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /keymapper/Classes/IRegistryTimestampService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Win32; 3 | 4 | namespace KeyMapper.Classes 5 | { 6 | public interface IRegistryTimestampService 7 | { 8 | DateTime GetRegistryKeyTimestamp(RegistryHive hive, string keyName); 9 | 10 | bool CanUserWriteToKey(RegistryHive hive, string keyName); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /keymapper/Forms/about.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace KeyMapper.Forms 4 | { 5 | public partial class AboutForm : KMBaseForm 6 | { 7 | public AboutForm() 8 | { 9 | InitializeComponent(); 10 | lblAppTitle.Text = "KeyMapper " + Application.ProductVersion.ToString(); 11 | } 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /keymapper/Classes/KeyboardRow.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace KeyMapper.Classes 4 | { 5 | public class KeyboardRow 6 | { 7 | public List Keys { get; private set; } 8 | 9 | public KeyboardRow(List keys) 10 | { 11 | Keys = keys; 12 | } 13 | 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /keymapper/Forms/KMBaseForm.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace KeyMapper.Forms 4 | { 5 | public class KMBaseForm : Form 6 | { 7 | public KMBaseForm() 8 | { 9 | SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true); 10 | // ReSharper disable once VirtualMemberCallInConstructor 11 | Font = new System.Drawing.Font("Verdana", 8.25F); 12 | StartPosition = FormStartPosition.Manual; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /keymapper/Controls/KMPictureBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | using System.Drawing; 4 | 5 | namespace KeyMapper.Controls 6 | { 7 | internal class KMPictureBox : PictureBox 8 | { 9 | public void SetImage(Bitmap bmp) 10 | { 11 | ReleaseImage(); 12 | Image = bmp; 13 | } 14 | 15 | internal void ReleaseImage() 16 | { 17 | IDisposable cleaner = Image; 18 | cleaner?.Dispose(); 19 | Image = null; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /keymapper/Classes/DpiInfo.cs: -------------------------------------------------------------------------------- 1 | using KeyMapper.Classes.Interop; 2 | using System; 3 | 4 | namespace KeyMapper.Classes 5 | { 6 | public static class DpiInfo 7 | { 8 | static DpiInfo() 9 | { 10 | // X is - DpiX = NativeMethods.GetDeviceCaps(NativeMethods.GetDC(IntPtr.Zero), 88); 11 | // But we only use Y in calculations. 12 | // DpiY = NativeMethods.GetDeviceCaps(NativeMethods.GetDC(IntPtr.Zero), 90); 13 | 14 | Dpi = NativeMethods.GetDeviceCaps(NativeMethods.GetDC(IntPtr.Zero), 90); 15 | } 16 | 17 | public static int Dpi { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /keymapper/Classes/UndoRedoMappingStack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | 5 | namespace KeyMapper.Classes 6 | { 7 | public class UndoRedoMappingStack 8 | { 9 | public UndoRedoMappingStack() 10 | { 11 | Mappings = new Stack>(); 12 | } 13 | 14 | public Stack> Mappings { get; } 15 | 16 | public void Push(Collection mappings) 17 | { 18 | Mappings.Push(mappings); 19 | } 20 | 21 | public int Count => Mappings.Count; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /keymapper/Interfaces/IKeyData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using KeyMapper.Classes; 3 | 4 | namespace KeyMapper.Interfaces 5 | { 6 | public interface IKeyData 7 | { 8 | // Keylists. Both of these added together make up the usual 104 keyboard. 9 | IList LocalizableKeys { get; } 10 | 11 | IList NonLocalizableKeys { get; } 12 | 13 | IEnumerable GetGroupList(int threshold); 14 | 15 | Dictionary GetGroupMembers(string group, int threshold); 16 | 17 | // Keyboard layout. 18 | KeyboardLayoutType GetKeyboardLayoutType(string locale); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /keymapper/Properties/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /keymapper/Classes/KeyHasher.cs: -------------------------------------------------------------------------------- 1 | namespace KeyMapper.Classes 2 | { 3 | public class KeyHasher 4 | { 5 | public static int GetHashFromKey(Key key) 6 | { 7 | return GetHashFromKeyData(key.ScanCode, key.Extended); 8 | } 9 | 10 | public static int GetHashFromKeyData(int scanCode, int extended) 11 | { 12 | // Need to preserve the actual extended value as they are all 224 except Pause 13 | // which is 225. 14 | return scanCode * 1000 + extended; 15 | } 16 | 17 | public static int GetScanCodeFromHash(int hash) 18 | { 19 | return hash / 1000; 20 | } 21 | 22 | public static int GetExtendedFromHash(int hash) 23 | { 24 | // Extended value is 224 when set. 25 | return hash % 1000; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /keymapper/Classes/KeyboardLayoutElement.cs: -------------------------------------------------------------------------------- 1 | namespace KeyMapper.Classes 2 | { 3 | public class KeyboardLayoutElement 4 | { 5 | public int ScanCode { get; } 6 | 7 | public int Extended { get; } 8 | 9 | public BlankButton Button { get; } 10 | 11 | public int HorizontalStretch { get; } 12 | 13 | public int VerticalStretch { get; } 14 | 15 | public int RightPadding { get; } 16 | 17 | public KeyboardLayoutElement(int scanCode, int extended, BlankButton button, 18 | int horizontalStretch, int verticalStretch, int rightPadding) 19 | { 20 | ScanCode = scanCode; 21 | Extended = extended; 22 | Button = button; 23 | HorizontalStretch = horizontalStretch; 24 | VerticalStretch = verticalStretch; 25 | RightPadding = rightPadding; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /keymapper/Classes/AppMutex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace KeyMapper.Classes 4 | { 5 | internal class AppMutex : IDisposable 6 | { 7 | private bool disposed; 8 | private System.Threading.Mutex appMutex; 9 | 10 | public bool GetMutex() 11 | { 12 | appMutex = new System.Threading.Mutex(true, "KeyMapperAppMutex", out bool acquired); 13 | 14 | return acquired; 15 | } 16 | 17 | ~AppMutex() 18 | { 19 | Dispose(false); 20 | } 21 | 22 | public void Dispose() 23 | { 24 | Dispose(true); 25 | GC.SuppressFinalize(this); 26 | } 27 | 28 | private void Dispose(bool disposing) 29 | { 30 | if (disposed) 31 | { 32 | return; 33 | } 34 | 35 | if (disposing) 36 | { 37 | appMutex.ReleaseMutex(); 38 | appMutex.Close(); 39 | } 40 | 41 | disposed = true; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /keymapper/KeyMapper.csproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ftp://justkeepswimming.net/httpdocs/keymapper/|ftp://justkeepswimming.net/httpdocs/KeyMapper/|ftp://justkeepswimming.net/httpdocs/KMTest/|ftp://justkeepswimming.net/KeyMapper/|C:\Published Programs\KeyMapper\ 5 | http://justkeepswimming.net/keymapper/|http://justkeepswimming.net/KeyMapper/|http://justkeepswimming.net/KMTest/|http://localhost/install.html/ 6 | http://justkeepswimming.net/KeyMapper/support.aspx 7 | 8 | 9 | 10 | 11 | en-US 12 | true 13 | ShowAllFiles 14 | false 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /keymapper/Properties/Settings.cs: -------------------------------------------------------------------------------- 1 | namespace KeyMapper.Properties { 2 | 3 | 4 | // This class allows you to handle specific events on the settings class: 5 | // The SettingChanging event is raised before a setting's value is changed. 6 | // The PropertyChanged event is raised after a setting's value is changed. 7 | // The SettingsLoaded event is raised after the setting values are loaded. 8 | // The SettingsSaving event is raised before the setting values are saved. 9 | internal sealed partial class Settings { 10 | 11 | public Settings() { 12 | // // To add event handlers for saving and changing settings, uncomment the lines below: 13 | // 14 | // this.SettingChanging += this.SettingChangingEventHandler; 15 | // 16 | // this.SettingsSaving += this.SettingsSavingEventHandler; 17 | // 18 | } 19 | 20 | private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) { 21 | // Add code to handle the SettingChangingEvent event here. 22 | } 23 | 24 | private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) { 25 | // Add code to handle the SettingsSaving event here. 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /keymapper/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | using System.Resources; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Key Mapper")] 9 | [assembly: AssemblyDescription("Remaps keyboard keys")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Rose Hill Solutions")] 12 | [assembly: AssemblyProduct("Key Mapper")] 13 | [assembly: AssemblyCopyright("Copyright © Stuart Dunkeld 2007-2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("cf934515-7836-467c-a5e3-12af6ed72b63")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | [assembly: AssemblyVersion("2.0.0.0")] 33 | [assembly: AssemblyFileVersion("2.0.0.0")] 34 | [assembly: NeutralResourcesLanguageAttribute("en-GB")] 35 | -------------------------------------------------------------------------------- /keymapper/Classes/FontSize.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Drawing.Text; 5 | using System.Globalization; 6 | using System.Linq; 7 | 8 | namespace KeyMapper.Classes 9 | { 10 | public class FontSize 11 | { 12 | public static float BaseFontSize { get; private set; } 13 | 14 | public static void SetFontSizes(float scale) 15 | { 16 | // See what font size fits the scaled-down button 17 | float baseFontSize = 36F; 18 | 19 | // Not using ButtonImages.GetButtonImage as that is where we were called from.. 20 | using (var font = AppController.GetButtonFont(baseFontSize, false)) 21 | using (var bmp = ButtonImages.ResizeBitmap(ButtonImages.GetBitmap(BlankButton.Blank), scale, false)) 22 | using (var g = Graphics.FromImage(bmp)) 23 | { 24 | // Helps MeasureString. Can also pass StringFormat.GenericTypographic apparently ?? 25 | 26 | g.TextRenderingHint = TextRenderingHint.AntiAlias; 27 | var characterWidth = (int)g.MeasureString(((char)77).ToString(CultureInfo.InvariantCulture), font).Width; 28 | 29 | // Only use 90% of the bitmap's size to allow for the edges (especially at small sizes) 30 | float ratio = 0.9F * bmp.Height / 2 / characterWidth; 31 | baseFontSize = baseFontSize * ratio; 32 | } 33 | 34 | BaseFontSize = baseFontSize; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /keymapper/Forms/Help.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | using KeyMapper.Classes; 3 | using static System.Net.WebRequestMethods; 4 | 5 | namespace KeyMapper.Forms 6 | { 7 | public partial class HelpForm : KMBaseForm 8 | { 9 | public HelpForm() 10 | { 11 | InitializeComponent(); 12 | LoadUserSettings(); 13 | FormClosed += FormsManager.ChildFormClosed; 14 | labelFAQ.Links[0].LinkData = "https://github.com/stuartd/keymapper/blob/main/README.md"; 15 | } 16 | 17 | private void LoadUserSettings() 18 | { 19 | var userSettings = new Properties.Settings(); 20 | chkShowHelpAtStartup.Checked = userSettings.ShowHelpFormAtStartup; 21 | } 22 | 23 | private void HelpFormFormClosed(object sender, FormClosedEventArgs e) 24 | { 25 | SaveUserSettings(); 26 | } 27 | 28 | private void SaveUserSettings() 29 | { 30 | var userSettings = new Properties.Settings(); 31 | userSettings.ShowHelpFormAtStartup = chkShowHelpAtStartup.Checked; 32 | userSettings.HelpFormLocation = Location; 33 | userSettings.Save(); 34 | } 35 | 36 | private void labelFAQClick(object sender, LinkLabelLinkClickedEventArgs e) 37 | { 38 | string url = e.Link.LinkData as string; 39 | if (string.IsNullOrEmpty(url) == false) 40 | { 41 | System.Diagnostics.Process.Start(url); 42 | } 43 | } 44 | 45 | 46 | 47 | } 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /keymapper/license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007-2024, Stuart Dunkeld 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 8 | in the documentation and/or other materials provided with the distribution. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 11 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 12 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 13 | IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 14 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 15 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 16 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 17 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 18 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 19 | 20 | The image used for the keyboard keys is based on the blank icon 21 | in the keyboard icon set released by Alan Who? 22 | http://alanwho.com/ 23 | 24 | The image is licensed under the Creative Commons 2.5 license, viewable at 25 | http://creativecommons.org/licenses/by/2.5/deed.en_GB -------------------------------------------------------------------------------- /keymapper/Classes/Key.cs: -------------------------------------------------------------------------------- 1 | namespace KeyMapper.Classes 2 | { 3 | public class Key 4 | { 5 | public string Name { get; } 6 | 7 | public int ScanCode { get; } 8 | 9 | public int Extended { get; } 10 | 11 | public Key() 12 | { 13 | Name = string.Empty; 14 | } 15 | 16 | public Key(int scanCode, int extended, string name) 17 | { 18 | Name = name; 19 | ScanCode = scanCode; 20 | Extended = extended; 21 | } 22 | 23 | public Key(int scanCode, int extended) 24 | : this(scanCode, extended, AppController.GetKeyName(scanCode, extended)) 25 | { 26 | } 27 | 28 | 29 | public override string ToString() 30 | { 31 | return AppController.GetKeyName(ScanCode, Extended); 32 | } 33 | 34 | public static bool operator ==(Key key1, Key key2) 35 | { 36 | // If ScanCode and Extended are the same, it's the same key. 37 | return key1.ScanCode == key2.ScanCode && key1.Extended == key2.Extended; 38 | } 39 | 40 | public override bool Equals(object obj) 41 | { 42 | if (obj.GetType() != GetType()) 43 | { 44 | return false; 45 | } 46 | 47 | return this == (Key)obj; 48 | } 49 | 50 | public override int GetHashCode() 51 | { 52 | return KeyHasher.GetHashFromKey(this); 53 | } 54 | 55 | public static bool operator !=(Key key1, Key key2) 56 | { 57 | return !(key1 == key2); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /keymapper/Forms/ColourMap.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace KeyMapper.Forms 2 | { 3 | partial class ColourMap 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (this.components != null)) 17 | { 18 | this.components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | /// 24 | /// Required method for Designer support - do not modify 25 | /// the contents of this method with the code editor. 26 | /// 27 | private void InitializeComponent() 28 | { 29 | this.SuspendLayout(); 30 | // 31 | // ColourMap 32 | // 33 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 13F); 34 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 35 | this.ClientSize = new System.Drawing.Size(249, 54); 36 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 37 | this.Margin = new System.Windows.Forms.Padding(4); 38 | this.MaximizeBox = false; 39 | this.MinimizeBox = false; 40 | this.Name = "ColourMap"; 41 | this.ShowIcon = false; 42 | this.ShowInTaskbar = false; 43 | this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; 44 | this.Text = "KeyMapper Colour Map"; 45 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ColourMapFormClosing); 46 | this.ResumeLayout(false); 47 | 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /keymapper/Controls/KeyboardListCombo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | using System.Security.Permissions; 3 | using KeyMapper.Classes; 4 | 5 | namespace KeyMapper.Controls 6 | { 7 | public class KeyboardListCombo : ComboBox 8 | { 9 | // Have to subclass this control just to suppress mouse wheel events. 10 | 11 | // Oh well, there's other things we can do as well, like setting up the OwnerDraw stuff. 12 | 13 | public KeyboardListCombo() 14 | { 15 | DrawMode = DrawMode.OwnerDrawVariable; 16 | MeasureItem += ComboItemSeparator.MeasureComboItem; 17 | DrawItem += ComboItemSeparator.DrawComboItem; 18 | } 19 | 20 | [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] 21 | protected override void WndProc(ref Message m) 22 | { 23 | if (m.HWnd != Handle) 24 | { 25 | return; 26 | } 27 | 28 | if (m.Msg == 0x020A) // WM_MOUSEWHEEL 29 | { 30 | if (MouseIsOutsideControl()) 31 | { 32 | return; 33 | } 34 | } 35 | 36 | base.WndProc(ref m); 37 | } 38 | 39 | private bool MouseIsOutsideControl() 40 | { 41 | // Where is Mouse right now? 42 | var localPosition = PointToClient(MousePosition); 43 | 44 | if (localPosition.X < 0 || localPosition.Y < 0) 45 | { 46 | return true; // Mouse is to the left or above the control 47 | } 48 | 49 | if (localPosition.X > Width || localPosition.Y > Height + (DroppedDown ? DropDownHeight : 0)) 50 | { 51 | return true; // Mouse is not over the combo or it's dropdown (if shown) 52 | } 53 | 54 | return false; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /keymapper/KeyMapper.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.329 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeyMapper", "KeyMapper.csproj", "{E0C87C26-3239-4F49-964E-91341A5F6BA0}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x86 = Debug|x86 12 | Release.x64|Any CPU = Release.x64|Any CPU 13 | Release.x64|x86 = Release.x64|x86 14 | Release|Any CPU = Release|Any CPU 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {E0C87C26-3239-4F49-964E-91341A5F6BA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {E0C87C26-3239-4F49-964E-91341A5F6BA0}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {E0C87C26-3239-4F49-964E-91341A5F6BA0}.Debug|x86.ActiveCfg = Debug|x86 21 | {E0C87C26-3239-4F49-964E-91341A5F6BA0}.Debug|x86.Build.0 = Debug|x86 22 | {E0C87C26-3239-4F49-964E-91341A5F6BA0}.Release.x64|Any CPU.ActiveCfg = Release.x64|x64 23 | {E0C87C26-3239-4F49-964E-91341A5F6BA0}.Release.x64|Any CPU.Build.0 = Release.x64|x64 24 | {E0C87C26-3239-4F49-964E-91341A5F6BA0}.Release.x64|x86.ActiveCfg = Release.x64|x86 25 | {E0C87C26-3239-4F49-964E-91341A5F6BA0}.Release.x64|x86.Build.0 = Release.x64|x86 26 | {E0C87C26-3239-4F49-964E-91341A5F6BA0}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {E0C87C26-3239-4F49-964E-91341A5F6BA0}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {E0C87C26-3239-4F49-964E-91341A5F6BA0}.Release|x86.ActiveCfg = Release|x86 29 | {E0C87C26-3239-4F49-964E-91341A5F6BA0}.Release|x86.Build.0 = Release|x86 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {D4127F3E-BB98-4B06-9E57-19C67F21C67A} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /keymapper/Providers/ConfigFileProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.IO; 5 | using KeyMapper.Properties; 6 | 7 | namespace KeyMapper.Providers 8 | { 9 | public static class ConfigFileProvider 10 | { 11 | private static void DeleteInvalidUserFileFromException(ConfigurationException ex) 12 | { 13 | Console.WriteLine("User Config file is invalid - resetting to default"); 14 | 15 | string fileName = ""; 16 | 17 | if (!string.IsNullOrEmpty(ex.Filename)) 18 | { 19 | fileName = ex.Filename; 20 | } 21 | else 22 | { 23 | if (ex.InnerException is ConfigurationErrorsException innerException && !string.IsNullOrEmpty(innerException.Filename)) 24 | { 25 | fileName = innerException.Filename; 26 | } 27 | } 28 | if (File.Exists(fileName)) 29 | { 30 | File.Delete(fileName); 31 | } 32 | } 33 | 34 | public static void ValidateUserConfigFile() 35 | { 36 | // Even with these checks, occasionally get a "failed to load configuration system" 37 | // exception. 38 | try 39 | { 40 | // If file is corrupt this will trigger an exception 41 | var config = ConfigurationManager.OpenExeConfiguration 42 | (ConfigurationUserLevel.PerUserRoamingAndLocal); 43 | } 44 | catch (ConfigurationErrorsException ex) 45 | { 46 | DeleteInvalidUserFileFromException(ex); 47 | return; 48 | } 49 | try 50 | { 51 | // Access a property to find any other error types - invalid XML etc. 52 | var userSettings = new Settings(); 53 | var p = userSettings.ColourEditorLocation; 54 | } 55 | catch (ConfigurationErrorsException ex) 56 | { 57 | DeleteInvalidUserFileFromException(ex); 58 | } 59 | } 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /keymapper/Controls/PanelFader1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Drawing.Imaging; 4 | using System.Windows.Forms; 5 | 6 | namespace KeyMapper.Controls 7 | { 8 | public class PanelFader : Control 9 | { 10 | public event EventHandler FadeComplete; 11 | 12 | private Bitmap startImage; 13 | private Bitmap endImage; 14 | private int fade; 15 | 16 | private readonly Timer timer = new Timer(); 17 | 18 | public PanelFader() 19 | { 20 | SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | 21 | ControlStyles.DoubleBuffer, true); 22 | timer.Tick += TimerFire; 23 | 24 | } 25 | 26 | protected override void OnPaint(PaintEventArgs e) 27 | { 28 | if (startImage == null || endImage == null) 29 | { 30 | return; 31 | } 32 | 33 | e.Graphics.DrawImage(startImage, ClientRectangle, 0, 0, startImage.Width, startImage. 34 | Height, GraphicsUnit.Pixel); 35 | 36 | var ia = new ImageAttributes(); 37 | 38 | var cm = new ColorMatrix 39 | { 40 | Matrix33 = 1.0f / 255 * fade 41 | }; 42 | 43 | ia.SetColorMatrix(cm); 44 | 45 | e.Graphics.DrawImage(endImage, ClientRectangle, 0, 0, startImage.Width, startImage. 46 | Height, GraphicsUnit.Pixel, ia); 47 | 48 | base.OnPaint(e); 49 | 50 | } 51 | 52 | public void DoFade(Control panel1, Control panel2) 53 | { 54 | startImage = new Bitmap(panel1.Width, panel1.Height); 55 | endImage = new Bitmap(panel2.Width, panel2.Height); 56 | 57 | panel1.DrawToBitmap(startImage, panel1.ClientRectangle); 58 | panel2.DrawToBitmap(endImage, panel2.ClientRectangle); 59 | 60 | Location = panel1.Location; 61 | Size = panel1.Size; 62 | 63 | fade = 1; 64 | 65 | timer.Interval = 1; 66 | timer.Enabled = true; 67 | 68 | } 69 | 70 | private void TimerFire(object sender, EventArgs e) 71 | { 72 | fade += 20; 73 | 74 | if (fade >= 255) 75 | { 76 | fade = 255; 77 | timer.Enabled = false; 78 | FadeComplete(null, null); 79 | } 80 | 81 | Invalidate(); 82 | } 83 | 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /keymapper/Providers/LogProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Text; 6 | using KeyMapper.Classes; 7 | 8 | namespace KeyMapper.Providers 9 | { 10 | public static class LogProvider 11 | { 12 | static LogProvider() 13 | { 14 | logFileName = Path.Combine(AppController.KeyMapperFilePath, ConsoleOutputFilename); 15 | } 16 | 17 | private const string ConsoleOutputFilename = "keymapper.log"; 18 | 19 | // Redirect console output 20 | private static StreamWriter consoleWriterStream; 21 | private static readonly string logFileName; 22 | 23 | public static void ClearLogFile() 24 | { 25 | if (consoleWriterStream != null) 26 | { 27 | consoleWriterStream.BaseStream.SetLength(0); 28 | Console.WriteLine("Log file cleared: {0}", DateTime.Now); 29 | } 30 | else 31 | { 32 | Console.Write("Can't clear log in debug mode."); 33 | } 34 | } 35 | 36 | public static void RedirectConsoleOutput() 37 | { 38 | string path = logFileName; 39 | string existingLogEntries = string.Empty; 40 | 41 | if (string.IsNullOrEmpty(path)) 42 | { 43 | return; 44 | } 45 | 46 | if (File.Exists(path)) 47 | { 48 | // In order to be able to clear the log, the StreamWriter must be opened in create mode. 49 | // so read the contents of the log first. 50 | 51 | using (var sr = new StreamReader(path)) 52 | { 53 | existingLogEntries = sr.ReadToEnd(); 54 | } 55 | } 56 | 57 | consoleWriterStream = new StreamWriter(path, false, Encoding.UTF8) 58 | { 59 | AutoFlush = true 60 | }; 61 | consoleWriterStream.Write(existingLogEntries); 62 | 63 | // Direct standard output to the log file. 64 | Console.SetOut(consoleWriterStream); 65 | 66 | Console.WriteLine("Logging started: {0}", DateTime.Now); 67 | } 68 | 69 | public static void CloseConsoleOutput() 70 | { 71 | consoleWriterStream?.Close(); 72 | } 73 | 74 | public static void ViewLogFile() 75 | { 76 | string logfile = logFileName; 77 | if (string.IsNullOrEmpty(logfile)) 78 | { 79 | return; 80 | } 81 | 82 | Process.Start(logfile); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /keymapper/Project/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 |
10 | 11 | 12 | 13 | 14 | 15 | 0, 0 16 | 17 | 18 | False 19 | 20 | 21 | False 22 | 23 | 24 | 0 25 | 26 | 27 | 0, 0 28 | 29 | 30 | 0 31 | 32 | 33 | False 34 | 35 | 36 | 0, 0 37 | 38 | 39 | 0 40 | 41 | 42 | False 43 | 44 | 45 | False 46 | 47 | 48 | 0, 0 49 | 50 | 51 | 0 52 | 53 | 54 | 0 55 | 56 | 57 | 0, 0 58 | 59 | 60 | 0 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /keymapper/Providers/RegistryProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using KeyMapper.Classes; 3 | using Microsoft.Win32; 4 | 5 | namespace KeyMapper.Providers 6 | { 7 | public static class RegistryProvider 8 | { 9 | public static bool GetRegistryLocation(MapLocation which, ref RegistryHive hive, ref string keyName, ref string valueName) 10 | { 11 | hive = RegistryHive.CurrentUser; 12 | 13 | switch (which) 14 | { 15 | case MapLocation.LocalMachineKeyboardLayout: 16 | hive = RegistryHive.LocalMachine; 17 | keyName = @"SYSTEM\CurrentControlSet\Control\Keyboard Layout"; 18 | valueName = "ScanCode Map"; 19 | break; 20 | 21 | case MapLocation.KeyMapperLocalMachineKeyboardLayout: 22 | keyName = AppController.ApplicationRegistryKeyName; 23 | valueName = "Mappings"; 24 | break; 25 | 26 | case MapLocation.KeyMapperMappingsCache: 27 | keyName = AppController.ApplicationRegistryKeyName; 28 | valueName = "MappingCache"; 29 | break; 30 | 31 | default: 32 | return false; 33 | } 34 | 35 | return true; 36 | } 37 | 38 | public static byte[] GetScanCodeMapFromRegistry(MapLocation which) 39 | { 40 | RegistryKey registry = null; 41 | var hive = RegistryHive.CurrentUser; 42 | string keyName = string.Empty; 43 | string valueName = string.Empty; 44 | 45 | if (GetRegistryLocation(which, ref hive, ref keyName, ref valueName)) 46 | { 47 | switch (hive) 48 | { 49 | case RegistryHive.LocalMachine: 50 | registry = Registry.LocalMachine.OpenSubKey(keyName); 51 | break; 52 | case RegistryHive.CurrentUser: 53 | registry = Registry.CurrentUser.OpenSubKey(keyName); 54 | break; 55 | } 56 | } 57 | 58 | var keyValue = registry?.GetValue(valueName, null); 59 | 60 | if (keyValue == null || 61 | registry.GetValueKind(valueName) != RegistryValueKind.Binary || 62 | keyValue.GetType() != Type.GetType("System.Byte[]")) 63 | { 64 | // Not there, or not the right type. 65 | return null; 66 | } 67 | 68 | // Can't see how this cast can fail, shrug, will return null anyway. 69 | var bytecodes = keyValue as byte[]; 70 | 71 | return bytecodes; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /keymapper/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 0, 0 12 | 13 | 14 | True 15 | 16 | 17 | False 18 | 19 | 20 | 0 21 | 22 | 23 | 0, 0 24 | 25 | 26 | False 27 | 28 | 29 | 0, 0 30 | 31 | 32 | False 33 | 34 | 35 | False 36 | 37 | 38 | 0, 0 39 | 40 | 41 | 0 42 | 43 | 44 | 0, 0 45 | 46 | 47 | True 48 | 49 | 50 | 0, 0 51 | 52 | 53 | False 54 | 55 | 56 | True 57 | 58 | 59 | 0 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /keymapper/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 0, 0 9 | 10 | 11 | True 12 | 13 | 14 | False 15 | 16 | 17 | 0 18 | 19 | 20 | 0, 0 21 | 22 | 23 | False 24 | 25 | 26 | 0, 0 27 | 28 | 29 | False 30 | 31 | 32 | False 33 | 34 | 35 | 0, 0 36 | 37 | 38 | 0 39 | 40 | 41 | 0, 0 42 | 43 | 44 | True 45 | 46 | 47 | 0, 0 48 | 49 | 50 | False 51 | 52 | 53 | True 54 | 55 | 56 | 0 57 | 58 | 59 | -------------------------------------------------------------------------------- /keymapper/Classes/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | using KeyMapper.Forms; 4 | using KeyMapper.Providers; 5 | 6 | [assembly: CLSCompliant(true)] 7 | namespace KeyMapper.Classes 8 | { 9 | internal class main 10 | { 11 | [STAThread] 12 | private static void Main() 13 | { 14 | if (AppController.IsOnlyAppInstance() == false) 15 | { 16 | return; 17 | } 18 | 19 | Application.EnableVisualStyles(); 20 | Application.SetCompatibleTextRenderingDefault(false); 21 | 22 | Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); 23 | Application.ThreadException += ApplicationThreadException; 24 | AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler; 25 | 26 | AppController.CreateAppDirectory(); 27 | 28 | #if DEBUG 29 | #else 30 | Console.Write("Redirecting console output"); 31 | LogProvider.RedirectConsoleOutput(); 32 | #endif 33 | 34 | ConfigFileProvider.ValidateUserConfigFile(); 35 | 36 | var userSettings = new Properties.Settings(); 37 | if (userSettings.UpgradeRequired) 38 | { 39 | Console.WriteLine("Upgrading settings to new version"); 40 | userSettings.Upgrade(); 41 | userSettings.UpgradeRequired = false; 42 | userSettings.Save(); 43 | } 44 | 45 | AppController.Start(); 46 | 47 | Application.Run(new KeyboardForm()); 48 | 49 | AppController.Close(); 50 | // Release static events or else leak. 51 | Application.ThreadException -= ApplicationThreadException; 52 | AppDomain.CurrentDomain.UnhandledException -= UnhandledExceptionHandler; 53 | } 54 | 55 | private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e) 56 | { 57 | if (e.ExceptionObject is Exception ex) 58 | { 59 | Console.WriteLine("Unhandled exception (1): {0}", ex); 60 | } 61 | } 62 | 63 | private static void ApplicationThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) 64 | { 65 | if (e.Exception is ArgumentException aex) 66 | { 67 | if (aex.ParamName != null && aex.ParamName.ToUpperInvariant() == "CULTURE") 68 | { 69 | // This is a bug in the .NET framework where some cultures won't load on Windows Server 2003 70 | // and throw "Culture ID x (0xX) is not a supported culture" 71 | 72 | Console.WriteLine("Handled culture info error"); 73 | return; 74 | } 75 | } 76 | 77 | Console.WriteLine("Unhandled exception (1): {0}", e.Exception); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /keymapper/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace KeyMapper.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("KeyMapper.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /keymapper/Classes/LocalizedKeyset.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace KeyMapper.Classes 5 | { 6 | /// 7 | /// This represents all the keyboard keys in the current keyboard layout. 8 | /// 9 | public class LocalizedKeySet 10 | { 11 | private readonly Hashtable keys = new Hashtable(); 12 | private readonly Hashtable localizableKeyNames = new Hashtable(); 13 | private readonly IList localizableKeys = new KeyDataXml().LocalizableKeys; 14 | private readonly Hashtable nonLocalizableKeyNames = new Hashtable(); 15 | private readonly IList nonLocalizableKeys = new KeyDataXml().NonLocalizableKeys; 16 | 17 | private readonly List overLongKeys = new List(0); 18 | 19 | public LocalizedKeySet() 20 | { 21 | keys.Clear(); 22 | overLongKeys.Clear(); 23 | 24 | GetLocalizableKeyNames(); 25 | GetNonLocalizableKeyNames(); 26 | 27 | foreach (DictionaryEntry de in localizableKeyNames) 28 | { 29 | keys.Add(de.Key, de.Value); 30 | } 31 | 32 | foreach (DictionaryEntry de in nonLocalizableKeyNames) 33 | { 34 | keys.Add(de.Key, de.Value); 35 | } 36 | } 37 | 38 | public bool ContainsKey(int hash) 39 | { 40 | return keys.Contains(hash); 41 | } 42 | 43 | public string GetKeyName(int hash) 44 | { 45 | return (string)keys[hash]; 46 | } 47 | 48 | public bool IsKeyNameOverlong(int hash) 49 | { 50 | return overLongKeys.Contains(hash); 51 | } 52 | 53 | public bool IsKeyLocalizable(int hash) 54 | { 55 | return localizableKeys.Contains(hash); 56 | } 57 | 58 | private void GetNonLocalizableKeyNames() 59 | { 60 | // These have to be extracted from the keycode XML 61 | // as they aren't available otherwise (they don't change) 62 | 63 | var kd = new KeyDataXml(); 64 | 65 | foreach (int code in nonLocalizableKeys) 66 | { 67 | nonLocalizableKeyNames.Add(code, kd.GetKeyNameFromCode(code)); 68 | } 69 | } 70 | 71 | private void GetLocalizableKeyNames() 72 | { 73 | localizableKeyNames.Clear(); 74 | 75 | foreach (int hash in localizableKeys) 76 | { 77 | // None of the localizable names need the extended bit. 78 | int scanCode = KeyHasher.GetScanCodeFromHash(hash); 79 | 80 | // Need to track if a localizable key is a symbol - shifter-symbol 81 | // combination but it's length is not 3 - i.e. instead of 1 and ! 82 | // it is Qaf and RehYehAlefLam (as on the Farsi keyboard) 83 | 84 | bool overlong = false; 85 | string name = KeyboardHelper.GetKeyName(scanCode, ref overlong); 86 | 87 | nonLocalizableKeyNames.Add(hash, name); 88 | if (overlong) 89 | { 90 | // Console.WriteLine("Adding overlong key: code {0} name length: {1} name: {2}", hash, name.Length, name); 91 | overLongKeys.Add(hash); 92 | } 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /keymapper/Classes/RegistryTimestampService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Win32; 3 | using KeyMapper.Classes.Interop; 4 | 5 | namespace KeyMapper.Classes 6 | { 7 | public class RegistryTimestampService : IRegistryTimestampService 8 | { 9 | private const int KEY_QUERY_VALUE = 0x0001; 10 | private const int KEY_SET_VALUE = 0x0002; 11 | 12 | public DateTime GetRegistryKeyTimestamp(RegistryHive hive, string keyName) 13 | { 14 | long ts = GetRawRegistryKeyTimestamp(hive, keyName); 15 | 16 | var dt = ts != 0 ? DateTime.FromFileTimeUtc(ts) : DateTime.MinValue; 17 | 18 | return dt.ToLocalTime(); 19 | } 20 | 21 | private long GetRawRegistryKeyTimestamp(RegistryHive hive, string keyName) 22 | { 23 | if (string.IsNullOrEmpty(keyName)) 24 | { 25 | return 0; // Otherwise the function opens HKLM (or HKCU) again. 26 | } 27 | 28 | var hKey = OpenKey(hive, keyName, KEY_QUERY_VALUE); 29 | 30 | if (hKey == UIntPtr.Zero) 31 | { 32 | return 0; // Didn't open key 33 | } 34 | 35 | uint result2 = NativeMethods.RegQueryInfoKey( 36 | hKey, IntPtr.Zero, 37 | IntPtr.Zero, IntPtr.Zero, 38 | IntPtr.Zero, IntPtr.Zero, 39 | IntPtr.Zero, IntPtr.Zero, 40 | IntPtr.Zero, IntPtr.Zero, 41 | IntPtr.Zero, out long keyTimestamp); 42 | 43 | if (result2 != 0) 44 | { 45 | keyTimestamp = 0; // Failed, don't return whatever value was supplied. 46 | } 47 | 48 | NativeMethods.RegCloseKey(hKey); 49 | 50 | return keyTimestamp; 51 | } 52 | 53 | public bool CanUserWriteToKey(RegistryHive hive, string keyName) 54 | { 55 | var hKey = OpenKey(hive, keyName, KEY_SET_VALUE); 56 | if (hKey == UIntPtr.Zero) 57 | { 58 | return false; 59 | } 60 | 61 | NativeMethods.RegCloseKey(hKey); 62 | return true; 63 | } 64 | 65 | private UIntPtr OpenKey(RegistryHive hive, string keyName, int requiredAccess) 66 | { 67 | UIntPtr hivePointer; 68 | 69 | switch (hive) 70 | { 71 | case RegistryHive.ClassesRoot: 72 | hivePointer = (UIntPtr)0x80000000; 73 | break; 74 | case RegistryHive.CurrentUser: 75 | hivePointer = (UIntPtr)0x80000001; 76 | break; 77 | case RegistryHive.LocalMachine: 78 | hivePointer = (UIntPtr)0x80000002; 79 | break; 80 | case RegistryHive.Users: 81 | hivePointer = (UIntPtr)0x80000003; 82 | break; 83 | case RegistryHive.CurrentConfig: 84 | hivePointer = (UIntPtr)0x80000005; 85 | break; 86 | default: 87 | return UIntPtr.Zero; 88 | } 89 | 90 | int result = NativeMethods.RegOpenKeyEx(hivePointer, keyName, 0, requiredAccess, out var hKey); 91 | 92 | if (result == 0) 93 | { 94 | return hKey; 95 | } 96 | 97 | return UIntPtr.Zero; 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /keymapper/Classes/SeparatorComboDecorator.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Windows.Forms; 3 | 4 | namespace KeyMapper.Classes 5 | { 6 | /// 7 | /// This decorator class owes a great deal to http://blogs.msdn.com/jfoscoding/articles/456968.aspx 8 | /// 9 | public static class ComboItemSeparator 10 | { 11 | private const int separatorHeight = 3; 12 | 13 | private const int verticalItemPadding = 4; 14 | 15 | internal class SeparatorItem 16 | { 17 | private readonly string name; 18 | 19 | public SeparatorItem(string name) 20 | { 21 | this.name = name; 22 | } 23 | 24 | public override string ToString() 25 | { 26 | if (name != null) 27 | { 28 | return name; 29 | } 30 | return base.ToString(); 31 | } 32 | 33 | } 34 | 35 | internal static void MeasureComboItem(object sender, MeasureItemEventArgs e) 36 | { 37 | if (e.Index == -1) 38 | { 39 | return; 40 | } 41 | 42 | if (sender is not ComboBox combo) 43 | { 44 | return; 45 | } 46 | 47 | var comboBoxItem = combo.Items[e.Index]; 48 | 49 | var textSize = TextRenderer.MeasureText(comboBoxItem.ToString(), combo.Font); 50 | 51 | e.ItemHeight = textSize.Height + verticalItemPadding; 52 | e.ItemWidth = textSize.Width; 53 | 54 | if (comboBoxItem is SeparatorItem) 55 | { 56 | // one white line, one dark, one white. 57 | e.ItemHeight += separatorHeight; 58 | } 59 | } 60 | 61 | internal static void DrawComboItem(object sender, DrawItemEventArgs e) 62 | { 63 | if (e.Index == -1) 64 | { 65 | return; 66 | } 67 | 68 | if (sender is ComboBox combo) 69 | { 70 | var comboBoxItem = combo.Items[e.Index]; 71 | 72 | e.DrawBackground(); 73 | e.DrawFocusRectangle(); 74 | 75 | bool isSeparatorItem = comboBoxItem is SeparatorItem; 76 | 77 | var bounds = e.Bounds; 78 | // adjust the bounds so that the text is centered properly. 79 | // if we're a separator, remove the separator height 80 | 81 | if (isSeparatorItem && (e.State & DrawItemState.ComboBoxEdit) != DrawItemState.ComboBoxEdit) 82 | { 83 | bounds.Height -= separatorHeight; 84 | } 85 | 86 | TextRenderer.DrawText(e.Graphics, comboBoxItem.ToString(), combo.Font, 87 | bounds, e.ForeColor, TextFormatFlags.Left & TextFormatFlags.VerticalCenter); 88 | 89 | // draw the separator line 90 | if (isSeparatorItem && (e.State & DrawItemState.ComboBoxEdit) != DrawItemState.ComboBoxEdit) 91 | { 92 | var separatorRect = new Rectangle(e.Bounds.Left, e.Bounds.Bottom - separatorHeight, e.Bounds.Width, separatorHeight); 93 | 94 | // fill the background behind the separator 95 | using (Brush br = new SolidBrush(combo.BackColor)) 96 | { 97 | e.Graphics.FillRectangle(br, separatorRect); 98 | } 99 | e.Graphics.DrawLine(SystemPens.ControlText, separatorRect.Left + 2, separatorRect.Top + 1, 100 | separatorRect.Right - 2, separatorRect.Top + 1); 101 | 102 | } 103 | } 104 | 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /keymapper/Classes/KeyMapping.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace KeyMapper.Classes 4 | { 5 | public class KeyMapping 6 | { 7 | public Key From { get; } 8 | 9 | public Key To { get; } 10 | 11 | public KeyMapping() : this(new Key(), new Key()) 12 | { 13 | } 14 | 15 | public KeyMapping(Key keyFrom, Key keyTo) 16 | { 17 | if (ReferenceEquals(keyFrom, null) || ReferenceEquals(keyTo, null)) 18 | { 19 | throw new NullReferenceException("Key can't be null"); 20 | } 21 | 22 | From = keyFrom; 23 | To = keyTo; 24 | } 25 | 26 | public override string ToString() 27 | { 28 | return MappingDescription; 29 | } 30 | 31 | public string MappingDescription 32 | { 33 | get 34 | { 35 | bool disabled; // Is the mapping disabled or to a key? 36 | 37 | string description = string.Empty; 38 | 39 | if (MappingsManager.IsMapped(this) == false) 40 | { 41 | // This 'mapping' is not currently mapped, so it must have been mapped previously and cleared. 42 | var km = MappingsManager.GetClearedMapping(From.ScanCode, From.Extended); 43 | if (MappingsManager.IsEmptyMapping(km) == false) 44 | { 45 | disabled = MappingsManager.IsDisabledMapping(km); 46 | 47 | description = From.Name + (disabled ? " will be enabled" : " will be unmapped") + " after a restart"; 48 | } 49 | } 50 | else 51 | { 52 | // So, mapped to something. 53 | // Need to also know if it's Current or Pending 54 | 55 | bool pending = MappingsManager.IsMappingPending(this); 56 | 57 | disabled = MappingsManager.IsDisabledMapping(this); 58 | 59 | description = From.Name + (pending ? " will be" : " is"); 60 | description += disabled ? " disabled" : " mapped to " + To.Name; 61 | 62 | if (pending) 63 | { 64 | description += " after a restart"; 65 | } 66 | } 67 | 68 | return description; 69 | } 70 | } 71 | 72 | // This will match anything created by New KeyMapping() with no parameters 73 | public bool IsEmpty() 74 | { 75 | return From.ScanCode == 0 && To.ScanCode == 0 && From.Extended == 0 && To.Extended == 0; 76 | } 77 | 78 | public bool IsValid() 79 | { 80 | // To be a valid mapping, From.ScanCode must be greater than zero (to be a key) 81 | // and To.ScanCode must be at least zero (either disabled or a key) 82 | 83 | return !IsEmpty() 84 | && From.ScanCode > 0 85 | && To.ScanCode > -1; 86 | 87 | } 88 | 89 | public static bool operator ==(KeyMapping map1, KeyMapping map2) 90 | { 91 | return map1.From == map2.From && map1.To == map2.To; 92 | } 93 | 94 | public override bool Equals(object obj) 95 | { 96 | if (obj.GetType() != GetType()) 97 | { 98 | return false; 99 | } 100 | 101 | return this == (KeyMapping)obj; 102 | } 103 | 104 | public static bool operator !=(KeyMapping map1, KeyMapping map2) 105 | { 106 | return !(map1 == map2); 107 | } 108 | 109 | public override int GetHashCode() 110 | { 111 | return KeyHasher.GetHashFromKey(From) * 31 + KeyHasher.GetHashFromKey(To); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /keymapper/Forms/about.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace KeyMapper.Forms 2 | { 3 | partial class AboutForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (this.components != null)) 17 | { 18 | this.components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | /// 24 | /// Required method for Designer support - do not modify 25 | /// the contents of this method with the code editor. 26 | /// 27 | private void InitializeComponent() 28 | { 29 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(AboutForm)); 30 | this.lblAppTitle = new System.Windows.Forms.Label(); 31 | this.label2 = new System.Windows.Forms.Label(); 32 | this.label3 = new System.Windows.Forms.Label(); 33 | this.label4 = new System.Windows.Forms.Label(); 34 | this.SuspendLayout(); 35 | // 36 | // lblAppTitle 37 | // 38 | this.lblAppTitle.AutoSize = true; 39 | this.lblAppTitle.Font = new System.Drawing.Font("Verdana", 18.25F); 40 | this.lblAppTitle.Location = new System.Drawing.Point(5, 9); 41 | this.lblAppTitle.Name = "lblAppTitle"; 42 | this.lblAppTitle.Size = new System.Drawing.Size(155, 31); 43 | this.lblAppTitle.TabIndex = 0; 44 | this.lblAppTitle.Text = "KeyMapper"; 45 | // 46 | // label2 47 | // 48 | this.label2.AutoSize = true; 49 | this.label2.Font = new System.Drawing.Font("Verdana", 10.25F); 50 | this.label2.Location = new System.Drawing.Point(10, 54); 51 | this.label2.Name = "label2"; 52 | this.label2.Size = new System.Drawing.Size(302, 34); 53 | this.label2.TabIndex = 2; 54 | this.label2.Text = "Copyright (c) 2007-2008 Stuart Dunkeld.\r\nAll rights reserved."; 55 | // 56 | // label3 57 | // 58 | this.label3.AutoSize = true; 59 | this.label3.Font = new System.Drawing.Font("Verdana", 10.25F); 60 | this.label3.Location = new System.Drawing.Point(8, 104); 61 | this.label3.Name = "label3"; 62 | this.label3.Size = new System.Drawing.Size(275, 17); 63 | this.label3.TabIndex = 3; 64 | this.label3.Text = "Rose Hill Solutions, Brighton, England"; 65 | // 66 | // label4 67 | // 68 | this.label4.AutoSize = true; 69 | this.label4.Font = new System.Drawing.Font("Verdana", 6.25F); 70 | this.label4.Location = new System.Drawing.Point(8, 136); 71 | this.label4.Name = "label4"; 72 | this.label4.Size = new System.Drawing.Size(312, 36); 73 | this.label4.TabIndex = 4; 74 | this.label4.Text = "The image used for the keyboard keys is based on the blank\r\nicon in the keyboard " + 75 | "icon set by Alan Who? and is licensed \r\nunder the Creative Commons 2.5 license. " + 76 | "http://alanwho.com"; 77 | // 78 | // AboutForm 79 | // 80 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 13F); 81 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 82 | this.ClientSize = new System.Drawing.Size(324, 182); 83 | this.Controls.Add(this.label4); 84 | this.Controls.Add(this.label3); 85 | this.Controls.Add(this.label2); 86 | this.Controls.Add(this.lblAppTitle); 87 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 88 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 89 | this.MaximizeBox = false; 90 | this.MinimizeBox = false; 91 | this.Name = "AboutForm"; 92 | this.ShowInTaskbar = false; 93 | this.Text = "About KeyMapper"; 94 | this.ResumeLayout(false); 95 | this.PerformLayout(); 96 | 97 | } 98 | 99 | private System.Windows.Forms.Label lblAppTitle; 100 | private System.Windows.Forms.Label label2; 101 | private System.Windows.Forms.Label label3; 102 | private System.Windows.Forms.Label label4; 103 | } 104 | } -------------------------------------------------------------------------------- /keymapper/UnitTests/KeyMapper.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {0D7B69B6-17A1-4A2E-BB8C-DA9EB7650362} 9 | Library 10 | Properties 11 | KeyMapper.UnitTests 12 | KeyMapper.UnitTests 13 | v4.5 14 | 512 15 | 16 | 17 | 18 | 19 | 3.5 20 | 21 | 22 | publish\ 23 | true 24 | Disk 25 | false 26 | Foreground 27 | 7 28 | Days 29 | false 30 | false 31 | true 32 | 0 33 | 1.0.0.%2a 34 | false 35 | false 36 | true 37 | 38 | 39 | true 40 | full 41 | false 42 | bin\Debug\ 43 | DEBUG;TRACE 44 | prompt 45 | 4 46 | false 47 | 48 | 49 | pdbonly 50 | true 51 | bin\Release\ 52 | TRACE 53 | prompt 54 | 4 55 | false 56 | 57 | 58 | 59 | False 60 | References\nunit.framework.dll 61 | 62 | 63 | False 64 | References\Rhino.Mocks.dll 65 | 66 | 67 | 68 | 3.5 69 | 70 | 71 | 3.5 72 | 73 | 74 | 3.5 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | {E0C87C26-3239-4F49-964E-91341A5F6BA0} 85 | KeyMapper 86 | 87 | 88 | 89 | 90 | False 91 | .NET Framework 3.5 SP1 Client Profile 92 | false 93 | 94 | 95 | False 96 | .NET Framework 3.5 SP1 97 | true 98 | 99 | 100 | 101 | 108 | -------------------------------------------------------------------------------- /keymapper/Classes/UserColours.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Drawing.Imaging; 5 | using Microsoft.Win32; 6 | 7 | namespace KeyMapper.Classes 8 | { 9 | internal static class UserColourSettingManager 10 | { 11 | public static event EventHandler ColoursChanged; 12 | private static bool loaded; 13 | 14 | private static readonly Dictionary settings 15 | = new Dictionary(); 16 | 17 | static UserColourSettingManager() 18 | { 19 | ColoursChanged += LoadColours; 20 | } 21 | 22 | private static void LoadColours(object sender, EventArgs e) 23 | { 24 | settings.Clear(); 25 | foreach (ButtonEffect effect in Enum.GetValues(typeof(ButtonEffect))) 26 | { 27 | var setting = GetColourSettingFromRegistry(effect); 28 | if (setting != null) 29 | { 30 | settings.Add(effect, setting); 31 | } 32 | } 33 | } 34 | 35 | public static void SaveSetting(ButtonEffect effect, ColorMatrix cm, int FontColour) 36 | { 37 | string key = AppController.ApplicationRegistryKeyName; 38 | string subkey = effect.ToString(); 39 | 40 | var reg = Registry.CurrentUser.CreateSubKey(key + @"\UserColours\" + subkey); 41 | 42 | if (reg == null) 43 | { 44 | return; 45 | } 46 | 47 | reg.SetValue("FontColour", FontColour); 48 | for (int i = 0; i < 5; i++) 49 | { 50 | for (int j = 0; j < 5; j++) 51 | { 52 | string name = "Matrix" 53 | + i.ToString(System.Globalization.CultureInfo.InvariantCulture) 54 | + j.ToString(System.Globalization.CultureInfo.InvariantCulture); 55 | 56 | var value = cm.GetType().GetProperty(name).GetValue(cm, null); 57 | // Console.WriteLine("i: {0}, j: {1}, value: {2}", i, j, value); 58 | reg.SetValue(name, (float)decimal.Parse(value.ToString(), System.Globalization.CultureInfo.InvariantCulture)); 59 | } 60 | } 61 | 62 | RaiseColoursChangedEvent(); 63 | } 64 | 65 | public static void RaiseColoursChangedEvent() 66 | { 67 | ColoursChanged?.Invoke(null, null); 68 | } 69 | 70 | public static UserColourSetting GetColourSettings(ButtonEffect effect) 71 | { 72 | if (loaded == false) 73 | { 74 | LoadColours(null, EventArgs.Empty); 75 | loaded = true; 76 | } 77 | 78 | if (settings.ContainsKey(effect)) 79 | { 80 | return settings[effect]; 81 | } 82 | 83 | return null; 84 | } 85 | 86 | 87 | private static UserColourSetting GetColourSettingFromRegistry(ButtonEffect effect) 88 | { 89 | // Need to be defensively minded as user could change, 90 | // delete, or change type of registry settings. 91 | 92 | string subkey = AppController.ApplicationRegistryKeyName + @"\UserColours\" + effect.ToString(); 93 | 94 | var reg = Registry.CurrentUser.OpenSubKey(subkey); 95 | if (reg == null) // No settings have been defined for this effect 96 | { 97 | return null; 98 | } 99 | 100 | var setting = new UserColourSetting(); 101 | 102 | // User may have changed type of FontColour 103 | // Using nullable int as any possible integer value could be a valid 104 | // ToARGB() result (well, I'm assuming it could anyway) 105 | 106 | int? fontColourArgb; 107 | 108 | var value = reg.GetValue("FontColour"); 109 | if (value == null || reg.GetValueKind("FontColour") != RegistryValueKind.DWord) 110 | { 111 | fontColourArgb = Color.Black.ToArgb(); 112 | } 113 | else 114 | { 115 | fontColourArgb = (int?)value; 116 | } 117 | 118 | setting.FontColour = Color.FromArgb((int)fontColourArgb); 119 | 120 | var cm = new ColorMatrix(); 121 | 122 | for (int i = 0; i < 5; i++) 123 | { 124 | for (int j = 0; j < 5; j++) 125 | { 126 | string name = "Matrix" 127 | + i.ToString(System.Globalization.CultureInfo.InvariantCulture) 128 | + j.ToString(System.Globalization.CultureInfo.InvariantCulture); 129 | 130 | value = reg.GetValue(name); 131 | if (value != null) 132 | { 133 | if (float.TryParse(value.ToString(), out float whatActuallyIsThisValue)) 134 | { 135 | cm.GetType().GetProperty(name).SetValue(cm, whatActuallyIsThisValue, null); 136 | } 137 | } 138 | } 139 | } 140 | 141 | setting.Matrix = cm; 142 | 143 | return setting; 144 | } 145 | } 146 | 147 | public class UserColourSetting 148 | { 149 | // This is the class that will be stored in the user settings for custom colours 150 | private int fontColour = Color.Black.ToArgb(); 151 | 152 | public Color FontColour 153 | { 154 | get => Color.FromArgb(fontColour); 155 | set => fontColour = value.ToArgb(); 156 | } 157 | 158 | public ColorMatrix Matrix { get; set; } = new ColorMatrix(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /keymapper/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /keymapper/Controls/PanelFader1.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /keymapper/Forms/ColourEditor.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /keymapper/Forms/ColourMap.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /keymapper/Classes/Interop/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Runtime.InteropServices; 4 | // ReSharper disable IdentifierTypo 5 | 6 | namespace KeyMapper.Classes.Interop 7 | { 8 | internal class NativeMethods 9 | { 10 | private NativeMethods() { } 11 | 12 | [DllImport("user32.dll", CharSet = CharSet.Unicode)] 13 | internal static extern void LockWindowUpdate(IntPtr hWnd); 14 | 15 | [DllImport("user32.dll", CharSet = CharSet.Unicode)] 16 | internal static extern IntPtr GetDC(IntPtr hwnd); 17 | 18 | [DllImport("gdi32.dll", CharSet = CharSet.Unicode)] 19 | internal static extern int GetDeviceCaps(IntPtr hdc, int capindex); 20 | 21 | [DllImport("user32.dll")] 22 | [return: MarshalAs(UnmanagedType.Bool)] 23 | internal static extern bool SetForegroundWindow(IntPtr hWnd); 24 | 25 | [DllImport("user32.dll")] 26 | [return: MarshalAs(UnmanagedType.Bool)] 27 | internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); 28 | 29 | [DllImport("user32.dll")] 30 | internal static extern int IsIconic(IntPtr hWnd); 31 | 32 | [DllImport("user32.dll", CharSet = CharSet.Unicode, 33 | EntryPoint = "MapVirtualKeyExW", ExactSpelling = true)] 34 | internal static extern uint MapVirtualKeyEx( 35 | uint uCode, 36 | uint uMapType, 37 | IntPtr dwhkl); 38 | 39 | [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 40 | internal static extern IntPtr GetKeyboardLayout(int idThread); 41 | 42 | [DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)] 43 | internal static extern int GetKeyboardLayoutList(int nBuff, [Out, MarshalAs(UnmanagedType.LPArray)] int[] lpList); 44 | 45 | [DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "LoadKeyboardLayoutW", ExactSpelling = true)] 46 | internal static extern IntPtr LoadKeyboardLayout(string pwszKLID, uint Flags); 47 | 48 | [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 49 | [return: MarshalAs(UnmanagedType.Bool)] 50 | internal static extern bool UnloadKeyboardLayout(IntPtr hkl); 51 | 52 | [DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, ThrowOnUnmappableChar = true)] 53 | internal static extern int ToUnicodeEx( 54 | uint wVirtKey, 55 | uint wScanCode, 56 | byte[] lpKeyState, 57 | StringBuilder pwszBuff, 58 | int cchBuff, 59 | uint wFlags, 60 | IntPtr hkl); 61 | 62 | [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 63 | internal static extern int GetKeyboardLayoutName([Out] StringBuilder pwszKLID); 64 | 65 | [DllImport("shlwapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] 66 | internal static extern uint SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, IntPtr ppvReserved); 67 | 68 | [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 69 | internal static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo); 70 | 71 | // Can be used to get localised names for modifier keys :: L10N 72 | // [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 73 | // public static extern int GetKeyNameText(uint lParam, [Out] StringBuilder lpString, int nSize); 74 | 75 | public const int KEYEVENTF_EXTENDEDKEY = 0x1; 76 | public const int KEYEVENTF_KEYUP = 0x2; 77 | public const int KL_NAMELENGTH = 9; 78 | public const int KLF_ACTIVATE = 0x00000001; 79 | // public const uint KLF_NOTELLSHELL = 0x00000080; 80 | public const uint KLF_SUBSTITUTE_OK = 0x00000002; 81 | 82 | 83 | // Marshal the delegate otherwise it get's GCd. 84 | [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 85 | internal static extern IntPtr SetWindowsHookEx(int idHook, [MarshalAs(UnmanagedType.FunctionPtr)] LowLevelKeyboardProc lpfn, IntPtr hMod, int dwThreadId); 86 | 87 | [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 88 | [return: MarshalAs(UnmanagedType.SysInt)] 89 | internal static extern IntPtr UnhookWindowsHookEx(IntPtr hhk); 90 | 91 | [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 92 | internal static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, 93 | IntPtr wParam, IntPtr lParam); 94 | 95 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 96 | internal static extern IntPtr GetModuleHandle(string lpModuleName); 97 | 98 | //[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 99 | //internal static extern uint MapVirtualKeyEx(uint uCode, uint uMapType, IntPtr dwhkl); 100 | 101 | [DllImport("advapi32.dll", CharSet = CharSet.Unicode)] 102 | public static extern int RegOpenKeyEx 103 | (UIntPtr hKey, 104 | string lpSubKey, 105 | uint ulOptions, 106 | int samDesired, 107 | out UIntPtr phkResult); 108 | 109 | [DllImport("advapi32.dll")] 110 | public static extern uint RegQueryInfoKey 111 | (UIntPtr hKey, 112 | IntPtr lpClass, 113 | IntPtr lpcbClass, 114 | IntPtr lpReserved, 115 | IntPtr lpcSubKeys, 116 | IntPtr lpcbMaxSubKeyLen, 117 | IntPtr lpcbMaxClassLen, 118 | IntPtr lpcValues, 119 | IntPtr lpcbMaxValueNameLen, 120 | IntPtr lpcbMaxValueLen, 121 | IntPtr lpcbSecurityDescriptor, 122 | out long lpftLastWriteTime); 123 | 124 | [DllImport("Advapi32.dll")] 125 | public static extern uint RegCloseKey(UIntPtr hKey); 126 | 127 | [DllImport("user32.dll", SetLastError = true)] 128 | [return: MarshalAs(UnmanagedType.Bool)] 129 | public static extern bool DestroyIcon(IntPtr hIcon); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /keymapper/Controls/KeyPictureBox.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | False 122 | 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![header](kmheader800w.png) 2 | 3 | # Key Mapper 4 | 5 | ## Make your keyboard work the way you want it to 6 | 7 | ### Features 8 | 9 | Key Mapper is a Windows Forms app that uses a virtual keyboard to create and show mappings. It will change the keyboard to reflect whatever keyboard you have active. You can browse the keyboards installed on your PC if you wish or view a 'slideshow' of all installed keyboards - it's in the 'Advanced' menu [^1] 10 | 11 | * * * 12 | 13 | [Download the current release .exe file (Windows 10/11 or .Net 4.8) from here](https://github.com/stuartd/keymapper/releases/download/1.4/KeyMapper.exe) 14 | 15 | **You will need to have Administrative access to your computer.** 16 | 17 | Support: [mailto:keymappersupport@gmail.com](mailto:keymappersupport@gmail.com) 18 | 19 | * * * 20 | 21 | ### UI 22 | 23 |

24 | 25 | Keymapper UI Screenshot 26 | 27 |
28 | Click the image to view full size 29 |

30 | 31 | 32 | ### History 33 | 34 | I created KeyMapper in 2007 when my employer decided to replace the in-house finance systems we had built with off-the shelf systems, but retained us for support only, so I had time on my hands. 35 | 36 | My intention was to use the (then) new language C# and also to solve a problem I had - **I never wanted caps lock on or num lock off!** - with a friendly user interface, using scancode mappings held in the registry rather than remapping them at runtime. 37 | 38 | I ended out taking a deep dive into keyboard layouts, localization and internationalization, and distributed source control. 39 | 40 | The documentation for scancode mappings stated: 41 | 42 | > The mappings stored in the registry work at system level and apply to all users. These mappings cannot be set to work differently depending on the current user. 43 | 44 | But I discovered that both Windows XP and Vista supported per-user key mappings written to `HKEY_CURRENT_USER\Keyboard Layout` and when KeyMapper was written, those were the latest Windows versions. 45 | 46 | Because this was never documented, it was an opportunity to build something that would be of real utility - let people set keyboard mappings for their own user account, which no other programs at the time seemed to support. 47 | 48 | But then Windows 7 came along and dropped the unofficial or accidental support, and it doesn't look like it's ever coming back: I took out all the code that used user mappings, but other than that the code is essentially as it was in 2008: I am happy with the code, but needless to say this is not how I would write it now.. 49 | 50 | If you're interested, my original blog post from 2008 on per-user scancode mappings is reproduced below (the blog itself is available [on the Internet Archive](https://web.archive.org/web/20150306040530/http://justkeepswimming.net/keymapper/blog/default.aspx)) 51 | 52 | * * * 53 | 54 | ### Documentation 55 | 56 | I have hosted the (extremely detailed!) Scan Code Specification (Revision 1.3a — March 16, 2000) [here](scancode.doc) - it's commonly available as a PDF but this is the original `.doc` 57 | 58 | * * * 59 | 60 | ### Credits 61 | 62 | The image used for the keyboard keys is based on the blank icon in the keyboard icon set released by Alan Who?, used with permission 63 | http://alanwho.com/ 64 | 65 | The late Michael Kaplan's [blog](https://web.archive.org/web/20250000000000*/%20http://blogs.msdn.com/b/michkap) was invaluable in understanding the minutiae of how Windows actually handles internationalization, localization, collations and keyboard usage 66 | 67 | * * * 68 | 69 | ### Per-User Scancode Mappings [From 2008, Windows 7 was released in 2009] 70 | 71 | One thing that distinguishes Key Mapper from other scancode mapping programs is that it lets you map or disable keys on a per-user basis: when Microsoft [originally implemented scancode mappings in Windows 2000](https://web.archive.org/web/20090208232046/https://www.microsoft.com/whdc/archive/w2kscan-map.mspx), they stated in the "disadvantages" section: 72 | 73 | > The mappings stored in the registry work at system level and apply to all users. These mappings cannot be set to work differently depending on the current user. 74 | 75 | This is because the mappings are stored in `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout` which needs Administrative access to change and is only loaded at boot time. 76 | 77 | In Windows XP, though, per-user mappings were quietly introduced, with no fanfare or documentation: scancode mappings set in the `HKEY_CURRENT_USER\Keyboard Layout` key were recognised, and applied to an individual user profile. This meant that mappings can be added or removed by logging off and logging back on again - still inconvenient, but less so than a full reboot: it also meant that mappings can be set up users without Administrative rights (and mappings set in `HKEY_LOCAL_MACHINE` were overridden by those in `HKEY_CURRENT_USER`). 78 | 79 | It's possible that Microsoft kept this quiet because user mappings are incompatible with Fast User Switching: when you switch to an account that's already logged on, the mappings are not reloaded. It's also possible that because they kept it quiet, the Fast User Switching development team didn't realise that user mappings should be reloaded when switching users. 80 | 81 | While this was a possible disadvantage to using user mappings, most people probably don't use more than one account on their computer anyway, and in computers attached to a domain (i.e. corporate PCs) which may often be used by different people Fast User Switching isn't available anyway. 82 | 83 | There are some other advantages to user mappings: 84 | 85 | * They don't require Administrative rights to be set or removed. 86 | * Different users can have different mappings - one can have Caps Lock disabled but Num Lock enabled, another can have them the other way round 87 | * Keys can be mapped on shared computers without affecting all users 88 | 89 | *** 90 | 91 | ### Origin 92 | 93 | Originally developed [on `code.google.com`](https://code.google.com/archive/redirect/a/code.google.com/p/keymapper?movedTo=https:%2F%2Fgithub.com%2Fstuartd%2Fkeymapper 94 | ) [^2] where it was popular with the shareware sites of the time (2008!) 95 | 96 | ![numbers](km_numbers_2008.png) 97 | 98 | * * * 99 | 100 | ### Trivia 101 | There was (is?) another place scancode mappings could be set - in the `HKEY_USERS\.DEFAULT\Keyboard Layout` key. These apply at the login prompt, but are then removed when logged in. 102 | 103 | [^1]: I spent a _lot_ of time seeing what was possible once the basic functionality was finished 104 | [^2]: The link of course redirects back to this repo 105 | -------------------------------------------------------------------------------- /keymapper/Forms/MappingList.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | True 122 | 123 | 124 | True 125 | 126 | 127 | True 128 | 129 | 130 | True 131 | 132 | -------------------------------------------------------------------------------- /keymapper/Forms/ColourEditor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Windows.Forms; 4 | using System.Drawing.Imaging; 5 | using System.Globalization; 6 | using KeyMapper.Classes; 7 | 8 | namespace KeyMapper.Forms 9 | { 10 | public sealed partial class ColourEditor : KMBaseForm 11 | { 12 | private const decimal lowBound = -1M; 13 | private const decimal highBound = 1M; 14 | private const decimal step = 0.1M; 15 | 16 | private bool drawing; 17 | private bool initialised; 18 | private readonly string caption; 19 | 20 | private ColorMatrix currentMatrix; 21 | 22 | public ButtonEffect Effect { get; } 23 | 24 | private Color fontColour = Color.Black; 25 | 26 | public ColourEditor(ButtonEffect effect, string caption) 27 | { 28 | InitializeComponent(); 29 | 30 | Effect = effect; 31 | this.caption = caption; 32 | 33 | Text = "Editing the " + caption + " button"; 34 | 35 | LoadSetting(); 36 | 37 | UserColourSettingManager.ColoursChanged += delegate(object sender, EventArgs e) { OnColoursChanged(); }; 38 | } 39 | 40 | private void OnColoursChanged() 41 | { 42 | LoadSetting(); 43 | } 44 | 45 | private void LoadSetting() 46 | { 47 | UpdateMatrix(ButtonImages.GetMatrix(Effect)); 48 | fontColour = ButtonImages.GetFontColour(Effect); 49 | DrawKey(); 50 | } 51 | 52 | private void UpdateMatrix(ColorMatrix cm) 53 | { 54 | currentMatrix = cm; 55 | SetUpDownValuesFromMatrix(); 56 | } 57 | 58 | private void SetUpDownValuesFromMatrix() 59 | { 60 | drawing = false; 61 | 62 | foreach (Control con in Controls) 63 | { 64 | if (con is NumericUpDown upDown) 65 | { 66 | if (!initialised) 67 | { 68 | upDown.DecimalPlaces = 1; 69 | upDown.Minimum = lowBound; 70 | upDown.Maximum = highBound; 71 | upDown.Increment = step; 72 | } 73 | 74 | upDown.ValueChanged -= UpDownValueChanged; 75 | upDown.Value = GetValue(upDown.Name); 76 | upDown.ValueChanged += UpDownValueChanged; 77 | } 78 | 79 | } 80 | 81 | drawing = true; 82 | initialised = true; 83 | } 84 | 85 | private decimal GetValue(string name) 86 | { 87 | // Access the appropriate property of the matrix: 88 | var value = currentMatrix.GetType().GetProperty(name).GetValue(currentMatrix, null); 89 | return decimal.TryParse(value.ToString(), out decimal result) ? result : decimal.Zero; 90 | } 91 | 92 | 93 | private void UpDownValueChanged(object sender, EventArgs e) 94 | { 95 | if (drawing) 96 | { 97 | UpdateMatrixFromControls(); 98 | SaveSetting(); 99 | } 100 | } 101 | 102 | private void UpdateMatrixFromControls() 103 | { 104 | currentMatrix = new ColorMatrix( 105 | new[] 106 | { 107 | new[] {(float)Matrix00.Value, (float)Matrix01.Value, (float)Matrix02.Value, (float)Matrix03.Value, (float)Matrix04.Value}, 108 | new[] {(float)Matrix10.Value, (float)Matrix11.Value, (float)Matrix12.Value, (float)Matrix13.Value, (float)Matrix14.Value}, 109 | new[] {(float)Matrix20.Value, (float)Matrix21.Value, (float)Matrix22.Value, (float)Matrix23.Value, (float)Matrix24.Value}, 110 | new[] {(float)Matrix30.Value, (float)Matrix31.Value, (float)Matrix32.Value, (float)Matrix33.Value, (float)Matrix34.Value}, 111 | new[] {(float)Matrix40.Value, (float)Matrix41.Value, (float)Matrix42.Value, (float)Matrix43.Value, (float)Matrix44.Value} 112 | }); 113 | 114 | } 115 | 116 | private void DrawKey() 117 | { 118 | 119 | var bmp = ButtonImages.GetButtonImage(BlankButton.MediumWideBlank, 0.75F, caption, currentMatrix, fontColour); 120 | 121 | KeyBox.Image?.Dispose(); 122 | 123 | KeyBox.Image = bmp; 124 | 125 | } 126 | 127 | private void ResetButtonClick(object sender, EventArgs e) 128 | { 129 | // Passing true to ignore user colours and fonts. 130 | UpdateMatrix(ButtonImages.GetMatrix(Effect, true)); 131 | fontColour = ButtonImages.GetFontColour(Effect, true); 132 | 133 | SaveSetting(); 134 | } 135 | 136 | 137 | private void BlankButtonClick(object sender, EventArgs e) 138 | { 139 | UpdateMatrix(new ColorMatrix()); 140 | fontColour = ButtonImages.GetFontColour(ButtonEffect.None, true); 141 | 142 | SaveSetting(); 143 | } 144 | 145 | private void ColourEditorFormClosed(object sender, FormClosedEventArgs e) 146 | { 147 | SaveUserSettings(); 148 | } 149 | 150 | private void SaveUserSettings() 151 | { 152 | var userSettings = new Properties.Settings(); 153 | userSettings.ColourEditorLocation = Location; 154 | userSettings.Save(); 155 | } 156 | 157 | private void SaveSetting() 158 | { 159 | UserColourSettingManager.SaveSetting(Effect, currentMatrix, fontColour.ToArgb()); 160 | } 161 | 162 | private void TextButtonClick(object sender, EventArgs e) 163 | { 164 | var colourPicker = new ColorDialog(); 165 | 166 | // Sets the initial color select to the current text color. 167 | colourPicker.Color = fontColour; 168 | 169 | if (colourPicker.ShowDialog() == DialogResult.OK) 170 | { 171 | fontColour = colourPicker.Color; 172 | SaveSetting(); 173 | 174 | } 175 | } 176 | 177 | 178 | private void RandomizeButtonClick(object sender, EventArgs e) 179 | { 180 | var cm = new ColorMatrix(); 181 | 182 | int numberOfChanges = 5; 183 | 184 | var r = new Random(); 185 | 186 | for (int i = 0; i < numberOfChanges; i++) 187 | { 188 | int x = r.Next(0, 5); 189 | int y = r.Next(0, 3); 190 | 191 | string name = "Matrix" + x.ToString(CultureInfo.InvariantCulture) + y.ToString(CultureInfo.InvariantCulture); 192 | float val = r.Next(-10, 11) / 10F; 193 | 194 | // Update control... 195 | // (this.Controls[name] as NumericUpDown).Value = val; 196 | 197 | // Or update field. 198 | cm.GetType().GetProperty(name).SetValue(cm, val, null); 199 | } 200 | 201 | fontColour = Color.FromArgb(r.Next(0, 256), r.Next(0, 256), r.Next(0, 256)); 202 | UpdateMatrix(cm); 203 | 204 | SaveSetting(); 205 | 206 | } 207 | } 208 | 209 | } 210 | -------------------------------------------------------------------------------- /keymapper/Forms/MappingList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Windows.Forms; 6 | using KeyMapper.Classes; 7 | 8 | namespace KeyMapper.Forms 9 | { 10 | public partial class MappingListForm : KMBaseForm 11 | { 12 | private readonly List clearedKeys = new List(); 13 | private readonly List keyList = new List(); 14 | private const int minimumWidth = 300; 15 | 16 | public MappingListForm() 17 | { 18 | //TODO: Look into changing the column header colours as they are much too dark on XP without themes or w2k 19 | InitializeComponent(); 20 | Populate(); 21 | MappingsManager.MappingsChanged += HandleMappingsChanged; 22 | } 23 | 24 | public void LoadUserSettings() 25 | { 26 | var userSettings = new Properties.Settings(); 27 | 28 | int savedWidth = userSettings.MappingListFormWidth; 29 | 30 | if (savedWidth > minimumWidth) 31 | { 32 | Width = savedWidth; 33 | } 34 | } 35 | 36 | private void MappingListFormClosing(object sender, FormClosingEventArgs e) 37 | { 38 | SaveUserSettings(); 39 | } 40 | 41 | private void SaveUserSettings() 42 | { 43 | var userSettings = new Properties.Settings 44 | { 45 | MappingListFormLocation = Location, 46 | MappingListFormWidth = Width 47 | }; 48 | userSettings.Save(); 49 | } 50 | 51 | private void HandleMappingsChanged(object sender, EventArgs e) 52 | { 53 | Populate(); 54 | } 55 | 56 | private void Populate() 57 | { 58 | // Form grabs focus from main form when repopulating. Check if we have focus now.. 59 | bool hasFocus = grdMappings.ContainsFocus; 60 | 61 | // Using grdMappings.Rows.Clear() sometimes results in 62 | // "Can't add rows where there are no columns" error, 63 | // resulting in an InvalidOperationException. 64 | 65 | for (int i = grdMappings.Rows.Count - 1; i >= 0; i--) 66 | { 67 | grdMappings.Rows.Remove(grdMappings.Rows[i]); 68 | } 69 | 70 | clearedKeys.Clear(); 71 | keyList.Clear(); 72 | 73 | try 74 | { 75 | AddRowsToGrid(); 76 | } 77 | catch (InvalidOperationException) 78 | { 79 | Console.WriteLine("Unexpected return of the AddRowsToGrid bug!"); 80 | return; 81 | } 82 | 83 | // Resize according to number of mappings 84 | int height = grdMappings.ColumnHeadersHeight 85 | + grdMappings.Rows.Cast().Sum(row => row.Height + row.DividerHeight); 86 | 87 | MinimumSize = new Size(0, 0); 88 | MaximumSize = new Size(0, 0); 89 | SetClientSizeCore(ClientSize.Width, height); 90 | MinimumSize = new Size(minimumWidth, Size.Height); 91 | 92 | // If we didn't have form to start with, set focus back to main form. 93 | if (hasFocus == false) 94 | { 95 | FormsManager.ActivateMainForm(); 96 | } 97 | } 98 | 99 | private void AddRowsToGrid() 100 | { 101 | AddRowsToGrid(MappingFilter.Set); 102 | AddRowsToGrid(MappingFilter.Cleared); 103 | 104 | if (grdMappings.RowCount == 0) 105 | { 106 | // No mappings. 107 | int index = grdMappings.Rows.Add("You haven't created any mappings yet"); 108 | clearedKeys.Add(index); // Stops Delete key being shown. 109 | } 110 | 111 | } 112 | 113 | private void AddRowsToGrid(MappingFilter filter) 114 | { 115 | var maps = MappingsManager.GetMappings(filter); 116 | 117 | foreach (var map in maps) 118 | { 119 | if (filter == MappingFilter.Cleared) 120 | { 121 | if (keyList.Contains(map.From)) 122 | { 123 | // Don't add an entry for a cleared key which has been remapped. 124 | break; 125 | } 126 | 127 | } 128 | else 129 | { 130 | keyList.Add(map.From); 131 | } 132 | 133 | int index = grdMappings.Rows.Add(map.ToString()); 134 | grdMappings.Rows[index].Tag = map; 135 | 136 | string cellValue = string.Empty; 137 | 138 | switch (filter) 139 | { 140 | case MappingFilter.Set: 141 | cellValue = "Set"; 142 | break; 143 | 144 | case MappingFilter.Cleared: 145 | cellValue = "Cleared"; 146 | 147 | // Need to store the row to a little array as 148 | // don't want to have to access each cell to decide whether 149 | // to show the delete button for it or not. 150 | clearedKeys.Add(index); 151 | 152 | break; 153 | } 154 | 155 | grdMappings.Rows[index].Cells[1].Value = cellValue; 156 | 157 | if (MappingsManager.IsMappingPending(map, filter)) 158 | { 159 | grdMappings.Rows[index].Cells[2].Value = "Pending"; 160 | } 161 | else 162 | { 163 | grdMappings.Rows[index].Cells[2].Value = "Mapped"; 164 | } 165 | } 166 | 167 | } 168 | 169 | 170 | private void grdMappingsCellContentClick(object sender, DataGridViewCellEventArgs e) 171 | { 172 | if (e.ColumnIndex != 3) 173 | { 174 | return; 175 | } 176 | 177 | int row = e.RowIndex; 178 | 179 | if (clearedKeys.Contains(row)) 180 | { 181 | return; // Shouldn't happen anyway 182 | } 183 | 184 | if (row >= 0) 185 | { 186 | var currentRow = grdMappings.Rows[row]; 187 | 188 | if (currentRow.Tag != null) 189 | { 190 | MappingsManager.DeleteMapping((KeyMapping)currentRow.Tag); 191 | } 192 | } 193 | } 194 | 195 | private void grdMappingsCellPainting(object sender, DataGridViewCellPaintingEventArgs e) 196 | { 197 | 198 | // Don't display Delete button for cleared rows (as it has no effect) 199 | // Unfortunately, delete looks CRAP on Windows 2000 200 | // as it takes on the background colour ..??. TODO. 201 | 202 | if (e.ColumnIndex == 3 && e.RowIndex >= 0) 203 | { 204 | if (clearedKeys.Contains(e.RowIndex)) 205 | { 206 | e.PaintBackground(e.CellBounds, true); 207 | e.Handled = true; 208 | } 209 | } 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /keymapper/Forms/MappingList.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace KeyMapper.Forms 2 | { 3 | partial class MappingListForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | protected override void Dispose(bool disposing) 14 | { 15 | if (disposing && (this.components != null)) 16 | { 17 | this.components.Dispose(); 18 | } 19 | base.Dispose(disposing); 20 | } 21 | 22 | /// 23 | /// Required method for Designer support - do not modify 24 | /// the contents of this method with the code editor. 25 | /// 26 | private void InitializeComponent() 27 | { 28 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); 29 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); 30 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle(); 31 | this.grdMappings = new System.Windows.Forms.DataGridView(); 32 | this.KeyMapping = new System.Windows.Forms.DataGridViewTextBoxColumn(); 33 | this.Type = new System.Windows.Forms.DataGridViewTextBoxColumn(); 34 | this.Status = new System.Windows.Forms.DataGridViewTextBoxColumn(); 35 | this.Delete = new System.Windows.Forms.DataGridViewButtonColumn(); 36 | ((System.ComponentModel.ISupportInitialize)(this.grdMappings)).BeginInit(); 37 | this.SuspendLayout(); 38 | // 39 | // grdMappings 40 | // 41 | this.grdMappings.AllowUserToAddRows = false; 42 | this.grdMappings.AllowUserToDeleteRows = false; 43 | this.grdMappings.AllowUserToResizeColumns = false; 44 | this.grdMappings.AllowUserToResizeRows = false; 45 | dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.Window; 46 | dataGridViewCellStyle1.ForeColor = System.Drawing.Color.Black; 47 | dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.ControlLight; 48 | dataGridViewCellStyle1.SelectionForeColor = System.Drawing.Color.Black; 49 | this.grdMappings.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1; 50 | this.grdMappings.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill; 51 | this.grdMappings.AutoSizeRowsMode = System.Windows.Forms.DataGridViewAutoSizeRowsMode.AllCells; 52 | this.grdMappings.BorderStyle = System.Windows.Forms.BorderStyle.None; 53 | this.grdMappings.CausesValidation = false; 54 | dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; 55 | dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.ControlDark; 56 | dataGridViewCellStyle2.Font = new System.Drawing.Font("Verdana", 8.25F); 57 | dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.WindowText; 58 | dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.ControlLightLight; 59 | dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText; 60 | dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True; 61 | this.grdMappings.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2; 62 | this.grdMappings.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; 63 | this.grdMappings.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { 64 | this.KeyMapping, 65 | this.Type, 66 | this.Status, 67 | this.Delete}); 68 | dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; 69 | dataGridViewCellStyle3.BackColor = System.Drawing.SystemColors.Window; 70 | dataGridViewCellStyle3.Font = new System.Drawing.Font("Verdana", 8.25F); 71 | dataGridViewCellStyle3.ForeColor = System.Drawing.Color.Black; 72 | dataGridViewCellStyle3.SelectionBackColor = System.Drawing.SystemColors.ControlLight; 73 | dataGridViewCellStyle3.SelectionForeColor = System.Drawing.Color.Black; 74 | dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.False; 75 | this.grdMappings.DefaultCellStyle = dataGridViewCellStyle3; 76 | this.grdMappings.Dock = System.Windows.Forms.DockStyle.Fill; 77 | this.grdMappings.EditMode = System.Windows.Forms.DataGridViewEditMode.EditProgrammatically; 78 | this.grdMappings.Location = new System.Drawing.Point(0, 0); 79 | this.grdMappings.MultiSelect = false; 80 | this.grdMappings.Name = "grdMappings"; 81 | this.grdMappings.ReadOnly = true; 82 | this.grdMappings.RowHeadersVisible = false; 83 | this.grdMappings.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; 84 | this.grdMappings.Size = new System.Drawing.Size(499, 156); 85 | this.grdMappings.TabIndex = 4; 86 | this.grdMappings.CellPainting += new System.Windows.Forms.DataGridViewCellPaintingEventHandler(this.grdMappingsCellPainting); 87 | this.grdMappings.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.grdMappingsCellContentClick); 88 | // 89 | // KeyMapping 90 | // 91 | this.KeyMapping.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; 92 | this.KeyMapping.FillWeight = 61F; 93 | this.KeyMapping.HeaderText = "Key Mappings"; 94 | this.KeyMapping.Name = "KeyMapping"; 95 | this.KeyMapping.ReadOnly = true; 96 | this.KeyMapping.Resizable = System.Windows.Forms.DataGridViewTriState.False; 97 | // 98 | // Type 99 | // 100 | this.Type.FillWeight = 13F; 101 | this.Type.HeaderText = "Type"; 102 | this.Type.Name = "Type"; 103 | this.Type.ReadOnly = true; 104 | this.Type.Resizable = System.Windows.Forms.DataGridViewTriState.False; 105 | // 106 | // Status 107 | // 108 | this.Status.FillWeight = 13F; 109 | this.Status.HeaderText = "Status"; 110 | this.Status.Name = "Status"; 111 | this.Status.ReadOnly = true; 112 | this.Status.Resizable = System.Windows.Forms.DataGridViewTriState.False; 113 | // 114 | // Delete 115 | // 116 | this.Delete.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; 117 | this.Delete.FillWeight = 13F; 118 | this.Delete.FlatStyle = System.Windows.Forms.FlatStyle.System; 119 | this.Delete.HeaderText = "Delete"; 120 | this.Delete.MinimumWidth = 60; 121 | this.Delete.Name = "Delete"; 122 | this.Delete.ReadOnly = true; 123 | this.Delete.Resizable = System.Windows.Forms.DataGridViewTriState.False; 124 | this.Delete.Text = "Delete"; 125 | this.Delete.ToolTipText = "Delete this mapping"; 126 | this.Delete.UseColumnTextForButtonValue = true; 127 | this.Delete.Width = 60; 128 | // 129 | // MappingListForm 130 | // 131 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 13F); 132 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 133 | this.ClientSize = new System.Drawing.Size(499, 156); 134 | this.Controls.Add(this.grdMappings); 135 | this.MaximizeBox = false; 136 | this.MinimizeBox = false; 137 | this.Name = "MappingListForm"; 138 | this.ShowIcon = false; 139 | this.ShowInTaskbar = false; 140 | this.Text = "Key Mapping List"; 141 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MappingListFormClosing); 142 | ((System.ComponentModel.ISupportInitialize)(this.grdMappings)).EndInit(); 143 | this.ResumeLayout(false); 144 | 145 | } 146 | 147 | private System.Windows.Forms.DataGridView grdMappings; 148 | private System.Windows.Forms.DataGridViewTextBoxColumn KeyMapping; 149 | private System.Windows.Forms.DataGridViewTextBoxColumn Type; 150 | private System.Windows.Forms.DataGridViewTextBoxColumn Status; 151 | private System.Windows.Forms.DataGridViewButtonColumn Delete; 152 | } 153 | } 154 | 155 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.vspscc 94 | *.vssscc 95 | .builds 96 | *.pidb 97 | *.svclog 98 | *.scc 99 | 100 | # Chutzpah Test files 101 | _Chutzpah* 102 | 103 | # Visual C++ cache files 104 | ipch/ 105 | *.aps 106 | *.ncb 107 | *.opendb 108 | *.opensdf 109 | *.sdf 110 | *.cachefile 111 | *.VC.db 112 | *.VC.VC.opendb 113 | 114 | # Visual Studio profiler 115 | *.psess 116 | *.vsp 117 | *.vspx 118 | *.sap 119 | 120 | # Visual Studio Trace Files 121 | *.e2e 122 | 123 | # TFS 2012 Local Workspace 124 | $tf/ 125 | 126 | # Guidance Automation Toolkit 127 | *.gpState 128 | 129 | # ReSharper is a .NET coding add-in 130 | _ReSharper*/ 131 | *.[Rr]e[Ss]harper 132 | *.DotSettings.user 133 | 134 | # TeamCity is a build add-in 135 | _TeamCity* 136 | 137 | # DotCover is a Code Coverage Tool 138 | *.dotCover 139 | 140 | # AxoCover is a Code Coverage Tool 141 | .axoCover/* 142 | !.axoCover/settings.json 143 | 144 | # Coverlet is a free, cross platform Code Coverage Tool 145 | coverage*.json 146 | coverage*.xml 147 | coverage*.info 148 | 149 | # Visual Studio code coverage results 150 | *.coverage 151 | *.coveragexml 152 | 153 | # NCrunch 154 | _NCrunch_* 155 | .*crunch*.local.xml 156 | nCrunchTemp_* 157 | 158 | # MightyMoose 159 | *.mm.* 160 | AutoTest.Net/ 161 | 162 | # Web workbench (sass) 163 | .sass-cache/ 164 | 165 | # Installshield output folder 166 | [Ee]xpress/ 167 | 168 | # DocProject is a documentation generator add-in 169 | DocProject/buildhelp/ 170 | DocProject/Help/*.HxT 171 | DocProject/Help/*.HxC 172 | DocProject/Help/*.hhc 173 | DocProject/Help/*.hhk 174 | DocProject/Help/*.hhp 175 | DocProject/Help/Html2 176 | DocProject/Help/html 177 | 178 | # Click-Once directory 179 | publish/ 180 | 181 | # Publish Web Output 182 | *.[Pp]ublish.xml 183 | *.azurePubxml 184 | # Note: Comment the next line if you want to checkin your web deploy settings, 185 | # but database connection strings (with potential passwords) will be unencrypted 186 | *.pubxml 187 | *.publishproj 188 | 189 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 190 | # checkin your Azure Web App publish settings, but sensitive information contained 191 | # in these scripts will be unencrypted 192 | PublishScripts/ 193 | 194 | # NuGet Packages 195 | *.nupkg 196 | # NuGet Symbol Packages 197 | *.snupkg 198 | # The packages folder can be ignored because of Package Restore 199 | **/[Pp]ackages/* 200 | # except build/, which is used as an MSBuild target. 201 | !**/[Pp]ackages/build/ 202 | # Uncomment if necessary however generally it will be regenerated when needed 203 | #!**/[Pp]ackages/repositories.config 204 | # NuGet v3's project.json files produces more ignorable files 205 | *.nuget.props 206 | *.nuget.targets 207 | 208 | # Microsoft Azure Build Output 209 | csx/ 210 | *.build.csdef 211 | 212 | # Microsoft Azure Emulator 213 | ecf/ 214 | rcf/ 215 | 216 | # Windows Store app package directories and files 217 | AppPackages/ 218 | BundleArtifacts/ 219 | Package.StoreAssociation.xml 220 | _pkginfo.txt 221 | *.appx 222 | *.appxbundle 223 | *.appxupload 224 | 225 | # Visual Studio cache files 226 | # files ending in .cache can be ignored 227 | *.[Cc]ache 228 | # but keep track of directories ending in .cache 229 | !?*.[Cc]ache/ 230 | 231 | # Others 232 | ClientBin/ 233 | ~$* 234 | *~ 235 | *.dbmdl 236 | *.dbproj.schemaview 237 | *.jfm 238 | *.pfx 239 | *.publishsettings 240 | orleans.codegen.cs 241 | 242 | # Including strong name files can present a security risk 243 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 244 | #*.snk 245 | 246 | # Since there are multiple workflows, uncomment next line to ignore bower_components 247 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 248 | #bower_components/ 249 | 250 | # RIA/Silverlight projects 251 | Generated_Code/ 252 | 253 | # Backup & report files from converting an old project file 254 | # to a newer Visual Studio version. Backup files are not needed, 255 | # because we have git ;-) 256 | _UpgradeReport_Files/ 257 | Backup*/ 258 | UpgradeLog*.XML 259 | UpgradeLog*.htm 260 | ServiceFabricBackup/ 261 | *.rptproj.bak 262 | 263 | # SQL Server files 264 | *.mdf 265 | *.ldf 266 | *.ndf 267 | 268 | # Business Intelligence projects 269 | *.rdl.data 270 | *.bim.layout 271 | *.bim_*.settings 272 | *.rptproj.rsuser 273 | *- [Bb]ackup.rdl 274 | *- [Bb]ackup ([0-9]).rdl 275 | *- [Bb]ackup ([0-9][0-9]).rdl 276 | 277 | # Microsoft Fakes 278 | FakesAssemblies/ 279 | 280 | # GhostDoc plugin setting file 281 | *.GhostDoc.xml 282 | 283 | # Node.js Tools for Visual Studio 284 | .ntvs_analysis.dat 285 | node_modules/ 286 | 287 | # Visual Studio 6 build log 288 | *.plg 289 | 290 | # Visual Studio 6 workspace options file 291 | *.opt 292 | 293 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 294 | *.vbw 295 | 296 | # Visual Studio LightSwitch build output 297 | **/*.HTMLClient/GeneratedArtifacts 298 | **/*.DesktopClient/GeneratedArtifacts 299 | **/*.DesktopClient/ModelManifest.xml 300 | **/*.Server/GeneratedArtifacts 301 | **/*.Server/ModelManifest.xml 302 | _Pvt_Extensions 303 | 304 | # Paket dependency manager 305 | .paket/paket.exe 306 | paket-files/ 307 | 308 | # FAKE - F# Make 309 | .fake/ 310 | 311 | # CodeRush personal settings 312 | .cr/personal 313 | 314 | # Python Tools for Visual Studio (PTVS) 315 | __pycache__/ 316 | *.pyc 317 | 318 | # Cake - Uncomment if you are using it 319 | # tools/** 320 | # !tools/packages.config 321 | 322 | # Tabs Studio 323 | *.tss 324 | 325 | # Telerik's JustMock configuration file 326 | *.jmconfig 327 | 328 | # BizTalk build output 329 | *.btp.cs 330 | *.btm.cs 331 | *.odx.cs 332 | *.xsd.cs 333 | 334 | # OpenCover UI analysis results 335 | OpenCover/ 336 | 337 | # Azure Stream Analytics local run output 338 | ASALocalRun/ 339 | 340 | # MSBuild Binary and Structured Log 341 | *.binlog 342 | 343 | # NVidia Nsight GPU debugger configuration file 344 | *.nvuser 345 | 346 | # MFractors (Xamarin productivity tool) working folder 347 | .mfractor/ 348 | 349 | # Local History for Visual Studio 350 | .localhistory/ 351 | 352 | # BeatPulse healthcheck temp database 353 | healthchecksdb 354 | 355 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 356 | MigrationBackup/ 357 | 358 | # Ionide (cross platform F# VS Code tools) working folder 359 | .ionide/ 360 | 361 | # Fody - auto-generated XML schema 362 | FodyWeavers.xsd 363 | 364 | .idea 365 | .DS_Store 366 | 367 | -------------------------------------------------------------------------------- /keymapper/Forms/AddEditMapping.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | 123 | iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 124 | YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAA7NJREFUWEe9l9tP 125 | VFcUxsdUH2vSP6F9UmCAEfvQx760Tf+DGggo02Fk5H5HREoJIQQR5CYhZDJF25fGEGJrrK21tt7qpYhj 126 | qVoFZNCZMzMw7Nlchks+19rpoVpveDidSX7ZM5Oc9X1rnbXO3meTxeDHM/A1LBZEpny+T6urKq5SmBWD 127 | oYxd5vYcQzg8jYHj3wjXvvyPKMoWY5EMXtXv/gqrq6sIhcPwDBwXztx9H8fVRF+/G7FYDCsrKwiFwnB7 128 | BkSOMzd+Jnr7+jG/sKBYXl4mEyH0uz3CkbM3Pia6j/ZByjlFVEosLS0jSCaoMsL+ec76TXR298IoQggw 129 | 0WhUrbHYEoLBEKg6ItvuWJ+JI509MPqZiUTACMEGoohEZlVfaMEgenr7xO5s++tNtB3pUuO0EXQDs7MC 130 | 09MzWFxchKYF0dXTK7J2Z7/aRGtbh3LM6IE2srKJIE3FAjWnpmno6OoRmVl7Xm6ipbUdfn9AMUtlNIMI 131 | mQhQBebn52nV0N7RLTIys15sormlFVOPHivYvVlwPzympObm5hAIaDjc3inSMzKfN9HU3IJJn09hlrge 132 | h008osQkjak/EMChw+1iV3rGsyYam5oxPvFQwaUzmxkyMembolElE1SRlkNt4rNd6f+aaGhswoOxcYXZ 133 | 4nq86ZkIJh5Oqib3+/2ob2gUtG+8S2y20A/c+/u+gkv2f8CjOTY+oTTu3r2Hg198KUn8Q2Krpa6+AXfo 134 | T4bLZTYsfv/BmIp/+cpVzl6m7Xy/fM3AgYP1GP3rjsJs8TCJc9Yc++Kly6BkZVraThb/ZO0W7D9QB+/t 135 | UYWZBlics+a4v164iNq6emnbkaaLv7d2dqjaX4uRW14FN4sZhOjRzllzzF/O/4aa2jpps71AnA9DFVU1 136 | +GN4RGGWuPfPURXv7LnzqK6plam2Hc9nrp/EyiqqcP3G8IbQjfMecMt7W8X68ezPqKyukSmptpeLs4mS 137 | sgr8fu36huBOZ/GbI14V54czP6G8slomp6S+WpwNFJWUwSg8VoxGh5AbwzfV91Onz6C0vFJak1NeL/7P 138 | bdhK6wcEPx7XTUFRCS5cuqK4RiXn9btTp1FcWi6t1uR1i7OHt4i3iXfehLyCIjVeOkMnv0dhcalMslrf 139 | SNzgW4HF4sorwDkaMWZw6CTyC4tlYlKcxNm1MzdPjdmJwSG48gplQmJSfDLXS+ZwuvDtiUHsdeXL7QmJ 140 | 8RVnE3aHE3vsDrlte0L8xf8zPbyt8t4e35fTp6aHx3iz0W5+Akg/ZPzkGa82AAAAAElFTkSuQmCC 141 | 142 | 143 | -------------------------------------------------------------------------------- /keymapper/Forms/Help.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace KeyMapper.Forms 2 | { 3 | partial class HelpForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (this.components != null)) 17 | { 18 | this.components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | /// 24 | /// Required method for Designer support - do not modify 25 | /// the contents of this method with the code editor. 26 | /// 27 | private void InitializeComponent() 28 | { 29 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(HelpForm)); 30 | this.label1 = new System.Windows.Forms.Label(); 31 | this.label2 = new System.Windows.Forms.Label(); 32 | this.label3 = new System.Windows.Forms.Label(); 33 | this.chkShowHelpAtStartup = new System.Windows.Forms.CheckBox(); 34 | this.label4 = new System.Windows.Forms.Label(); 35 | this.label5 = new System.Windows.Forms.Label(); 36 | this.panel1 = new System.Windows.Forms.Panel(); 37 | this.labelFAQ = new System.Windows.Forms.LinkLabel(); 38 | this.panel1.SuspendLayout(); 39 | this.SuspendLayout(); 40 | // 41 | // label1 42 | // 43 | this.label1.AutoSize = true; 44 | this.label1.Font = new System.Drawing.Font("Verdana", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 45 | this.label1.Location = new System.Drawing.Point(15, 9); 46 | this.label1.Name = "label1"; 47 | this.label1.Size = new System.Drawing.Size(282, 16); 48 | this.label1.TabIndex = 0; 49 | this.label1.Text = "Drag a key off the keyboard to disable it."; 50 | // 51 | // label2 52 | // 53 | this.label2.AutoSize = true; 54 | this.label2.Font = new System.Drawing.Font("Verdana", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 55 | this.label2.Location = new System.Drawing.Point(15, 43); 56 | this.label2.Name = "label2"; 57 | this.label2.Size = new System.Drawing.Size(219, 16); 58 | this.label2.TabIndex = 1; 59 | this.label2.Text = "To remap a key, double-click it."; 60 | // 61 | // label3 62 | // 63 | this.label3.AutoSize = true; 64 | this.label3.Font = new System.Drawing.Font("Verdana", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 65 | this.label3.Location = new System.Drawing.Point(15, 77); 66 | this.label3.Name = "label3"; 67 | this.label3.Size = new System.Drawing.Size(471, 16); 68 | this.label3.TabIndex = 2; 69 | this.label3.Text = "To delete a key mapping or re-enable a key, drag it off the keyboard."; 70 | // 71 | // chkShowHelpAtStartup 72 | // 73 | this.chkShowHelpAtStartup.AutoSize = true; 74 | this.chkShowHelpAtStartup.Checked = true; 75 | this.chkShowHelpAtStartup.CheckState = System.Windows.Forms.CheckState.Checked; 76 | this.chkShowHelpAtStartup.Font = new System.Drawing.Font("Verdana", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 77 | this.chkShowHelpAtStartup.Location = new System.Drawing.Point(18, 3); 78 | this.chkShowHelpAtStartup.Name = "chkShowHelpAtStartup"; 79 | this.chkShowHelpAtStartup.Size = new System.Drawing.Size(167, 20); 80 | this.chkShowHelpAtStartup.TabIndex = 3; 81 | this.chkShowHelpAtStartup.Text = "Show help at startup"; 82 | this.chkShowHelpAtStartup.UseVisualStyleBackColor = true; 83 | // 84 | // label4 85 | // 86 | this.label4.AutoSize = true; 87 | this.label4.Font = new System.Drawing.Font("Verdana", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 88 | this.label4.Location = new System.Drawing.Point(15, 111); 89 | this.label4.Name = "label4"; 90 | this.label4.Size = new System.Drawing.Size(446, 32); 91 | this.label4.TabIndex = 4; 92 | this.label4.Text = "You can capture the key you want to map or choose it from a list\r\nby choosing Cre" + 93 | "ate New Mapping from the Mappings menu."; 94 | // 95 | // label5 96 | // 97 | this.label5.AutoSize = true; 98 | this.label5.Font = new System.Drawing.Font("Verdana", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 99 | this.label5.Location = new System.Drawing.Point(15, 161); 100 | this.label5.Name = "label5"; 101 | this.label5.Size = new System.Drawing.Size(453, 32); 102 | this.label5.TabIndex = 1; 103 | this.label5.Text = "If the key you want to map and the one you want to map it to are\r\nboth visible, d" + 104 | "rag and drop the action key onto the target key.\r\n"; 105 | // 106 | // panel1 107 | // 108 | this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; 109 | this.panel1.Controls.Add(this.labelFAQ); 110 | this.panel1.Controls.Add(this.chkShowHelpAtStartup); 111 | this.panel1.Location = new System.Drawing.Point(-1, 206); 112 | this.panel1.Name = "panel1"; 113 | this.panel1.Size = new System.Drawing.Size(500, 26); 114 | this.panel1.TabIndex = 5; 115 | // 116 | // labelFAQ 117 | // 118 | this.labelFAQ.AutoSize = true; 119 | this.labelFAQ.Font = new System.Drawing.Font("Verdana", 9.75F); 120 | this.labelFAQ.Location = new System.Drawing.Point(410, 4); 121 | this.labelFAQ.Name = "labelFAQ"; 122 | this.labelFAQ.Size = new System.Drawing.Size(80, 16); 123 | this.labelFAQ.TabIndex = 4; 124 | this.labelFAQ.TabStop = true; 125 | this.labelFAQ.Text = "Online FAQ"; 126 | this.labelFAQ.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.labelFAQClick); 127 | // 128 | // HelpForm 129 | // 130 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 13F); 131 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 132 | this.ClientSize = new System.Drawing.Size(495, 231); 133 | this.Controls.Add(this.panel1); 134 | this.Controls.Add(this.label4); 135 | this.Controls.Add(this.label3); 136 | this.Controls.Add(this.label5); 137 | this.Controls.Add(this.label2); 138 | this.Controls.Add(this.label1); 139 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 140 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 141 | this.MaximizeBox = false; 142 | this.MinimizeBox = false; 143 | this.Name = "HelpForm"; 144 | this.ShowInTaskbar = false; 145 | this.Text = "KeyMapper Help"; 146 | this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.HelpFormFormClosed); 147 | this.panel1.ResumeLayout(false); 148 | this.panel1.PerformLayout(); 149 | this.ResumeLayout(false); 150 | this.PerformLayout(); 151 | 152 | } 153 | 154 | private System.Windows.Forms.Label label1; 155 | private System.Windows.Forms.Label label2; 156 | private System.Windows.Forms.Label label3; 157 | private System.Windows.Forms.CheckBox chkShowHelpAtStartup; 158 | private System.Windows.Forms.Label label4; 159 | private System.Windows.Forms.Label label5; 160 | private System.Windows.Forms.Panel panel1; 161 | private System.Windows.Forms.LinkLabel labelFAQ; 162 | } 163 | } -------------------------------------------------------------------------------- /keymapper/Classes/KeySniffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.InteropServices; 4 | using System.Security.Permissions; 5 | using KeyMapper.Classes.Interop; 6 | // ReSharper disable InconsistentNaming 7 | // ReSharper disable IdentifierTypo 8 | 9 | namespace KeyMapper.Classes 10 | { 11 | 12 | public delegate IntPtr LowLevelKeyboardProc(int code, IntPtr wParam, IntPtr lParam); 13 | 14 | internal class KeySniffer : IDisposable 15 | { 16 | private const int WH_KEYBOARD_LL = 13; 17 | private const int WM_KEYDOWN = 0x100; 18 | private const int WM_SYSKEYDOWN = 0x104; 19 | 20 | private LowLevelKeyboardProc _proc; 21 | 22 | // Implemented a subclass of CriticalHandleZeroOrMinusOneIsInvalid 23 | // to make sure handle is released, but it meant giving up too much control 24 | // of when the hook is deactivated. 25 | 26 | private IntPtr hookId; 27 | 28 | private readonly bool suppressKeystroke; 29 | private bool disposed; 30 | 31 | public event EventHandler KeyPressed; 32 | 33 | public KeySniffer(bool suppressKeystroke) 34 | { 35 | this.suppressKeystroke = suppressKeystroke; 36 | } 37 | 38 | // Default to not suppressing keypresses 39 | public KeySniffer() 40 | : this(false) 41 | { 42 | } 43 | 44 | ~KeySniffer() 45 | { 46 | Dispose(false); 47 | } 48 | 49 | public void Dispose() 50 | { 51 | Dispose(true); 52 | GC.SuppressFinalize(this); 53 | } 54 | 55 | protected virtual void Dispose(bool disposing) 56 | { 57 | if (!disposed) 58 | { 59 | if (disposing) 60 | { 61 | if (hookId != IntPtr.Zero) 62 | { 63 | Unhook(); 64 | } 65 | } 66 | disposed = true; 67 | } 68 | } 69 | 70 | 71 | public void ActivateHook() 72 | { 73 | if (hookId != IntPtr.Zero) 74 | { 75 | // Already hooked.. 76 | return; 77 | } 78 | 79 | if (_proc == null) 80 | { 81 | _proc = HookCallback; 82 | GC.KeepAlive(_proc); 83 | } 84 | 85 | Hook(); 86 | 87 | } 88 | 89 | public void DeactivateHook() 90 | { 91 | 92 | if (hookId == IntPtr.Zero) 93 | { 94 | // there is no hook.. 95 | return; 96 | } 97 | 98 | Unhook(); 99 | } 100 | 101 | [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] 102 | private void Hook() 103 | { 104 | 105 | using (var curProcess = Process.GetCurrentProcess()) 106 | using (var curModule = curProcess.MainModule) 107 | { 108 | hookId = NativeMethods.SetWindowsHookEx(WH_KEYBOARD_LL, _proc, 109 | NativeMethods.GetModuleHandle(curModule.ModuleName), 0); 110 | 111 | if (hookId == IntPtr.Zero) 112 | { 113 | int errorCode = Marshal.GetLastWin32Error(); 114 | throw new System.ComponentModel.Win32Exception(errorCode); 115 | } 116 | } 117 | } 118 | 119 | [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] 120 | private void Unhook() 121 | { 122 | 123 | // Documentation on UnhookWindows Ex states: 124 | // "If the function succeeds, the return value is nonzero. 125 | // If the function fails, the return value is zero. To get extended error information, call GetLastError." 126 | 127 | // This generates FXCop warning "Method called GetLastWin32Error but the immediately 128 | // preceding call to IntPtr.op_Explicit(IntPtr):Int32 is not a platform invoke statement. Move the call to 129 | // GetLastWin32Error so that it immediately follows the relevant platform invoke call." 130 | 131 | // Sure looks to me like that's wrong, probable because the method is in a different class, which 132 | // fxcop told me to do in the first place. 133 | 134 | if (hookId == IntPtr.Zero) 135 | { 136 | return; 137 | } 138 | 139 | int result = (int)NativeMethods.UnhookWindowsHookEx(hookId); 140 | int error = Marshal.GetLastWin32Error(); 141 | 142 | if (result == 0) 143 | { 144 | if (error != 1404) // 1404 is 'Invalid hook handle.' 145 | { 146 | // Well, this is bad. A key-suppressing keyboard hook that fails to unhook could paralyse the system. 147 | // Throwing a hissy fit isn't going to achieve anything though. 148 | Console.WriteLine("UnhookWindowsEx failed with error code {0}", error); 149 | } 150 | } 151 | 152 | hookId = IntPtr.Zero; 153 | _proc = null; 154 | 155 | } 156 | 157 | private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) 158 | { 159 | if (nCode >= 0 && (wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN)) 160 | { 161 | // Cast lParam into our structure 162 | var keypress = (KBHookStruct)Marshal.PtrToStructure(lParam, typeof(KBHookStruct)); 163 | 164 | // Console.WriteLine("ScanCode: {0}, Extended: {1}, KeyCode: {2}, Name: {3}", 165 | // keypress.ScanCode, keypress.Extended, keypress.VirtualKeyCode, AppController.GetKeyName(keypress.ScanCode, keypress.Extended)); 166 | 167 | if (keypress.ScanCode == 541) 168 | { 169 | // Right Alt, at least on my Dell SK-8115 keyboard 170 | // Console.WriteLine("Fixing Dell's Right Alt keyboard bug"); 171 | 172 | keypress.ScanCode = 56; 173 | keypress.KeyFlags = 1; 174 | 175 | } 176 | 177 | if (keypress.VirtualKeyCode == 19) 178 | { 179 | // Pause. This doesn't capture well - it's extended value is 225 180 | // rather than 224, so 181 | 182 | keypress.ScanCode = 29; 183 | keypress.KeyFlags = 2; 184 | 185 | } 186 | 187 | // Some keyboards report Num Lock as having the extended bit set 188 | // on keypress, but that doesn't work in a mapping. 189 | if (keypress.ScanCode == 69 && keypress.Extended == 224) 190 | { 191 | // The Keyboard lies. 192 | keypress.Extended = 0; 193 | } 194 | 195 | // Raise the event: 196 | if (KeyPressed != null) 197 | { 198 | var e = new KeyMapperKeyPressedEventArgs(keypress); 199 | KeyPressed(new object(), e); 200 | } 201 | 202 | if (suppressKeystroke) 203 | { 204 | // Return 1 to suppress the keypress. 205 | return (IntPtr)1; 206 | } 207 | } 208 | return NativeMethods.CallNextHookEx(hookId, nCode, wParam, lParam); 209 | } 210 | } 211 | 212 | [StructLayout(LayoutKind.Sequential)] 213 | public struct KBHookStruct 214 | { 215 | private const int LLKHF_EXTENDED = 0x1; 216 | private const int LLKHF_EXTENDED_PAUSE = 0x2; 217 | 218 | public int VirtualKeyCode { get; set; } 219 | 220 | public int ScanCode { get; set; } 221 | 222 | public int Extended 223 | { 224 | get 225 | { 226 | 227 | if ((LLKHF_EXTENDED & KeyFlags) == LLKHF_EXTENDED) 228 | { 229 | return 224; 230 | } 231 | 232 | if ((LLKHF_EXTENDED_PAUSE & KeyFlags) == LLKHF_EXTENDED_PAUSE) 233 | { 234 | return 225; 235 | } 236 | 237 | return 0; 238 | } 239 | 240 | set => KeyFlags = value == 224 ? LLKHF_EXTENDED : 0; 241 | } 242 | 243 | public int KeyFlags { private get; set; } 244 | 245 | public static bool operator ==(KBHookStruct key1, KBHookStruct key2) 246 | { 247 | // If ScanCode and Extended are the same, it's the same key. 248 | return key1.ScanCode == key2.ScanCode && key1.Extended == key2.Extended; 249 | } 250 | 251 | public override bool Equals(object obj) 252 | { 253 | return obj is KBHookStruct @struct && this == @struct; 254 | } 255 | 256 | // override object.GetHashCode 257 | public override int GetHashCode() 258 | { 259 | return KeyHasher.GetHashFromKeyData(ScanCode, Extended); 260 | } 261 | 262 | public static bool operator !=(KBHookStruct key1, KBHookStruct key2) 263 | { 264 | return !(key1 == key2); 265 | } 266 | 267 | } 268 | 269 | } 270 | -------------------------------------------------------------------------------- /keymapper/Classes/keydataxml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Xml.XPath; 4 | using System.Globalization; 5 | using System.IO; 6 | using KeyMapper.Interfaces; 7 | 8 | namespace KeyMapper.Classes 9 | { 10 | internal class KeyDataXml : IKeyData 11 | { 12 | /// This class handles extracting the key data from XML files 13 | /// via XPath. 14 | private readonly System.Reflection.Assembly currentAssembly = null; 15 | 16 | private const string keyFilename = "keycodes.xml"; 17 | private const string keyboardFilename = "keyboards.xml"; 18 | private readonly XPathNavigator navigator; 19 | private const string CommonlyUsedKeysGroupName = "Commonly Used"; 20 | private const string AllKeysGroupName = "All Keys"; 21 | 22 | public KeyDataXml() 23 | { 24 | currentAssembly = System.Reflection.Assembly.GetExecutingAssembly(); 25 | 26 | // Initialise our navigator from the embedded XML keys file. 27 | using (var xmlStream = GetXmlDocumentAsStream(keyFilename)) 28 | { 29 | var document = new XPathDocument(xmlStream); 30 | navigator = document.CreateNavigator(); 31 | } 32 | } 33 | 34 | private Stream GetXmlDocumentAsStream(string name) 35 | { 36 | return currentAssembly.GetManifestResourceStream("KeyMapper.XML." + name); 37 | } 38 | 39 | private static string GetElementValue(string elementName, XPathNavigator node) 40 | { 41 | var element = node.SelectChildren(elementName, ""); 42 | 43 | if (element.Count > 0) 44 | { 45 | element.MoveNext(); 46 | return element.Current.Value; 47 | } 48 | return string.Empty; 49 | } 50 | 51 | public KeyboardLayoutType GetKeyboardLayoutType(string locale) 52 | { 53 | if (string.IsNullOrEmpty(locale)) 54 | { 55 | return KeyboardLayoutType.US; 56 | } 57 | 58 | // Get the layout type - US, European etc. The locale in the XML file must be upper case! 59 | string expression = @"/keyboards/keyboard[locale='" + locale.ToUpper(CultureInfo.InvariantCulture) + "']"; 60 | 61 | XPathNodeIterator iterator; 62 | 63 | using (var xmlStream = GetXmlDocumentAsStream(keyboardFilename)) 64 | { 65 | var document = new XPathDocument(xmlStream); 66 | var nav = document.CreateNavigator(); 67 | 68 | iterator = nav.Select(expression); 69 | } 70 | 71 | int value = 0; // Default to US 72 | 73 | if (iterator.Count == 1) 74 | { 75 | iterator.MoveNext(); 76 | string layout = GetElementValue("layout", iterator.Current); 77 | if (string.IsNullOrEmpty(layout) == false) 78 | { 79 | value = int.Parse(layout, CultureInfo.InvariantCulture.NumberFormat); 80 | } 81 | } 82 | 83 | return (KeyboardLayoutType)value; 84 | } 85 | 86 | public IEnumerable GetGroupList(int threshold) 87 | { 88 | string expression; 89 | XPathNodeIterator iterator; 90 | var groups = new List(); 91 | 92 | switch (threshold) 93 | { 94 | case -1: 95 | // Get all the group names: add an extra one at the top with all the keys in. 96 | groups.Add(AllKeysGroupName); 97 | expression = "/KeycodeData/keycodes/group[not(.=preceding::*/group)] "; 98 | iterator = navigator.Select(expression); 99 | 100 | foreach (XPathNavigator node in iterator) 101 | { 102 | groups.Add(node.Value); 103 | } 104 | break; 105 | 106 | 107 | case 0: 108 | 109 | // Get all the groups which have a working member: 110 | expression = @"/KeycodeData/keycodes[useful='0']"; 111 | iterator = navigator.Select(expression); 112 | 113 | foreach (XPathNavigator node in iterator) 114 | { 115 | string group = GetElementValue("group", node); 116 | if (groups.Contains(group) == false) 117 | { 118 | groups.Add(@group); 119 | } 120 | } 121 | 122 | break; 123 | 124 | case 1: 125 | 126 | // For this threshold, create an extra group of commonly used keys: 127 | // Most of the Media Keys, browser and email, and Print Screen fall into this category. 128 | 129 | // They have a threshold of 2. 130 | 131 | groups.Add(CommonlyUsedKeysGroupName); 132 | 133 | expression = @"/KeycodeData/keycodes[useful='1']"; 134 | iterator = navigator.Select(expression); 135 | 136 | foreach (XPathNavigator node in iterator) 137 | { 138 | string group = GetElementValue("group", node); 139 | if (groups.Contains(group) == false) 140 | { 141 | groups.Add(@group); 142 | } 143 | } 144 | 145 | break; 146 | 147 | 148 | } 149 | 150 | return groups; 151 | } 152 | 153 | public List GetSortedGroupList(int threshold) 154 | { 155 | var groups = GetGroupList(threshold); 156 | 157 | var sortedGroups = new List(groups); 158 | sortedGroups.Sort(); 159 | 160 | return sortedGroups; 161 | } 162 | 163 | public Dictionary GetGroupMembers(string groupName, int threshold) 164 | { 165 | 166 | // Enumerate group. 167 | string queryExpression; 168 | 169 | if (groupName == AllKeysGroupName) 170 | { 171 | queryExpression = @"/KeycodeData/keycodes[group!='Unmappable Keys']"; 172 | } 173 | else if (groupName == CommonlyUsedKeysGroupName) 174 | { 175 | queryExpression = @"/KeycodeData/keycodes[useful>='2'" + "]"; 176 | } 177 | else 178 | { 179 | queryExpression = @"/KeycodeData/keycodes[group='" + groupName + "' and useful>='" + threshold.ToString(CultureInfo.InvariantCulture.NumberFormat) + "']"; 180 | } 181 | 182 | var iterator = navigator.Select(queryExpression); 183 | 184 | // Gives us a bunch of keycode nodes. 185 | // Given the scanCode / extended from each node, ask for the name from the current layout. 186 | 187 | var dir = new Dictionary(iterator.Count); 188 | 189 | foreach (XPathNavigator node in iterator) 190 | { 191 | int scanCode = int.Parse(GetElementValue("sc", node), CultureInfo.InvariantCulture.NumberFormat); 192 | int extended = int.Parse(GetElementValue("ex", node), CultureInfo.InvariantCulture.NumberFormat); 193 | string name = AppController.GetKeyName(scanCode, extended); 194 | 195 | if (dir.ContainsKey(name)) // ArgumentException results when trying to add duplicate key.. 196 | { 197 | Console.WriteLine("Duplicate name error: Name {0} Existing ScanCode : {1} ScanCode: {2}", name, dir[name], scanCode); 198 | } 199 | else 200 | { 201 | dir.Add(name, KeyHasher.GetHashFromKeyData(scanCode, extended)); 202 | } 203 | } 204 | 205 | return dir; 206 | } 207 | 208 | public IList LocalizableKeys => GetKeys(true); 209 | 210 | public IList NonLocalizableKeys => GetKeys(false); 211 | 212 | private List GetKeys(bool localizable) 213 | { 214 | string expression = @"/KeycodeData/keycodes[localize = '" + (localizable ? "true" : "false") + "']"; 215 | 216 | var iterator = navigator.Select(expression); 217 | 218 | var keys = new List(iterator.Count); 219 | 220 | for (int i = 0; i < iterator.Count; i++) 221 | { 222 | iterator.MoveNext(); 223 | int scanCode = int.Parse(GetElementValue("sc", iterator.Current), CultureInfo.InvariantCulture.NumberFormat); 224 | int extended = int.Parse(GetElementValue("ex", iterator.Current), CultureInfo.InvariantCulture.NumberFormat); 225 | keys.Add(KeyHasher.GetHashFromKeyData(scanCode, extended)); 226 | } 227 | 228 | return keys; 229 | } 230 | 231 | public string GetKeyNameFromCode(int code) 232 | { 233 | int scanCode = KeyHasher.GetScanCodeFromHash(code); 234 | int extended = KeyHasher.GetExtendedFromHash(code); 235 | 236 | string expression = @"/KeycodeData/keycodes[sc = '" + scanCode.ToString(CultureInfo.InvariantCulture.NumberFormat) + "' and ex = '" + extended.ToString(CultureInfo.InvariantCulture.NumberFormat) + "'] "; 237 | 238 | var iterator = navigator.Select(expression); 239 | 240 | string name = string.Empty; 241 | 242 | if (iterator.Count == 1) 243 | { 244 | iterator.MoveNext(); 245 | name = GetElementValue("name", iterator.Current); 246 | } 247 | 248 | return name; 249 | 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /keymapper/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace KeyMapper.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.4.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.UserScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | [global::System.Configuration.DefaultSettingValueAttribute("0, 0")] 29 | public global::System.Drawing.Point KeyboardFormLocation { 30 | get { 31 | return ((global::System.Drawing.Point)(this["KeyboardFormLocation"])); 32 | } 33 | set { 34 | this["KeyboardFormLocation"] = value; 35 | } 36 | } 37 | 38 | [global::System.Configuration.UserScopedSettingAttribute()] 39 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 40 | [global::System.Configuration.DefaultSettingValueAttribute("True")] 41 | public bool KeyboardFormHasNumberPad { 42 | get { 43 | return ((bool)(this["KeyboardFormHasNumberPad"])); 44 | } 45 | set { 46 | this["KeyboardFormHasNumberPad"] = value; 47 | } 48 | } 49 | 50 | [global::System.Configuration.UserScopedSettingAttribute()] 51 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 52 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 53 | public bool UserHasSavedSettings { 54 | get { 55 | return ((bool)(this["UserHasSavedSettings"])); 56 | } 57 | set { 58 | this["UserHasSavedSettings"] = value; 59 | } 60 | } 61 | 62 | [global::System.Configuration.UserScopedSettingAttribute()] 63 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 64 | [global::System.Configuration.DefaultSettingValueAttribute("0")] 65 | public int KeyboardFormWidth { 66 | get { 67 | return ((int)(this["KeyboardFormWidth"])); 68 | } 69 | set { 70 | this["KeyboardFormWidth"] = value; 71 | } 72 | } 73 | 74 | [global::System.Configuration.UserScopedSettingAttribute()] 75 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 76 | [global::System.Configuration.DefaultSettingValueAttribute("0, 0")] 77 | public global::System.Drawing.Point ColourListFormLocation { 78 | get { 79 | return ((global::System.Drawing.Point)(this["ColourListFormLocation"])); 80 | } 81 | set { 82 | this["ColourListFormLocation"] = value; 83 | } 84 | } 85 | 86 | [global::System.Configuration.UserScopedSettingAttribute()] 87 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 88 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 89 | public bool ColourMapFormOpen { 90 | get { 91 | return ((bool)(this["ColourMapFormOpen"])); 92 | } 93 | set { 94 | this["ColourMapFormOpen"] = value; 95 | } 96 | } 97 | 98 | [global::System.Configuration.UserScopedSettingAttribute()] 99 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 100 | [global::System.Configuration.DefaultSettingValueAttribute("0, 0")] 101 | public global::System.Drawing.Point MappingListFormLocation { 102 | get { 103 | return ((global::System.Drawing.Point)(this["MappingListFormLocation"])); 104 | } 105 | set { 106 | this["MappingListFormLocation"] = value; 107 | } 108 | } 109 | 110 | [global::System.Configuration.UserScopedSettingAttribute()] 111 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 112 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 113 | public bool KeyboardFormHasMacKeyboard { 114 | get { 115 | return ((bool)(this["KeyboardFormHasMacKeyboard"])); 116 | } 117 | set { 118 | this["KeyboardFormHasMacKeyboard"] = value; 119 | } 120 | } 121 | 122 | [global::System.Configuration.UserScopedSettingAttribute()] 123 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 124 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 125 | public bool MappingListFormOpen { 126 | get { 127 | return ((bool)(this["MappingListFormOpen"])); 128 | } 129 | set { 130 | this["MappingListFormOpen"] = value; 131 | } 132 | } 133 | 134 | [global::System.Configuration.UserScopedSettingAttribute()] 135 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 136 | [global::System.Configuration.DefaultSettingValueAttribute("0, 0")] 137 | public global::System.Drawing.Point EditMappingFormLocation { 138 | get { 139 | return ((global::System.Drawing.Point)(this["EditMappingFormLocation"])); 140 | } 141 | set { 142 | this["EditMappingFormLocation"] = value; 143 | } 144 | } 145 | 146 | [global::System.Configuration.UserScopedSettingAttribute()] 147 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 148 | [global::System.Configuration.DefaultSettingValueAttribute("0")] 149 | public int MappingListFormWidth { 150 | get { 151 | return ((int)(this["MappingListFormWidth"])); 152 | } 153 | set { 154 | this["MappingListFormWidth"] = value; 155 | } 156 | } 157 | 158 | [global::System.Configuration.UserScopedSettingAttribute()] 159 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 160 | [global::System.Configuration.DefaultSettingValueAttribute("0, 0")] 161 | public global::System.Drawing.Point ColourEditorLocation { 162 | get { 163 | return ((global::System.Drawing.Point)(this["ColourEditorLocation"])); 164 | } 165 | set { 166 | this["ColourEditorLocation"] = value; 167 | } 168 | } 169 | 170 | [global::System.Configuration.UserScopedSettingAttribute()] 171 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 172 | [global::System.Configuration.DefaultSettingValueAttribute("True")] 173 | public bool ShowHelpFormAtStartup { 174 | get { 175 | return ((bool)(this["ShowHelpFormAtStartup"])); 176 | } 177 | set { 178 | this["ShowHelpFormAtStartup"] = value; 179 | } 180 | } 181 | 182 | [global::System.Configuration.UserScopedSettingAttribute()] 183 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 184 | [global::System.Configuration.DefaultSettingValueAttribute("0, 0")] 185 | public global::System.Drawing.Point HelpFormLocation { 186 | get { 187 | return ((global::System.Drawing.Point)(this["HelpFormLocation"])); 188 | } 189 | set { 190 | this["HelpFormLocation"] = value; 191 | } 192 | } 193 | 194 | [global::System.Configuration.UserScopedSettingAttribute()] 195 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 196 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 197 | public bool ColourMapShowAllButtons { 198 | get { 199 | return ((bool)(this["ColourMapShowAllButtons"])); 200 | } 201 | set { 202 | this["ColourMapShowAllButtons"] = value; 203 | } 204 | } 205 | 206 | [global::System.Configuration.UserScopedSettingAttribute()] 207 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 208 | [global::System.Configuration.DefaultSettingValueAttribute("True")] 209 | public bool UpgradeRequired { 210 | get { 211 | return ((bool)(this["UpgradeRequired"])); 212 | } 213 | set { 214 | this["UpgradeRequired"] = value; 215 | } 216 | } 217 | 218 | [global::System.Configuration.UserScopedSettingAttribute()] 219 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 220 | [global::System.Configuration.DefaultSettingValueAttribute("0")] 221 | public int KeyboardLayout { 222 | get { 223 | return ((int)(this["KeyboardLayout"])); 224 | } 225 | set { 226 | this["KeyboardLayout"] = value; 227 | } 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /keymapper/Controls/KeyPictureBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | using System.Drawing; 4 | using KeyMapper.Classes; 5 | using KeyMapper.Classes.Interop; 6 | 7 | namespace KeyMapper.Controls 8 | { 9 | internal sealed class KeyPictureBox : KMPictureBox 10 | { 11 | private IntPtr iconHandle; 12 | private Cursor dragCursor; 13 | private readonly float dragIconScale; 14 | private bool outsideForm; 15 | private readonly bool mapped; 16 | private readonly BlankButton button; 17 | private readonly int horizontalStretch; 18 | private readonly int verticalStretch; 19 | private readonly float scale; 20 | private Rectangle dragBox; 21 | 22 | private bool escapePressed; 23 | 24 | // These are always the physical values not any mapped ones. 25 | private readonly int scanCode; 26 | private readonly int extended; 27 | 28 | public KeyMapping Map { get; } 29 | 30 | public KeyPictureBox(int scanCode, int extended, BlankButton button, float scale, int horizontalStretch, int verticalStretch) 31 | { 32 | this.scanCode = scanCode; 33 | this.extended = extended; 34 | this.button = button; 35 | this.scale = scale; 36 | this.horizontalStretch = horizontalStretch; 37 | this.verticalStretch = verticalStretch; 38 | dragIconScale = 0.75F; 39 | dragBox = Rectangle.Empty; 40 | 41 | Map = MappingsManager.GetKeyMapping(scanCode, extended); 42 | 43 | mapped = Map.To.ScanCode != -1; 44 | 45 | AllowDrop = true; 46 | 47 | // Box controls itself. 48 | DragOver += KeyPictureBoxDragOver; 49 | DragDrop += KeyPictureBoxDragDrop; 50 | DragLeave += KeyPictureBoxDragLeave; 51 | GiveFeedback += KeyPictureBoxGiveFeedback; 52 | MouseDown += KeyPictureBoxMouseDown; 53 | MouseMove += KeyPictureBoxMouseMove; 54 | MouseUp += KeyPictureBoxMouseUp; 55 | QueryContinueDrag += KeyPictureBoxQueryContinueDrag; 56 | 57 | DrawKey(); 58 | Width = Image.Width; 59 | Height = Image.Height; 60 | } 61 | 62 | private void DrawKey() 63 | { 64 | int scanCode = this.scanCode; 65 | int extended = this.extended; 66 | 67 | ButtonEffect effect; 68 | 69 | if (MappingsManager.IsEmptyMapping(Map) == false) 70 | { 71 | // Remapped or disabled? 72 | if (MappingsManager.IsDisabledMapping(Map)) 73 | { 74 | // Disabled 75 | if (MappingsManager.IsMappingPending(Map)) 76 | { 77 | effect = ButtonEffect.DisabledPending; 78 | } 79 | else 80 | { 81 | effect = ButtonEffect.Disabled; 82 | } 83 | } 84 | else 85 | { 86 | // Is this key mapped under the current filter? 87 | if (MappingsManager.IsMappingPending(Map)) 88 | { 89 | effect = ButtonEffect.MappedPending; 90 | } 91 | else 92 | { 93 | effect = ButtonEffect.Mapped; 94 | } 95 | 96 | // Either way, we want the button to show what it is (will be) mapped to: 97 | scanCode = Map.To.ScanCode; 98 | extended = Map.To.Extended; 99 | } 100 | } 101 | else 102 | { 103 | // Not mapped now, but was this key mapped before under the current filter?? 104 | var km = MappingsManager.GetClearedMapping(scanCode, extended); 105 | if (MappingsManager.IsEmptyMapping(km)) 106 | { 107 | effect = ButtonEffect.None; 108 | } 109 | else if (MappingsManager.IsDisabledMapping(km)) 110 | { 111 | effect = ButtonEffect.EnabledPending; 112 | } 113 | else 114 | { 115 | effect = ButtonEffect.UnmappedPending; 116 | } 117 | } 118 | 119 | var buttonImage = ButtonImages.GetButtonImage( 120 | scanCode, extended, button, horizontalStretch, verticalStretch, scale, effect); 121 | 122 | SetImage(buttonImage); 123 | } 124 | 125 | 126 | private void KeyPictureBoxQueryContinueDrag(object sender, QueryContinueDragEventArgs e) 127 | { 128 | 129 | // e.Action = DragAction.Continue; 130 | 131 | bool wasOutsideAlready = outsideForm; 132 | 133 | IsControlOutsideForm(sender); 134 | 135 | if (wasOutsideAlready && !outsideForm) 136 | { 137 | // Have reentered form 138 | SetDragCursor( 139 | ButtonImages.GetButtonImage( 140 | scanCode, extended, button, horizontalStretch, verticalStretch, scale, ButtonEffect.None)); 141 | } 142 | 143 | if (outsideForm) 144 | { 145 | if (mapped) 146 | { 147 | // Change icon to be original. 148 | SetDragCursor( 149 | ButtonImages.GetButtonImage( 150 | scanCode, extended, button, horizontalStretch, verticalStretch, scale, ButtonEffect.None)); 151 | } 152 | else 153 | { 154 | // Show disabled 155 | SetDragCursor( 156 | ButtonImages.GetButtonImage( 157 | scanCode, extended, button, horizontalStretch, verticalStretch, scale, ButtonEffect.Disabled)); 158 | } 159 | } 160 | 161 | if (e.EscapePressed) 162 | { 163 | e.Action = DragAction.Cancel; 164 | escapePressed = true; 165 | } 166 | else 167 | { 168 | escapePressed = false; 169 | } 170 | } 171 | 172 | private void SetDragCursor(Bitmap bmp) 173 | { 174 | ReleaseIconResources(); 175 | bmp = ButtonImages.ResizeBitmap(bmp, dragIconScale, false); 176 | iconHandle = bmp.GetHicon(); 177 | dragCursor = new Cursor(iconHandle); 178 | bmp.Dispose(); 179 | } 180 | 181 | private void ReleaseIconResources() 182 | { 183 | if (iconHandle != IntPtr.Zero) 184 | { 185 | if (dragCursor != null) 186 | { 187 | dragCursor.Dispose(); 188 | dragCursor = null; 189 | } 190 | NativeMethods.DestroyIcon(iconHandle); 191 | } 192 | } 193 | 194 | private void KeyPictureBoxMouseDown(object sender, MouseEventArgs e) 195 | { 196 | if (e.Button == MouseButtons.Left) 197 | { 198 | 199 | // Create a dragbox so we can tell if the mouse moves far enough while down to trigger a drag event 200 | var dragSize = SystemInformation.DragSize; 201 | dragBox = new Rectangle(new Point(e.X - dragSize.Width / 2, e.Y - dragSize.Height / 2), dragSize); 202 | } 203 | } 204 | 205 | // This only fires when no drag operation commences. 206 | private void KeyPictureBoxMouseUp(object sender, MouseEventArgs e) 207 | { 208 | dragBox = Rectangle.Empty; 209 | } 210 | 211 | private void KeyPictureBoxMouseMove(object sender, MouseEventArgs e) 212 | { 213 | if (dragBox == Rectangle.Empty || dragBox.Contains(e.X, e.Y) == false) 214 | { 215 | return; 216 | } 217 | 218 | dragBox = Rectangle.Empty; 219 | 220 | // Draw self to bitmap, then convert to an icon via a handle 221 | // both of shich which we must release 222 | 223 | var bmp = new Bitmap(Width, Height); 224 | DrawToBitmap(bmp, new Rectangle(0, 0, Size.Width, Size.Height)); 225 | 226 | SetDragCursor(bmp); 227 | 228 | var de = DoDragDrop(Map, DragDropEffects.Copy); 229 | 230 | if (escapePressed == false) 231 | { 232 | if (outsideForm) 233 | { 234 | // Outside drag. 235 | if (mapped) 236 | { 237 | DeleteCurrentMapping(); 238 | } 239 | else 240 | { 241 | DisableKey(); 242 | } 243 | } 244 | } 245 | // Now we are done. Release icon. 246 | ReleaseIconResources(); 247 | } 248 | 249 | private void DeleteCurrentMapping() 250 | { 251 | MappingsManager.DeleteMapping(Map); 252 | } 253 | 254 | private void DisableKey() 255 | { 256 | MappingsManager.AddMapping(new KeyMapping(Map.From, new Key(0, 0))); 257 | } 258 | 259 | private void KeyPictureBoxGiveFeedback(object sender, GiveFeedbackEventArgs e) 260 | { 261 | 262 | //e.UseDefaultCursors = false; 263 | //Cursor.Current = cur; 264 | 265 | IsControlOutsideForm(sender); 266 | 267 | // Console.WriteLine("Effect: {0} OutsideForm: {1}", e.Effect, outsideForm); 268 | 269 | if (e.Effect == DragDropEffects.None && !outsideForm) 270 | { 271 | e.UseDefaultCursors = true; 272 | } 273 | else 274 | { 275 | e.UseDefaultCursors = false; 276 | Cursor.Current = dragCursor; 277 | } 278 | 279 | } 280 | 281 | private void IsControlOutsideForm(object originator) 282 | { 283 | if (originator is Control ctrl) 284 | { 285 | var frm = ctrl.FindForm(); 286 | if (frm != null) 287 | { 288 | var loc = SystemInformation.WorkingArea.Location; 289 | 290 | outsideForm = 291 | MousePosition.X - loc.X < frm.DesktopBounds.Left || 292 | MousePosition.X - loc.X > frm.DesktopBounds.Right || 293 | MousePosition.Y - loc.Y < frm.DesktopBounds.Top || 294 | MousePosition.Y - loc.Y > frm.DesktopBounds.Bottom; 295 | 296 | } 297 | } 298 | } 299 | 300 | private void KeyPictureBoxDragLeave(object sender, EventArgs e) 301 | { 302 | DrawKey(); 303 | } 304 | 305 | private void KeyPictureBoxDragDrop(object sender, DragEventArgs e) 306 | { 307 | 308 | if (e.Data.GetDataPresent("KeyMapper.KeyMapping")) 309 | { 310 | var draggedMap = (KeyMapping)e.Data.GetData("KeyMapper.KeyMapping"); 311 | 312 | if (MappingsManager.AddMapping(new KeyMapping(Map.From, draggedMap.From)) == false) 313 | { 314 | // Mapping failed. Need to revert our appearance.. 315 | DrawKey(); 316 | } 317 | } 318 | } 319 | 320 | private void KeyPictureBoxDragOver(object sender, DragEventArgs e) 321 | { 322 | 323 | if (e.Data.GetDataPresent("KeyMapper.KeyMapping") == false) 324 | { 325 | e.Effect = DragDropEffects.None; 326 | return; 327 | } 328 | 329 | var draggedMap = (KeyMapping)e.Data.GetData("KeyMapper.KeyMapping"); 330 | 331 | if (draggedMap.To.ScanCode >= 0) 332 | { 333 | // Can't drop a mapped key onto another key 334 | e.Effect = DragDropEffects.None; 335 | return; 336 | } 337 | 338 | if (draggedMap.From == Map.From) 339 | { 340 | return; // No need to redraw self 341 | } 342 | 343 | // Console.WriteLine("Dragover: " + scanCode) 344 | 345 | SetImage(ButtonImages.GetButtonImage 346 | (draggedMap.From.ScanCode, draggedMap.From.Extended, 347 | button, horizontalStretch, verticalStretch, scale, ButtonEffect.MappedPending)); 348 | 349 | e.Effect = DragDropEffects.Copy; 350 | 351 | } 352 | 353 | // When disposing, make sure that final bitmap is released. 354 | ~KeyPictureBox() 355 | { 356 | ReleaseImage(); 357 | ReleaseIconResources(); 358 | 359 | } 360 | } 361 | } 362 | --------------------------------------------------------------------------------