Windows Shortcuts with Global Hotkeys

Besides the well-known stuff of the Windows Shortcuts (.lnk), there is an another feature which is mostly not known to users. Shortcut can have global hotkey. When I say global, I mean it. By this way you can bind launch of any app (file or whatever) to hotkey which will work everywhere. For example, we can make the Notepad start by a single keyboard combo without any AutoIt or other custom handlers.

There are limitations:

  • Shortcut should be placed on Desktop. May be there are another places like Start Menu, but this feature will not work from custom directory.
  • Explorer Properties dialog supports only Ctrl+Alt+Something hotkeys which is not good. Many usual apps use Ctrl+Alt combinations for their own functions.
  • Hotkeys can't overlap each other. Only one of them will work in such case.

There are two points of interest here:

  • To make hotkeys with modifiers others than Ctrl+Alt.
  • To create shortcut with global hotkey programmatically.

They are both the same, because there is no way to assign something another than Ctrl+Alt to hotkey using Properties dialog. There is one exception in this rule that is Properties dialog allows single keys like Numpad8 to be assigned. I don't see reason in this.

The Working Code

The usual code for shortcut creation on C# is:

WshShell shell = new WshShell(), 
try
{
  IWshShortcut link = (IWshShortcut)shell.CreateShortcut(myShortCutPath), 
  link.TargetPath = myExecutablePath, 
  link.Description = "Run my awesome app", 
  link.Arguments = myExecutableArgs, 
  link.Hotkey = myHotkey, // here
  link.Save(), 
}
finally
{
  Marshal.ReleaseComObject(shell), 
}

This code will require adding COM dependency from Windows Script Host Object Model to your project.

If you create it in Desktop directory (Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "test.lnk")) hotkey will work immediately. But what is the myHotkey? It receives string like Ctrl+Alt+K but with some mysterious rules. And this string is different than the string you will see in Explorer Properties dialog.

Modifier keys work in the way that we expect: Alt, Ctrl and Shift. Unfortunately I didn't manage WinKey to work, so we can't create shortcut for Win+Z for example.

Modifier Keys

To build the modifier part of myHotkey string we could use simple enum:

[Flags]
public enum ModifierKey
{
  Shift = 1 << 0, 
  Ctrl = 1 << 1, 
  Alt = 1 << 2, 
}

Bit values are also meaningful and this will be discussed later. Simple code to build modifier part of myHotkey string is:

StringBuilder sb = new StringBuilder(), 

if ((modifierKey & ModifierKey.Ctrl) != 0)
  sb.Append("Ctrl+"), 

if ((modifierKey & ModifierKey.Shift) != 0)
  sb.Append("Shift+"), 

if ((modifierKey & ModifierKey.Alt) != 0)
  sb.Append("Alt+"), 

Usual keys

The keys are a bit tricky here. Basically we can use System.Windows.Forms.Keys values, but not all of them can be accepted and not all of them will work directly with ToString(). Here are the enumeration of working keys:

public enum PressableKeys
{
  // The TAB key.
  Tab = 9, 
  // The ENTER key. Use "Return" name.
  Enter = 13, 
  // The PAUSE key.
  Pause = 19, 
  // The CAPS LOCK key. Use "Captial" name (not a typo, this spelling should be used).
  CapsLock = 20, 
  // The ESC key.
  Escape = 27, 
  // The SPACEBAR key.
  Space = 32, 
  // The PAGE UP key. Use "Ext+Prior" name.
  PageUp = 33, 
  // The PAGE DOWN key. Use "Ext+Next" name.
  PageDown = 34, 
  // The END key.
  End = 35, 
  // The HOME key.
  Home = 36, 
  // The LEFT ARROW key.
  Left = 37, 
  // The UP ARROW key.
  Up = 38, 
  // The RIGHT ARROW key.
  Right = 39, 
  // The DOWN ARROW key.
  Down = 40, 
  // The INS key.
  Insert = 45, 
  // The DEL key.
  Delete = 46, 
  // The 0 key. Use "0" name.
  D0 = 48, 
  // code skipped. All D0..D9 will work the same way.
  // The 9 key. Use "9" name.
  D9 = 57, 
  // The A key.
  A = 65, 
  // code skipped. All A..Z will work the same way.
  // The Z key.
  Z = 90, 
  // The 0 key on the numeric keypad. Use "Numpad0".
  NumPad0 = 96, 
  // code skipped. All Numpad0..Numpad9 will work the same way.
  // The 9 key on the numeric keypad. Use "Numpad9".
  NumPad9 = 105, 
  // The multiply (numpad star) key.
  Multiply = 106, 
  // The add (numpad plus) key.
  Add = 107, 
  // The separator key.
  Separator = 108, 
  // The subtract (numpad minus) key.
  Subtract = 109, 
  // The decimal (numpad dot) key.
  Decimal = 110, 
  // The divide (numpad slash) key.
  Divide = 111, 
  // The F1 key.
  F1 = 112, 
  // code skipped. All F1..F12 will work the same way.
  // The F12 key.
  F12 = 123, 
  // The NUM LOCK key.
  NumLock = 144, 
  // The SCROLL LOCK key.
  Scroll = 145, 
}

For members which are not commented as "Use xxx name" the direct ToString() value can be used.

Hacks

String form is really not the best for this goal, because we have to follow keyboard naming rules, with all their typos. However, if we use COM and string form, the hotkey will begin to work immediately after we call IWshShortcut.Save().

But yes, there is an another way. After creating the shortcut file using of COM we can write two bytes at position 0x40 (64) using the values of enums that I've listed:

using (Stream stream = new FileStream(shortcutPath, FileMode.Open, FileAccess.ReadWrite))
{
  stream.Position = 64, 
  stream.WriteByte((byte)pressableKey), 
  stream.WriteByte((byte)modifierKey), 
}

With this way the hotkey will not work immediately. Restart of OS or at least Windows Explorer will be required.