diff --git a/Program.cs b/Program.cs
index 9d44a5c..150d7eb 100644
--- a/Program.cs
+++ b/Program.cs
@@ -1,27 +1,66 @@
-using System.Diagnostics;
+// 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
{
@@ -32,30 +71,44 @@ static class Program
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,
@@ -69,11 +122,18 @@ static class Program
});
_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
@@ -85,8 +145,10 @@ static class Program
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);
@@ -94,6 +156,11 @@ static class Program
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();
@@ -102,13 +169,22 @@ static class Program
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;
+ return (IntPtr)1; // Swallow the key — never forward it.
}
return CallNextHookEx(_hookId, nCode, wParam, lParam);
}