diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8af8f32 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# ignore cmake output directories +/out*/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5409390 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright jdknight + +cmake_minimum_required(VERSION 3.11) +project(capsblock) + +set(BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(INC_DIR ${BASE_DIR}/src) +set(SRC_DIR ${BASE_DIR}/src/capsblock) +include(${BASE_DIR}/support.cmake) + +include(${BASE_DIR}/sources.cmake) +include_directories(${INC_DIR}) + +add_executable(capsblock WIN32 ${CAPSBLOCK_SRCS}) +install (TARGETS capsblock RUNTIME DESTINATION bin) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e009607 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright jdknight + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ec7387c --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Caps Block + +## Overview + +Provides the ability in Windows to help force the Caps Lock key in a toggled +off state while still allowing programs to use the key. + +This application was created with the goal of supporting the Caps Lock key +as a voice key for various voice-supporting applications. Users can use the +key to trigger a voice event, but also reduce the chance that the Caps Lock +key is left enabled. diff --git a/scripts/.gitignore b/scripts/.gitignore new file mode 100644 index 0000000..f2a921f --- /dev/null +++ b/scripts/.gitignore @@ -0,0 +1,2 @@ +# ignore built executable if dropped here +capsblock.exe diff --git a/scripts/install.bat b/scripts/install.bat new file mode 100644 index 0000000..673bd3a --- /dev/null +++ b/scripts/install.bat @@ -0,0 +1,16 @@ +@echo off +setlocal enabledelayedexpansion + +set script_path=%~dp0 +set base_path=%script_path:~0,-1% +set exe_path="%base_path%\capsblock.exe" + +if exist %exe_path% ( + reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Run /t REG_SZ ^ + /v "Caps Block" /d "%exe_path%" /f >NUL + if !ERRORLEVEL! neq 0 exit /b !ERRORLEVEL! + + echo Caps Block has been configured to startup. +) else ( + echo Caps Block executable is missing! +) diff --git a/scripts/shutdown.bat b/scripts/shutdown.bat new file mode 100644 index 0000000..f800425 --- /dev/null +++ b/scripts/shutdown.bat @@ -0,0 +1,2 @@ +@echo off +taskkill /f /im capsblock.exe diff --git a/scripts/uninstall.bat b/scripts/uninstall.bat new file mode 100644 index 0000000..4e0b71e --- /dev/null +++ b/scripts/uninstall.bat @@ -0,0 +1,3 @@ +@echo off +reg delete HKCU\Software\Microsoft\Windows\CurrentVersion\Run ^ + /v "Caps Block" /f >NUL diff --git a/sources.cmake b/sources.cmake new file mode 100644 index 0000000..0097bec --- /dev/null +++ b/sources.cmake @@ -0,0 +1,6 @@ +# Copyright jdknight + +set(CAPSBLOCK_SRCS + ${SRC_DIR}/capsblock.rc + ${SRC_DIR}/main.c +) diff --git a/src/capsblock/capsblock.rc b/src/capsblock/capsblock.rc new file mode 100644 index 0000000..09fef62 --- /dev/null +++ b/src/capsblock/capsblock.rc @@ -0,0 +1,21 @@ +// Copyright jdknight + +#include "winres.h" + +VS_VERSION_INFO VERSIONINFO +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "jdknight" + VALUE "FileDescription", "Caps Block" + VALUE "LegalCopyright", "Copyright (C) jdknight" + VALUE "ProductName", "Caps Block" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/capsblock/main.c b/src/capsblock/main.c new file mode 100644 index 0000000..c690331 --- /dev/null +++ b/src/capsblock/main.c @@ -0,0 +1,68 @@ +// Copyright jdknight + +#include +#include +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include + +// state to suppress hook events when generating new caps-lock events +volatile bool gSkip = false; + +// invoked when a low-level keyboard event occurs +LRESULT CALLBACK LowLevelKeyboardProc(int code, WPARAM msg, LPARAM ctx) +{ + // listen for key-up events of the caps-lock key + if (!gSkip && !code >= 0 && msg == WM_KEYUP) { + KBDLLHOOKSTRUCT* kbd = (KBDLLHOOKSTRUCT*)ctx; + if (kbd->vkCode == VK_CAPITAL) { + // check if we have caps-lock enabled in our current state + bool hasCapsLock = ((GetKeyState(VK_CAPITAL) & 0x0001) != 0); + if (hasCapsLock) { + // before we attempt to toggle off the caps-lock state, + // flag our hook as disabled, to ensure we do not attempt + // to process the state of any caps-lock keys issued from + // our hook + gSkip = true; + + // generate two additional key events: + /// + // 1) a key-up event to "complete" the toggled on state + // 2) a key-down event to trigger a new caps-lock event + // + // do not consume this active event, to complete the final + // key-up event + INPUT input[2] = {0}; + input[0].type = input[1].type = INPUT_KEYBOARD; + input[0].ki.wVk = input[1].ki.wVk = VK_CAPITAL; + input[0].ki.dwFlags = KEYEVENTF_KEYUP; + SendInput(2, input, sizeof(INPUT)); + + // after issuing our key changes, re-enable this hook + gSkip = false; + } + } + } + + return CallNextHookEx(NULL, code, msg, ctx); +} + +#pragma warning(disable: 4100) +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + PWSTR pCmdLine, int nCmdShow) +{ + // create a hook to listen for keyboard events + HHOOK hook = SetWindowsHookEx( + WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), 0); + if (!hook) { + fprintf(stderr, "unable to create keyboard hook\n"); + return 1; + } + + // consume all messages + MSG msg; + while(GetMessage(&msg, NULL, 0, 0) != 0); + + return 0; +} diff --git a/support.cmake b/support.cmake new file mode 100644 index 0000000..c618fb8 --- /dev/null +++ b/support.cmake @@ -0,0 +1,13 @@ +# Copyright jdknight + +# targeting c11 +set(CMAKE_C_STANDARD 11) + +# force c support (ensures MSVC has been processed) +enable_language(C) + +# enable warnings +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W4") + +# force unicode +add_definitions(-DUNICODE -D_UNICODE)