// FuckCapsLock — Permanently disable the vestigial Caps Lock key. // Copyright (c) 2026 Llama Chile Shop. MIT License. // // This application was pair-programmed by: // gramps@llamachile.shop (senior developer, direct supervision) // opencode (AI pair programmer) // // Under direct, senior supervision, paired programming can be an // effective and efficient tool. // // Installs a low-level keyboard hook (WH_KEYBOARD_LL) that swallows // VK_CAPITAL (0x14) before it reaches any window. Runs in the system // tray until the user exits via the context menu. // // System calls reference: // SetWindowsHookEx / UnhookWindowsHookEx / CallNextHookEx — user32.dll // GetModuleHandle — kernel32.dll // Registry.CurrentUser\...\Run — auto-start on boot using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Win32; static class Program { /// Low-level keyboard hook identifier — intercepts keys before any window. const int WH_KEYBOARD_LL = 13; /// Virtual-key code for Caps Lock — the only key this app blocks. const int VK_CAPITAL = 0x14; /// Callback signature for LowLevelKeyboardProc used by SetWindowsHookEx. /// Hook code — >= 0 means the hook should process the message. /// Message type (WM_KEYDOWN, WM_KEYUP, etc.). /// Pointer to a KBDLLHOOKSTRUCT with the raw key data. /// 1 to swallow the key, or CallNextHookEx to let it through. delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam); /// Installs a low-level keyboard hook into the global hook chain. /// Hook type — WH_KEYBOARD_LL (13) for keyboard events. /// Pointer to the callback function. /// Module handle for the current process. /// 0 = hook applies globally to all threads in this desktop. /// Hook handle on success, IntPtr.Zero on failure. [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId); /// Removes a previously installed hook from the hook chain. /// Hook handle returned by SetWindowsHookEx. /// True on success. [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool UnhookWindowsHookEx(IntPtr hhk); /// Passes the hook event to the next hook in the chain. [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); /// Retrieves the module handle for the calling process — required by SetWindowsHookEx. [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern IntPtr GetModuleHandle(string lpModuleName); /// Structure deserialized from the lParam pointer in the hook callback. [StructLayout(LayoutKind.Sequential)] struct KBDLLHOOKSTRUCT { public uint vkCode; public uint scanCode; public uint flags; public uint time; public IntPtr dwExtraInfo; } /// Cached delegate — prevents garbage collection of the callback. static LowLevelKeyboardProc _proc = HookCallback; /// Active hook handle returned by SetWindowsHookEx. static IntPtr _hookId = IntPtr.Zero; /// System tray icon — visible while the app is running. static NotifyIcon? _trayIcon; /// /// Entry point. Registers auto-start, enforces a single instance via a /// named mutex, installs the keyboard hook, and runs the WinForms message /// loop. The app lives in the system tray until Exit is selected. /// [STAThread] static void Main() { RegisterStartup(); // Named mutex prevents multiple instances — second launch exits silently. using var mutex = new Mutex(true, @"Global\FuckCapsLock", out var createdNew); if (!createdNew) return; // Install the low-level hook; bail if it fails (e.g., insufficient permissions). _hookId = SetHook(_proc); if (_hookId == IntPtr.Zero) return; ApplicationConfiguration.Initialize(); // Hidden window — required to pump messages and keep the hook alive. using var form = new Form(); form.WindowState = FormWindowState.Minimized; form.ShowInTaskbar = false; form.Load += (_, _) => form.Hide(); // Tray icon with Exit context menu to cleanly unhook and quit. _trayIcon = new NotifyIcon { Icon = SystemIcons.Application, Text = "FuckCapsLock", ContextMenuStrip = new ContextMenuStrip() }; _trayIcon.ContextMenuStrip.Items.Add("Exit", null, (_, _) => { _trayIcon.Visible = false; Application.Exit(); }); _trayIcon.Visible = true; // Blocks until the form closes (Exit is clicked or process is killed). Application.Run(form); // Remove the hook when the app exits. UnhookWindowsHookEx(_hookId); } /// /// Writes the current executable path to HKCU\Software\Microsoft\Windows\ /// CurrentVersion\Run so the app auto-starts on login. Silently ignores /// errors (no admin rights needed for HKCU). /// static void RegisterStartup() { try { using var key = Registry.CurrentUser.OpenSubKey( @"Software\Microsoft\Windows\CurrentVersion\Run", true); if (key is null) return; var path = Environment.ProcessPath; if (path is null) return; // Quote the path if it contains spaces (standard Windows practice). var value = path.Contains(' ') ? $"\"{path}\"" : path; // Only write if the path has changed — avoids unnecessary registry churn. var existing = key.GetValue("FuckCapsLock") as string; if (!string.Equals(existing, value, StringComparison.OrdinalIgnoreCase)) key.SetValue("FuckCapsLock", value); } catch { } } /// /// Installs the low-level keyboard hook using SetWindowsHookEx. /// /// Delegate to the callback that processes each key event. /// Hook handle or IntPtr.Zero if installation failed. static IntPtr SetHook(LowLevelKeyboardProc proc) { using var curProcess = Process.GetCurrentProcess(); using var curModule = curProcess.MainModule; return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule!.ModuleName), 0); } /// /// Hook callback invoked for every keyboard event. If the key is VK_CAPITAL (Caps Lock), /// the event is swallowed by returning 1 without calling CallNextHookEx. All other keys /// pass through normally. /// /// >= 0 means the hook should process the event. /// Window message type (WM_KEYDOWN, WM_SYSKEYUP, etc.). /// Pointer to a KBDLLHOOKSTRUCT with the raw key data. /// 1 if Caps Lock was blocked; otherwise the result of CallNextHookEx. static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode >= 0) { var hookStruct = Marshal.PtrToStructure(lParam); if (hookStruct.vkCode == VK_CAPITAL) return (IntPtr)1; // Swallow the key — never forward it. } return CallNextHookEx(_hookId, nCode, wParam, lParam); } }