views:

576

answers:

3

I work on a project called UAWKS (Unofficial Apple Wireless Keyboard Support) that helps Windows users use Apple's bluetooth keyboard. One of the main goals of UAWKS is to swap the Cmd key (which behaves as Winkey in Windows) with Ctrl, allowing users to do Cmd+C for copy, Cmd+T for new tab, etc.

It is currently developed using AutoHotkey, which worked pretty well under Windows XP. However, on Vista and Windows 7, Cmd+L causes problems:

  • Regardless of low-level keyboard hooks, Winkey+L is always intercepted by Windows and normally locks the workstation...
  • You can disable workstation locking with this registry hack, but pressing Winkey+L still can't be rebound in AHK
  • Pressing Winkey+L leaves Winkey in the Keydown state until the next (additional) Winkey Up. Simulating a Keyup event doesn't seem to work either!

It seems that Winkey+L is a special chord that messes everything else up.

I've looked through the AHK source code, and they try to address this problem in SendKey() in keyboard_mouse.cpp (near line 883 in v1.0.48.05), but it doesn't work. I wrote up my own low-level keyboard hook application in C#, and I see the same problem.

Has anyone else run into this? Is there a workaround?

A: 

If you can detect the key Cmd+L could you just go ahead and lock the workstation without bothering to forward Winkey+L? you can do it with the API LockWorkstation (or rundll32.exe user32.dll,LockWorkStation)

Alex K.
Actually, I'm trying to _prevent_ the workstation from locking. I can do this with the registry hack, but the Winkey state still gets messed up whenever you press Winkey+L/Cmd+L (they're the same; I kind of used Cmd and Winkey interchangably above, sorry for the confusion. On an apple keyboard it's called Cmd, but the Windows keyboard driver interprets it as Winkey.)
Brian Jorgensen
A: 

I tried to interrupt the windows key using the Windows Input Simulator library. This is my callback:

private static unsafe IntPtr HookCallback( int nCode, IntPtr wParam, IntPtr lParam )
{
    if( nCode >= 0 && ( wParam == (IntPtr)WM_KEYDOWN ) )
    {
        var replacementKey = (KBDLLHOOKSTRUCT*)lParam;
        if( replacementKey->vkCode == (int)VirtualKeyCode.LWIN )
        {
            InputSimulator.SimulateKeyDown( VirtualKeyCode.SHIFT );
            return (IntPtr)1;
        }
    }
    return CallNextHookEx( m_HookID, nCode, wParam, lParam );
}

Using this hook my left windows key acts as a shift key (as implemented & expected) under Win XP.
Pressing WinKey + l returns just L.

EDIT: However, I can confirm your observation, that this code does not work under Windows 7 anymore :/ Sorry, I can't help you any further.

tanascius
Thanks, but I'm actually using InputSimulator for my sandbox C# app. For some reason the Winkey down gets stuck before I even call InputSimulator.SimulateKeyDown(vkCode), so the effect ends up being Winkey+vkCode. And this ONLY happens with Winkey+L.
Brian Jorgensen
Much appreciated, thanks!
Brian Jorgensen
+1  A: 

I figured out a way to do this in C#. There are four states involved in a possible Win+L keypress sequence (None, Win, Win+L, L). Whenever the Win+L state is reached, set a flag ("winLSet" below). Whenever all of the keys have been released, we check for this flag and simulate the press if it's been set.

The final piece of the puzzle is to simulate the WinKey's KeyUp before the Ctrl-L (no KeyDown). I've tried similar approaches in AutoHotkey and it never worked, but it seems to work perfectly here.

The code is below. Please see explanatory notes at the bottom if you plan to use this code.

public partial class MainWindow : Window
{
    LowLevelKeyboardHook hook;

    bool winKeyDown;
    bool lKeyDown;
    bool winLSet;

    public MainWindow()
    {
        InitializeComponent();

        hook = new LowLevelKeyboardHook();

        hook.KeyDown += OnKeyDown;
        hook.KeyUp += OnKeyUp;
    }

    void OnKeyDown(object sender, LowLevelKeyEventArgs e)
    {
        e.EventHandled = true;

        switch (e.Key)
        {
            case Key.L:
                lKeyDown = true;
                UpdateWinLState();
                e.EventHandled = winKeyDown;
                break;

            case Key.LWin:
                winKeyDown = true;
                UpdateWinLState();
                InputSimulator.SimulateKeyDown(VirtualKeyCode.LCONTROL);
                break;

            case Key.LeftCtrl:
                InputSimulator.SimulateKeyDown(VirtualKeyCode.LWIN);
                break;

            default:
                e.EventHandled = false;
                break;
        }
    }

    void OnKeyUp(object sender, LowLevelKeyEventArgs e)
    {
        e.EventHandled = true;

        switch (e.Key)
        {
            case Key.L:
                lKeyDown = false;
                UpdateWinLState();
                e.EventHandled = winKeyDown;
                break;

            case Key.LWin:
                winKeyDown = false;
                UpdateWinLState();
                InputSimulator.SimulateKeyUp(VirtualKeyCode.LCONTROL);
                break;

            case Key.LeftCtrl:
                InputSimulator.SimulateKeyUp(VirtualKeyCode.LWIN);
                break;

            default:
                e.EventHandled = false;
                break;
        }
    }

    void UpdateWinLState()
    {
        if (winKeyDown && lKeyDown)
        {
            winLSet = true;
        }
        else if (!winKeyDown && !lKeyDown && winLSet)
        {
            winLSet = false;

            InputSimulator.SimulateKeyUp(VirtualKeyCode.LWIN);

            InputSimulator.SimulateModifiedKeyStroke(
                VirtualKeyCode.LCONTROL,
                (VirtualKeyCode)'L');
        }
    }
}

For posterity: please note that this code uses InputSimulator and LowLevelKeyboardHook, which are not from the .NET Framework. LowLevelKeyboardHook is a class I wrote a while back that exposes global KeyDown and KeyUp events as C# events. There are similar examples here, here, and a bunch can be found here.

Also notice that I'm using System.Windows.Input.Key, not System.Windows.Forms.Keys, which could confuse some people. System.Windows.Input.Key is the new enumeration of keys in .NET 3.0 and above, while System.Windows.Forms.Keys is the old enumeration from Windows Forms.

Brian Jorgensen