2

I'm writing a program that uses Caps Lock as a toggle switch. It would be nice to set the LED of the key to show that my program is on or off, like the Caps Lock key does naturally.

I know that I could just SendInput('Capslock'); or whatever to actually turn caps-lock on and off. But my application is a typing program, and I don't want to have to deal with translating the all-caps keys that turning it on would give me into their lower/upper cases. I might go that route eventually, but not for this version.

I would however be interested in just turning on the LED light without actually turning on Caps Lock. Is there a way to do that?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ck_
  • 3,719
  • 10
  • 49
  • 76
  • 10
    You might want to reconsider your design. If turning on the caps lock key doesn't let users type all-caps, then your app has big usability flaws. – Anon. Feb 11 '10 at 22:46
  • 1
    I used to have an MSN messenger plugin years ago that flashed the caps lock / scroll lock / etc light when I received a new IM. I typed a few times by mistake in all caps, and the plugin was quickly uninstalled. – alex Feb 11 '10 at 22:49
  • 5
    I can see it now...your program crashes unceremoniously and the caps lock LED gets inverted. Then a question everyone thinks is stupid is posted on SuperUser... – Restore the Data Dumps Feb 11 '10 at 22:51
  • 1
    The whole point of the app is to reassign the capslock key to something useful. People change it to an extra ctrl or whatever all the time. So if they've installed my app, they'll be expecting the lack of caps. – ck_ Feb 11 '10 at 22:56
  • So the MSN messenger plugin just turned capslock on/off? That would be annoying... Or did it actually just flash the LEDs? – ck_ Feb 11 '10 at 22:59
  • 3
    A System.Windows.Forms.NotifyIcon would be easier to code. Just change the Icon property to point to different icons when your program is on or off. – Chris R. Timmons Feb 11 '10 at 23:00
  • Yep, using that already as well as a balloontip. Thanks :) – ck_ Feb 11 '10 at 23:08
  • Any specific reason you cannot use Scroll or num lock instead of capslock? Capslock would be very misleading... – sam Feb 11 '10 at 23:16
  • Easier to access via homerow typing position. Scroll/Num lock will be options though. Do you guys seriously use capslock enough that you're second-guessing reassigning it? – ck_ Feb 11 '10 at 23:20
  • @cksubs I assumed it flashed the toggles for them - when they flashed, I would type LIKE THIS. – alex Feb 11 '10 at 23:33
  • 1
    @cksubs: The problem isn't how frequently one uses the caps lock, it's how infrequently we learn something new about it. The way we know what the caps lock does is because out little brains figured it out a reeeeeeeeeeeeal long time ago and it's been baked in for 10, 20...50 years or so. Changing what it does would be the equivalent of coming out with an add on for your car that (as a feature) reversed the direction of the steering wheel... – Restore the Data Dumps Feb 11 '10 at 23:50
  • 1
    It would be more like taking that one feature from your car radio that you never ever use, and making it do something that you'd use a lot more. But activating it via the same control. And you were the one that took a screwdriver to your radio, so you know the change was made. -- I'm not saying it's easy to break habits, but if the reward is high enough people will make the switch. Capslock isn't the steering wheel. – ck_ Feb 12 '10 at 00:41
  • 1
    Of course capslock isn't the steering wheel. It is... CRUISE CONTROL! (Sorry, couldn't resist.) – Ishmaeel Dec 06 '11 at 13:50
  • @cksubs consider that, in a German keyboard with capslock on, typing `1` yields `!`, and `shift+1` yields `1`; think about how automatic and hard-wired our use of the keyboard is, after years of "muscle-memory" training; now think about what happens in the brain of a German user when he is subconsciously aware that the caps light is on. :( – ANeves Jan 18 '12 at 10:43

3 Answers3

3

There is a plugin for Miranda IM named "Keyboard Notify Ext." which contains in its source code a C implementation of controlling LEDs. See file keyboard.c in the source code. Probably you can port it to C#.

Here are most interesting highlights from source code:

mir_snprintf(aux1, sizeof(aux1), "Kbd%d", i);
mir_snprintf(aux2, sizeof(aux2), "\\Device\\KeyboardClass%d", i);
DefineDosDevice(DDD_RAW_TARGET_PATH, aux1, aux2);

mir_snprintf(aux1, sizeof(aux1), "\\\\.\\Kbd%d", i);
hKbdDev[i] = CreateFile(aux1, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

...

result |= DeviceIoControl(hKbdDev[i], IOCTL_KEYBOARD_SET_INDICATORS, &InputBuffer, DataLength, NULL, 0, &ReturnedLength, NULL);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ivanzoid
  • 5,952
  • 2
  • 34
  • 43
2

I'm pretty sure you can't toggle the LED without toggling the actual caps lock, unless you were writing a keyboard driver. (I'm not recommending that!)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Scott P
  • 3,775
  • 1
  • 24
  • 30
  • He should build a hardware peripheral that ships with the software. A great big globe in a box, with label "TOGGLE" – alex Feb 11 '10 at 23:34
  • If you are booting your own OS and have direct write access to the port the keyboard is plugged in to... but in C#? I doubt it. – Daniel Coffman Feb 12 '10 at 00:30
0

A bit late to the party, but in case anyone else stumbles across this, here is a C# program flipping the Caps Lock LED. It is based on @ivanzoid's answer (and a lot more random Googling).

The basic procedure is

  • obtain a handle for the keyboard device
  • read the current LED status
  • flip the bit corresponding to the Caps Lock LED <-- this is the place where you could do other random things
  • write the status back to the keyboard
  • clean up
// to compile: powershell> C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc /out:capsblink.exe /target:exe capsblink.cs
//
// Following:
//  - original motivation: https://stackoverflow.com/questions/72679665/is-it-possible-to-control-capslock-light-without-actual-capslocking/72679984#72679984
//  - partial answer using C-API: https://stackoverflow.com/questions/2248358/way-to-turn-on-keyboards-caps-lock-light-without-actually-turning-on-caps-lock
//  - full version of the partial answer from above (in C, 1999): https://www.codeguru.com/windows/manipulating-the-keyboard-lights-in-windows-nt/
//  - calling C-API from C# via p/invoke: https://learn.microsoft.com/en-us/archive/msdn-magazine/2003/july/net-column-calling-win32-dlls-in-csharp-with-p-invoke
//  - Win32 API docs: https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol (and related)

using System.Runtime.InteropServices;
using System.ComponentModel;
using System;

class CapsLockLight
{
    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern Boolean DefineDosDevice(UInt32 flags, String deviceName, String targetPath);

    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern IntPtr CreateFile(String fileName,
                       UInt32 desiredAccess, UInt32 shareMode, IntPtr securityAttributes,
                       UInt32 creationDisposition, UInt32 flagsAndAttributes, IntPtr templateFile
                      );

    [StructLayout(LayoutKind.Sequential)]
    public struct KEYBOARD_INDICATOR_PARAMETERS
    {
        public UInt16 unitID;
        public UInt16 LEDflags;
    }

    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern Boolean DeviceIoControl(IntPtr device, UInt32 ioControlCode,
                          ref KEYBOARD_INDICATOR_PARAMETERS KIPin,  UInt32  inBufferSize,
                          ref KEYBOARD_INDICATOR_PARAMETERS KIPout, UInt32 outBufferSize,
                          ref UInt32 bytesReturned, IntPtr overlapped
                         );
    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern Boolean DeviceIoControl(IntPtr device, UInt32 ioControlCode,
                          IntPtr KIPin,  UInt32  inBufferSize,
                          ref KEYBOARD_INDICATOR_PARAMETERS KIPout, UInt32 outBufferSize,
                          ref UInt32 bytesReturned, IntPtr overlapped
                         );
    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern Boolean DeviceIoControl(IntPtr device, UInt32 ioControlCode,
                          ref KEYBOARD_INDICATOR_PARAMETERS KIPin,  UInt32  inBufferSize,
                          IntPtr KIPout, UInt32 outBufferSize,
                          ref UInt32 bytesReturned, IntPtr overlapped
                         );

    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern Boolean CloseHandle(IntPtr handle);

    static void Main(string[] args)
    {
        UInt32 bytesReturned = 0;
        IntPtr device;
        KEYBOARD_INDICATOR_PARAMETERS KIPbuf = new KEYBOARD_INDICATOR_PARAMETERS { unitID = 0, LEDflags = 0 };

        if(!DefineDosDevice(Flags.DDD_RAW_TARGET_PATH, "myKBD", "\\Device\\KeyboardClass0"))
        {
            Int32 err = Marshal.GetLastWin32Error();
            throw new Win32Exception(err);
        }

        // Console.WriteLine("Created device");

        device = CreateFile("\\\\.\\myKBD", Flags.GENERIC_WRITE, 0, IntPtr.Zero, Flags.OPEN_EXISTING, 0, IntPtr.Zero);
        if(device == Flags.INVALID_HANDLE_VALUE)
        {
            Int32 err = Marshal.GetLastWin32Error();
            throw new Win32Exception(err);
        }

        // Console.WriteLine("Opened device");

        if(!DeviceIoControl(device, Flags.IOCTL_KEYBOARD_QUERY_INDICATORS, IntPtr.Zero, 0, ref KIPbuf, (UInt32)Marshal.SizeOf(KIPbuf), ref bytesReturned, IntPtr.Zero))
        {
            Int32 err = Marshal.GetLastWin32Error();
            throw new Win32Exception(err);
        }

        // Console.WriteLine(String.Format("Read LED status: {0:x}", KIPbuf.LEDflags));

        KIPbuf.LEDflags = (UInt16)(KIPbuf.LEDflags ^ Flags.KEYBOARD_CAPS_LOCK_ON);

        // Console.WriteLine(String.Format("Changed LED status to: {0:x}", KIPbuf.LEDflags));

        if(!DeviceIoControl(device, Flags.IOCTL_KEYBOARD_SET_INDICATORS, ref KIPbuf, (UInt32)Marshal.SizeOf(KIPbuf), IntPtr.Zero, 0, ref bytesReturned, IntPtr.Zero))
        {
            Int32 err = Marshal.GetLastWin32Error();
            throw new Win32Exception(err);
        }

        // Console.WriteLine("Set new LED status");

        if(!CloseHandle(device))
        {
            Int32 err = Marshal.GetLastWin32Error();
            throw new Win32Exception(err);
        }

        // Console.WriteLine("Closed device handle");

        if(!DefineDosDevice(Flags.DDD_REMOVE_DEFINITION, "myKBD", null))
        {
            Int32 err = Marshal.GetLastWin32Error();
            throw new Win32Exception(err);
        }

        // Console.WriteLine("Removed device definition");
    }
};

class Flags
{
    public static IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1);
    public const UInt32 IOCTL_KEYBOARD_SET_INDICATORS   = (0x0000000b << 16) | (0 << 14) | (0x0002 << 2) | 0; // from ntddkbd.h, ntddk.h
    public const UInt32 IOCTL_KEYBOARD_QUERY_INDICATORS = (0x0000000b << 16) | (0 << 14) | (0x0010 << 2) | 0; // from ntddkbd.h, ntddk.h

    public const UInt32 DDD_RAW_TARGET_PATH       = 0x00000001;
    public const UInt32 DDD_REMOVE_DEFINITION     = 0x00000002;
    public const UInt32 DDD_EXACT_MATCH_ON_REMOVE = 0x00000004;
    public const UInt32 DDD_NO_BROADCAST_SYSTEM   = 0x00000008;

    public const UInt32 GENERIC_ALL     = 0x10000000;
    public const UInt32 GENERIC_EXECUTE = 0x20000000;
    public const UInt32 GENERIC_WRITE   = 0x40000000;
    public const UInt32 GENERIC_READ    = 0x80000000;

    public const UInt32 CREATE_NEW        = 1;
    public const UInt32 CREATE_ALWAYS     = 2;
    public const UInt32 OPEN_EXISTING     = 3;
    public const UInt32 OPEN_ALWAYS       = 4;
    public const UInt32 TRUNCATE_EXISTING = 5;

    public const UInt16 KEYBOARD_SCROLL_LOCK_ON = 1;
    public const UInt16 KEYBOARD_NUM_LOCK_ON    = 2;
    public const UInt16 KEYBOARD_CAPS_LOCK_ON   = 4;
    public const UInt16 KEYBOARD_SHADOW         = 0x4000;
    public const UInt16 KEYBOARD_LED_INJECTED   = 0x8000;
};