diff --git a/README.md b/README.md index 496949a..5b54106 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,36 @@ -recaps -====== +Recaps 0.6 +------------------------------------ -Using CapsLock to switch the keyboard language +This simple program allows you to switch between languages using the mostly-unused CapsLock key instead of the clumsy Alt+Shift combination. + +Just press CapsLock and the language will change. You can use Alt-CapsLock if you still need the old CapsLock action - to write a lot of text in capital letters. + +If you accidentally typed some text in the wrong layout, Ctrl-Capslock should fix it. + +If you have more than two languages installed, you can select the ones you want to cycle through if you right click the application's icon. + +Enjoy! + +Eli Golovinsky +http://www.gooli.org/ + + +Revision history +------------------------------------ +0.6 Added conversion of text typed with the wrong layout + using Ctrl-CapsLock. Alt-CapsLock now changes the old + CapsLock mode. + +0.5 Fixed selected languages configuration not being saved + in the registry. + +0.4 Fixed language not being switched in some applications. + +0.3 Added a tray icon, a menu and support for more than two + languages. Recaps now cycles between the lanugages selected + in the menu. + +0.2 Changed Shift-CapsLock to change language as well as CapsLock. + Ctrl-CapsLock now changes the old CapsLock mode. + +0.1 Initial release diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..61cba2f --- /dev/null +++ b/build.bat @@ -0,0 +1,37 @@ +@echo off + +set FILENAME=recaps-0.6 + +:: clean up +if exist Output\*.* del /q Output\*.* +if exist Release\nul rd /s/q Release +if exist src\nul rd /s/q src + +:: build binaries +devenv recaps.2005.sln /rebuild Release +if errorlevel 1 goto error + +:: build binary distribution using Inno Setup +iscc /F%FILENAME%-setup setup.iss +if errorlevel 1 goto error + +:: copy standalone exe +copy Release\recaps.exe Output\%FILENAME%.exe +if errorlevel 1 goto error + +:: build source distribution using 7zip +mkdir src +copy *.* src +del src\*.ncb +del src\*.aps +del src\*.user +7z -tzip a Output\%FILENAME%-src.zip src/* +if errorlevel 1 goto error +rd /s/q src + +goto end + +:error +pause + +:end diff --git a/fixlayouts.cpp b/fixlayouts.cpp new file mode 100644 index 0000000..640075a --- /dev/null +++ b/fixlayouts.cpp @@ -0,0 +1,311 @@ +#include "stdafx.h" +#include "fixlayouts.h" +#include "utils.h" + +#define HKL_HEBREW (HKL)0x040D040D +#define HKL_ENGLISH (HKL)0x04090409 + +/////////////////////////////////////////////////////////////////////////////// +// Converts the text in the active window from one keyboard layout to another +// using the clipboard. +void ConvertSelectedTextInActiveWindow(HKL hklSource, HKL hklTarget) +{ + WCHAR* sourceText = NULL; + WCHAR* targetText = NULL; + const WCHAR dummy [] = L"__RECAPS__"; + + // store previous clipboard data and set clipboard to dummy string + ClipboardData prevClipboardData; + StoreClipboardData(&prevClipboardData); + + SetClipboardText(dummy); + + // copy the selected text by simulating Ctrl-C + SendKeyCombo(VK_CONTROL, 'C', FALSE); + + // wait until copy operation completes and get the copied data from the clipboard + // this loop has the nice side effect of setting copyOK to FALSE if there's no + // selected text, since nothing is copied and the clipboard still contains the + // contents of `dummy`. + BOOL copyOK = FALSE; + for(int i = 0; i < 10; i++) + { + sourceText = GetClipboardText(); + if (sourceText && wcscmp(sourceText, dummy) != 0) + { + copyOK = TRUE; + break; + } + else + { + free(sourceText); + Sleep(30); + } + } + + if (copyOK) + { + // if the string only matches one particular layout, use it + // otherwise use the provided layout + int matches = 0; + HKL hklDetected = DetectLayoutFromString(sourceText, &matches); + if (matches == 1) + hklSource = hklDetected; + + // convert the text between layouts + size_t length = wcslen(sourceText); + targetText = (WCHAR*)malloc(sizeof(WCHAR) * (length+1)); + size_t converted = LayoutConvertString(sourceText, targetText, length+1, hklSource, hklTarget); + + if (converted) + { + // put the converted string on the clipboard + if (SetClipboardText(targetText)) + { + // simulate Ctrl-V to paste the text, replacing the previous text + SendKeyCombo(VK_CONTROL, 'V', FALSE); + + // let the application complete pasting before putting the old data back on the clipboard + Sleep(REMOTE_APP_WAIT); + } + } + + // restore the original clipboard data + RestoreClipboardData(&prevClipboardData); + FreeClipboardData(&prevClipboardData); + + // free allocated memory + free(sourceText); + free(targetText); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Converts a character from one keyboard layout to another +WCHAR LayoutConvertChar(WCHAR ch, HKL hklSource, HKL hklTarget) +{ + // special handling for some ambivalent characters in hebrew layout + if (hklSource == HKL_HEBREW && hklTarget == HKL_ENGLISH) + { + switch (ch) + { + case L'.': return L'/'; + case L'/': return L'q'; + case L'\'': return L'w'; + case L',': return L'\''; + } + } + else if (hklSource == HKL_ENGLISH && hklTarget == HKL_HEBREW) + { + switch (ch) + { + case L'/': return L'.'; + case L'q': return L'/'; + case L'w': return L'\''; + case L'\'': return L','; + } + } + + // get the virtual key code and the shift state using the character and the source keyboard layout + SHORT vkAndShift = VkKeyScanEx(ch, hklSource); + if (vkAndShift == -1) + return 0; // no such char in source keyboard layout + + BYTE vk = LOBYTE(vkAndShift); + BYTE shift = HIBYTE(vkAndShift); + + // convert the shift state returned from VkKeyScanEx to an array that represents the + // key state usable with ToUnicodeEx that we'll be calling next + BYTE keyState[256] = {0}; + if (shift & 1) keyState[VK_SHIFT] = 0x80; // turn on high bit + if (shift & 2) keyState[VK_CONTROL] = 0x80; + if (shift & 4) keyState[VK_MENU] = 0x80; + + // convert virtual key and key state to a new character using the target keyboard layout + WCHAR buffer[10] = {0}; + int result = ToUnicodeEx(vk, 0, keyState, buffer, 10, 0, hklTarget); + + // result can be more than 1 if the character in the source layout is represented by + // several characters in the target layout, but we ignore this to simplify the function. + if (result == 1) + return buffer[0]; + + // conversion failed for some reason + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// Converts a string from one keyboard layout to another +size_t LayoutConvertString(const WCHAR* str, WCHAR* buffer, size_t size, HKL hklSource, HKL hklTarget) +{ + size_t i; + for (i = 0; i < wcslen(str) && i < size-1; i++) + { + WCHAR ch = LayoutConvertChar(str[i], hklSource, hklTarget); + if (ch == 0) + return 0; + buffer[i] = ch; + } + buffer[i] = '\0'; + return i; +} + +/////////////////////////////////////////////////////////////////////////////// +// Goes through all the installed keyboard layouts and returns a layout that +// can generate the string. If not matching layout is found, returns NULL. +// If `multiple` isn't NULL it will be set to the number of matched layouts. +HKL DetectLayoutFromString(const WCHAR* str, int* pmatches) +{ + HKL result = NULL; + HKL* hkls; + UINT layoutCount; + layoutCount = GetKeyboardLayoutList(0, NULL); + hkls = (HKL*)malloc(sizeof(HKL) * layoutCount); + GetKeyboardLayoutList(layoutCount, hkls); + + int matches = 0; + for (size_t layout = 0; layout < layoutCount; layout++) + { + BOOL validLayout = TRUE; + for (size_t i = 0; i < wcslen(str); i++) + { + UINT vk = VkKeyScanEx(str[i], hkls[layout]); + if (vk == -1) + { + validLayout = FALSE; + break; + } + } + if (validLayout) + { + matches++; + if (!result) + result = hkls[layout]; + } + } + + if (pmatches) + *pmatches = matches; + + return result; +} + +/////////////////////////////////////////////////////////////////////////////// +// Stores the clipboard data in all its formats in `formats`. +// You must call FreeAllClipboardData on `formats` when it's no longer needed. +BOOL StoreClipboardData(ClipboardData* formats) +{ + if (OpenClipboard(NULL)) + { + formats->count = CountClipboardFormats(); + formats->dataArray = (ClipboardFormat*)malloc(sizeof(ClipboardData) * formats->count); + ZeroMemory(formats->dataArray, sizeof(ClipboardData) * formats->count); + int i = 0; + UINT format = EnumClipboardFormats(0); + while (format) + { + HANDLE dataHandle = GetClipboardData(format); + LPVOID source = GlobalLock(dataHandle); + size_t size = GlobalSize(dataHandle); + formats->dataArray[i].format = format; + formats->dataArray[i].dataHandle = GlobalAlloc(GHND, size); + LPVOID dest = GlobalLock(formats->dataArray[i].dataHandle); + CopyMemory(dest, source, size); + GlobalUnlock(formats->dataArray[i].dataHandle); + GlobalUnlock(dataHandle); + + // next format + format = EnumClipboardFormats(format); + i++; + } + CloseClipboard(); + return TRUE; + } + return FALSE; +} + +/////////////////////////////////////////////////////////////////////////////// +// Restores the data in the clipboard from `formats` that was generated by +// StoreClipboardData. +BOOL RestoreClipboardData(const ClipboardData* formats) +{ + if (OpenClipboard(NULL)) + { + EmptyClipboard(); + for (int i = 0; i < formats->count; i++) + { + SetClipboardData(formats->dataArray[i].format, formats->dataArray[i].dataHandle); + } + CloseClipboard(); + return TRUE; + } + return FALSE; +} + +/////////////////////////////////////////////////////////////////////////////// +// Frees `formats` allocated by StoreClipboardData +void FreeClipboardData(ClipboardData* formats) +{ + free(formats->dataArray); +} + +/////////////////////////////////////////////////////////////////////////////// +// Gets unicode text from the clipboard. +// You must free the returned string when you don't need it anymore. +WCHAR* GetClipboardText() +{ + WCHAR* text = NULL; + if (OpenClipboard(NULL)) + { + HANDLE handle = GetClipboardData(CF_UNICODETEXT); + WCHAR* clipboardText = (WCHAR*)GlobalLock(handle); + if (!clipboardText) return NULL; + size_t size = sizeof(WCHAR) * (wcslen(clipboardText)+1); + text = (WCHAR*)malloc(size); + if (!text) return NULL; + memcpy(text, clipboardText, size); + CloseClipboard(); + } + return text; +} + +/////////////////////////////////////////////////////////////////////////////// +// Puts unicode text on the clipboard +BOOL SetClipboardText(const WCHAR* text) +{ + if (OpenClipboard(NULL)) + { + size_t size = sizeof(WCHAR) * (wcslen(text)+1); + HANDLE handle = GlobalAlloc(GHND, size); + WCHAR* clipboardText = (WCHAR*)GlobalLock(handle); + memcpy(clipboardText, text, size); + GlobalUnlock(handle); + EmptyClipboard(); + SetClipboardData(CF_UNICODETEXT, handle); + CloseClipboard(); + return TRUE; + } + return FALSE; +} + +/////////////////////////////////////////////////////////////////////////////// +// Simulates a key press in the active window +void SendKey(BYTE vk, BOOL extended) +{ + keybd_event(vk, 0, extended ? KEYEVENTF_EXTENDEDKEY : 0, 0); + keybd_event(vk, 0, KEYEVENTF_KEYUP | (extended ? KEYEVENTF_EXTENDEDKEY : 0), 0); +} + +/////////////////////////////////////////////////////////////////////////////// +// Simulates a key combination (such as Ctrl+X) in the active window +void SendKeyCombo(BYTE vkModifier, BYTE vk, BOOL extended) +{ + BOOL modPressed = (GetKeyState(vkModifier) & 0x80000000) > 0; + if (!modPressed) + keybd_event(vkModifier, 0, 0, 0); + keybd_event(vk, 0, extended ? KEYEVENTF_EXTENDEDKEY : 0, 0); + if (!modPressed) + keybd_event(vkModifier, 0, KEYEVENTF_KEYUP, 0); + keybd_event(vk, 0, KEYEVENTF_KEYUP | (extended ? KEYEVENTF_EXTENDEDKEY : 0), 0); +} + diff --git a/fixlayouts.h b/fixlayouts.h new file mode 100644 index 0000000..c5bdc3f --- /dev/null +++ b/fixlayouts.h @@ -0,0 +1,38 @@ +struct ClipboardFormat +{ + UINT format; + HANDLE dataHandle; +}; + +struct ClipboardData +{ + int count; + ClipboardFormat* dataArray; +}; + + +// time in milliseconds to allow the target application +// to execute commands simmulated by keystrokes +#define REMOTE_APP_WAIT 20 + +// The main function that converts the current selected text in the active +// window from one layout to another. +void ConvertSelectedTextInActiveWindow(HKL hklSource, HKL hklTarget); + +// Functions to convert UNICODE strings between keyboard layouts +WCHAR LayoutConvertChar(WCHAR ch, HKL hklSource, HKL hklTarget); +size_t LayoutConvertString(const WCHAR* str, WCHAR* buffer, size_t size, HKL hklSource, HKL hklTarget); +HKL DetectLayoutFromString(const WCHAR* str, BOOL* pmatches); + +// Functions to store and restoe all of the data in the clipboard +BOOL StoreClipboardData(ClipboardData* formats); +BOOL RestoreClipboardData(const ClipboardData* formats); +void FreeClipboardData(ClipboardData* formats); + +// Convenience functions for the clipboard +WCHAR* GetClipboardText(); +BOOL SetClipboardText(const WCHAR* text); + +// Functions that simulate key presses in the current window +void SendKey(BYTE vk, BOOL extended); +void SendKeyCombo(BYTE vkModifier, BYTE vk, BOOL extended); diff --git a/recaps.2005.sln b/recaps.2005.sln new file mode 100644 index 0000000..f7c12e9 --- /dev/null +++ b/recaps.2005.sln @@ -0,0 +1,19 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "recaps", "recaps.2005.vcproj", "{A6546042-77AD-46B0-B4B4-63641E29810E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A6546042-77AD-46B0-B4B4-63641E29810E}.Debug|Win32.ActiveCfg = Debug|Win32 + {A6546042-77AD-46B0-B4B4-63641E29810E}.Debug|Win32.Build.0 = Debug|Win32 + {A6546042-77AD-46B0-B4B4-63641E29810E}.Release|Win32.ActiveCfg = Release|Win32 + {A6546042-77AD-46B0-B4B4-63641E29810E}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/recaps.2005.vcproj b/recaps.2005.vcproj new file mode 100644 index 0000000..7e51649 --- /dev/null +++ b/recaps.2005.vcproj @@ -0,0 +1,316 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/recaps.cpp b/recaps.cpp new file mode 100644 index 0000000..6a89dab --- /dev/null +++ b/recaps.cpp @@ -0,0 +1,453 @@ +/****************************************************************************** + +Recaps - change language and keyboard layout using the CapsLock key. +Copyright (C) 2007 Eli Golovinsky + +------------------------------------------------------------------------------- + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*******************************************************************************/ + +#include "stdafx.h" +#include "resource.h" +#include "trayicon.h" +#include "fixlayouts.h" + +#define HELP_MESSAGE \ + L"Recaps allows you to quickly switch the current\r\n"\ + L"language using the Capslock key.\r\n"\ + L"\r\n"\ + L"Capslock changes the current keyboard laguange.\r\n"\ + L"Ctrl-Capslock fixes text you typed in the wrong laguange.\r\n"\ + L"Alt-Capslock is the old Capslock that lets you type in CAPITAL.\r\n"\ + L"\r\n"\ + L"http://www.gooli.org/blog/recaps\r\n\r\n"\ + L"Eli Golovinsky, Israel 2008\r\n" + +#define HELP_TITLE \ + L"Recaps 0.6 - Retake your Capslock!" + +// Tray icon constants +#define ID_TRAYICON 1 +#define APPWM_TRAYICON WM_APP +#define APPWM_NOP WM_APP + 1 + +// Our commands +#define ID_ABOUT 2000 +#define ID_EXIT 2001 +#define ID_LANG 2002 + +// Hook stuff +#define WH_KEYBOARD_LL 13 +#define LLKHF_INJECTED 0x10 +typedef struct { + DWORD vkCode; + DWORD scanCode; + DWORD flags; + DWORD time; + ULONG_PTR dwExtraInfo; +} KBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT; + +// General constants +#define MAXLEN 1024 +#define MAX_LAYOUTS 256 +#define MUTEX L"recaps-D3E743A3-E0F9-47f5-956A-CD15C6548789" +#define WINDOWCLASS_NAME L"RECAPS" +#define TITLE L"Recaps" + +struct KeyboardLayoutInfo +{ + WCHAR names[MAX_LAYOUTS][MAXLEN]; + HKL hkls[MAX_LAYOUTS]; + UINT menuIds[MAX_LAYOUTS]; + BOOL inUse[MAX_LAYOUTS]; + UINT count; + UINT current; +}; + +KeyboardLayoutInfo g_keyboardInfo = {0}; +BOOL g_modalShown = FALSE; +HHOOK g_hHook = NULL; + +// Prototypes +void ShowError(const WCHAR* message); +void PrintDebugString(const char* format, ...); + +LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +int OnTrayIcon(HWND hWnd, WPARAM wParam, LPARAM lParam); +int OnCommand(HWND hWnd, WORD wID, HWND hCtl); +BOOL ShowPopupMenu(HWND hWnd); + +void GetKeyboardLayouts(KeyboardLayoutInfo* info); +void LoadConfiguration(KeyboardLayoutInfo* info); +void SaveConfiguration(const KeyboardLayoutInfo* info); + +HWND RemoteGetFocus(); +HKL SwitchLayout(); +LRESULT CALLBACK LowLevelHookProc(int nCode, WPARAM wParam, LPARAM lParam); + +/////////////////////////////////////////////////////////////////////////////// +// Program's entry point +int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) +{ + UNREFERENCED_PARAMETER(hInstance); + UNREFERENCED_PARAMETER(hPrevInstance); + UNREFERENCED_PARAMETER(lpCmdLine); + UNREFERENCED_PARAMETER(nCmdShow); + + // Prevent from two copies of Recaps from running at the same time + HANDLE mutex = CreateMutex(NULL, FALSE, MUTEX); + DWORD result = WaitForSingleObject(mutex, 0); + if (result == WAIT_TIMEOUT) + { + MessageBox(NULL, L"Recaps is already running.", L"Recaps", MB_OK | MB_ICONINFORMATION); + return 1; + } + + // Create a fake window to listen to events + WNDCLASSEX wclx = {0}; + wclx.cbSize = sizeof( wclx ); + wclx.lpfnWndProc = &WindowProc; + wclx.hInstance = hInstance; + wclx.lpszClassName = WINDOWCLASS_NAME; + RegisterClassEx(&wclx); + HWND hMessageWindow = CreateWindow(WINDOWCLASS_NAME, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hInstance, 0); + + // Set hook to capture CapsLock + g_hHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelHookProc, GetModuleHandle(NULL), 0); + + // Initialize + AddTrayIcon(hMessageWindow, 0, APPWM_TRAYICON, IDI_MAINFRAME, TITLE); + GetKeyboardLayouts(&g_keyboardInfo); + LoadConfiguration(&g_keyboardInfo); + + // Handle messages + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + // Clean up + RemoveTrayIcon(hMessageWindow, 0); + UnregisterClass(WINDOWCLASS_NAME, hInstance); + DestroyWindow(hMessageWindow); + SaveConfiguration(&g_keyboardInfo); + UnhookWindowsHookEx(g_hHook); + + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// Handles events at the window (both hot key and from the tray icon) +LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case APPWM_TRAYICON: + return OnTrayIcon(hWnd, wParam, lParam); + + case WM_COMMAND: + return OnCommand(hWnd, LOWORD(wParam), (HWND)lParam); + + case WM_CLOSE: + PostQuitMessage(0); + return 0; + + default: + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Create and display a popup menu when the user right-clicks on the icon +int OnTrayIcon(HWND hWnd, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(wParam); + + if (g_modalShown == TRUE) + return 0; + + switch (lParam) + { + case WM_RBUTTONUP: + // Show the context menu + ShowPopupMenu(hWnd); + return 0; + } + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// Handles user commands from the menu +int OnCommand(HWND hWnd, WORD wID, HWND hCtl) +{ + UNREFERENCED_PARAMETER(hCtl); + + // Have a look at the command and act accordingly + if (wID == ID_EXIT) + { + PostMessage( hWnd, WM_CLOSE, 0, 0 ); + } + else if (wID == ID_ABOUT) + { + MessageBox(NULL, HELP_MESSAGE, HELP_TITLE, MB_OK | MB_ICONINFORMATION); + } + else + { + for (UINT i = 0; i < g_keyboardInfo.count; i++) + { + if (g_keyboardInfo.menuIds[i] == wID) + { + g_keyboardInfo.inUse[i] = ! (g_keyboardInfo.inUse[i]); + break; + } + } + SaveConfiguration(&g_keyboardInfo); + } + + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// Create and display a popup menu when the user right-clicks on the icon +BOOL ShowPopupMenu(HWND hWnd) +{ + HMENU hPop = NULL; + int i = 0; + BOOL cmd; + POINT curpos; + + // Create the menu + hPop = CreatePopupMenu(); + InsertMenu(hPop, i++, MF_BYPOSITION | MF_STRING, ID_ABOUT, L"Help..."); + InsertMenu(hPop, i++, MF_SEPARATOR, 0, NULL); + + // Add items for the languages + for (UINT layout = 0; layout < g_keyboardInfo.count; layout++) + { + UINT flags = MF_BYPOSITION | MF_STRING; + if (g_keyboardInfo.inUse[layout]) + flags |= MF_CHECKED; + InsertMenu(hPop, i++, flags, ID_LANG+layout, g_keyboardInfo.names[layout]); + g_keyboardInfo.menuIds[layout] = ID_LANG+layout; + } + + InsertMenu(hPop, i++, MF_SEPARATOR, 0, NULL); + InsertMenu(hPop, i++, MF_BYPOSITION | MF_STRING, ID_EXIT, L"Exit"); + + // Show the menu + + // See http://support.microsoft.com/kb/135788 for the reasons + // for the SetForegroundWindow and Post Message trick. + GetCursorPos(&curpos); + SetForegroundWindow(hWnd); + g_modalShown = TRUE; + cmd = (WORD)TrackPopupMenu( + hPop, TPM_LEFTALIGN|TPM_RIGHTBUTTON|TPM_RETURNCMD|TPM_NONOTIFY, + curpos.x, curpos.y, 0, hWnd, NULL + ); + PostMessage(hWnd, WM_NULL, 0, 0); + g_modalShown = FALSE; + + // Send a command message to the window to handle the menu item the user chose + if (cmd) + SendMessage(hWnd, WM_COMMAND, cmd, 0); + + DestroyMenu(hPop); + + return cmd; +} + +/////////////////////////////////////////////////////////////////////////////// +// Fills ``info`` with the currently installed keyboard layouts +// Based on http://blogs.msdn.com/michkap/archive/2004/12/05/275231.aspx. +void GetKeyboardLayouts(KeyboardLayoutInfo* info) +{ + memset(info, 0 ,sizeof(KeyboardLayoutInfo)); + info->count = GetKeyboardLayoutList(MAX_LAYOUTS, info->hkls); + for(UINT i = 0; i < info->count; i++) + { + LANGID language = (LANGID)(((UINT)info->hkls[i]) & 0x0000FFFF); // bottom 16 bit of HKL + LCID locale = MAKELCID(language, SORT_DEFAULT); + GetLocaleInfo(locale, LOCALE_SLANGUAGE, info->names[i], MAXLEN); + info->inUse[i] = TRUE; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Load currently active keyboard layouts from the registry +void LoadConfiguration(KeyboardLayoutInfo* info) +{ + HKEY hkey; + LONG result; + + result = RegOpenKey(HKEY_CURRENT_USER, L"Software\\Recaps", &hkey); + + // Load current isUse value for each language + if (result == ERROR_SUCCESS) + { + for (UINT i = 0; i < info->count; i++) + { + DWORD data = 0; + DWORD length = sizeof(DWORD); + result = RegQueryValueEx(hkey, info->names[i], 0, NULL, (BYTE*)(&data), &length); + if (result == ERROR_SUCCESS) + { + info->inUse[i] = (BOOL)data; + } + } + } + + RegCloseKey(hkey); +} + +/////////////////////////////////////////////////////////////////////////////// +// Saves currently active keyboard layouts to the registry +void SaveConfiguration(const KeyboardLayoutInfo* info) +{ + HKEY hkey; + LONG result; + + result = RegOpenKey(HKEY_CURRENT_USER, L"Software\\Recaps", &hkey); + + if (result != ERROR_SUCCESS) + { + result = RegCreateKey(HKEY_CURRENT_USER, L"Software\\Recaps", &hkey); + } + + // Save current isUse value for each language + if (result == ERROR_SUCCESS) + { + for (UINT i = 0; i < info->count; i++) + { + DWORD data = info->inUse[i]; + RegSetValueEx(hkey, info->names[i], 0, REG_DWORD, (CONST BYTE*)(&data), sizeof(DWORD)); + } + } + + RegCloseKey(hkey); +} + +/////////////////////////////////////////////////////////////////////////////// +// Finds out which window has the focus +HWND RemoteGetFocus() +{ + HWND hwnd = GetForegroundWindow(); + DWORD remoteThreadId = GetWindowThreadProcessId(hwnd, NULL); + DWORD currentThreadId = GetCurrentThreadId(); + AttachThreadInput(remoteThreadId, currentThreadId, TRUE); + HWND focused = GetFocus(); + AttachThreadInput(remoteThreadId, currentThreadId, FALSE); + return focused; +} + +/////////////////////////////////////////////////////////////////////////////// +// Returns the current layout in the active window +HKL GetCurrentLayout() +{ + HWND hwnd = RemoteGetFocus(); + DWORD threadId = GetWindowThreadProcessId(hwnd, NULL); + return GetKeyboardLayout(threadId); +} + +/////////////////////////////////////////////////////////////////////////////// +// Switches the current language +HKL SwitchLayout() +{ + HWND hwnd = RemoteGetFocus(); + HKL currentLayout = GetCurrentLayout(); + + // Find the current keyboard layout's index + UINT i; + for (i = 0; i < g_keyboardInfo.count; i++) + { + if (g_keyboardInfo.hkls[i] == currentLayout) + break; + } + UINT currentLanguageIndex = i; + + // Find the next active layout + BOOL found = FALSE; + UINT newLanguage = currentLanguageIndex; + for (UINT i = 0; i < g_keyboardInfo.count; i++) + { + newLanguage = (newLanguage + 1) % g_keyboardInfo.count; + if (g_keyboardInfo.inUse[newLanguage]) + { + found = TRUE; + break; + } + } + + // Activate the selected language + if (found) + { + g_keyboardInfo.current = newLanguage; + PostMessage(hwnd, WM_INPUTLANGCHANGEREQUEST, 0, (LPARAM)(g_keyboardInfo.hkls[g_keyboardInfo.current])); + #ifdef _DEBUG + PrintDebugString("Language set to %S", g_keyboardInfo.names[g_keyboardInfo.current]); + #endif + return g_keyboardInfo.hkls[g_keyboardInfo.current]; + } + + return NULL; +} + +/////////////////////////////////////////////////////////////////////////////// +// Selects the entire current line and converts it to the current kwyboard layout +void SwitchAndConvert(void*) +{ + SendKeyCombo(VK_CONTROL, 'A', TRUE); + HKL sourceLayout = GetCurrentLayout(); + HKL targetLayout = SwitchLayout(); + ConvertSelectedTextInActiveWindow(sourceLayout, targetLayout); +} + +/////////////////////////////////////////////////////////////////////////////// +// A LowLevelHookProc implementation that captures the CapsLock key +LRESULT CALLBACK LowLevelHookProc(int nCode, WPARAM wParam, LPARAM lParam) +{ + if (nCode < 0) return CallNextHookEx(g_hHook, nCode, wParam, lParam); + + KBDLLHOOKSTRUCT* data = (KBDLLHOOKSTRUCT*)lParam; + + BOOL ctrl = (GetKeyState(VK_CONTROL) & 0x80000000) > 0; + BOOL caps = data->vkCode == VK_CAPITAL && wParam == WM_KEYDOWN; + + // ignore injected keystrokes + if ((data->flags & LLKHF_INJECTED) == 0) + { + // Handle CapsLock - only switch current layout + if (caps && !ctrl) + { + SwitchLayout(); + return 1; + } + // Handle Ctrl-CapsLock - switch current layout and convert text in current field + else if (caps && ctrl) + { + // We start SwitchLayoutAndConvertSelected in another thread since it simulates + // keystrokes to copy and paste the teset which call back into this hook. + // That isn't good.. + _beginthread(SwitchAndConvert, 0, NULL); + return 1; // prevent windows from handling the keystroke + } + } + + return CallNextHookEx(g_hHook, nCode, wParam, lParam); +} diff --git a/recaps.ico b/recaps.ico new file mode 100644 index 0000000..aa2d06a Binary files /dev/null and b/recaps.ico differ diff --git a/recaps.sln b/recaps.sln new file mode 100644 index 0000000..21932c4 --- /dev/null +++ b/recaps.sln @@ -0,0 +1,21 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "recaps", "recaps.vcproj", "{A6546042-77AD-46B0-B4B4-63641E29810E}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Release = Release + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {A6546042-77AD-46B0-B4B4-63641E29810E}.Debug.ActiveCfg = Debug|Win32 + {A6546042-77AD-46B0-B4B4-63641E29810E}.Debug.Build.0 = Debug|Win32 + {A6546042-77AD-46B0-B4B4-63641E29810E}.Release.ActiveCfg = Release|Win32 + {A6546042-77AD-46B0-B4B4-63641E29810E}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/recaps.vcproj b/recaps.vcproj new file mode 100644 index 0000000..18f1eac --- /dev/null +++ b/recaps.vcproj @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resource.h b/resource.h new file mode 100644 index 0000000..b3209e6 --- /dev/null +++ b/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by resource.rc +// +#define IDI_MAINFRAME 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/resource.rc b/resource.rc new file mode 100644 index 0000000..2c7f3aa --- /dev/null +++ b/resource.rc @@ -0,0 +1,72 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Hebrew resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_HEB) +#ifdef _WIN32 +LANGUAGE LANG_HEBREW, SUBLANG_DEFAULT +#pragma code_page(1255) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_MAINFRAME ICON "recaps.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // Hebrew resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/setup.iss b/setup.iss new file mode 100644 index 0000000..c761f3e --- /dev/null +++ b/setup.iss @@ -0,0 +1,28 @@ +[Setup] +AppName=Recaps +AppVerName=Recaps +AppPublisher=gooli.org +DefaultDirName={pf}\Recaps +DisableDirPage=yes +DefaultGroupName=Recaps +DisableProgramGroupPage=yes +SetupIconFile=recaps.ico +InfoBeforeFile=readme.txt +Compression=lzma +SolidCompression=yes +AppMutex=recaps-D3E743A3-E0F9-47f5-956A-CD15C6548789 +PrivilegesRequired=poweruser + +[Files] +Source: "Release\recaps.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "readme.txt"; DestDir: "{app}"; Flags: ignoreversion +Source: "LICENSE"; DestDir: "{app}"; Flags: ignoreversion + +[Icons] +Name: "{commonstartup}\Recaps"; Filename: "{app}\recaps.exe" + +[Registry] +Root: HKCU; Subkey: "Software\Recaps"; Flags: uninsdeletekey + +[Run] +Filename: "{app}\recaps.exe"; Description: "{cm:LaunchProgram,Recaps}"; Flags: nowait postinstall skipifsilent diff --git a/stdafx.cpp b/stdafx.cpp new file mode 100644 index 0000000..1f6d4e7 --- /dev/null +++ b/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// hebcaps.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/stdafx.h b/stdafx.h new file mode 100644 index 0000000..b57e88b --- /dev/null +++ b/stdafx.h @@ -0,0 +1,28 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_) +#define AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include +#include +#include +#include + +#include +#include +#include +#include + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_) diff --git a/tasks.txt b/tasks.txt new file mode 100644 index 0000000..c7ad151 --- /dev/null +++ b/tasks.txt @@ -0,0 +1,14 @@ +Tasks +===== + + * if capslock is pressed when some text is selected, change it to the new locale + + should work with: + - standard text boxes + - rich text (wordpad) + - word + - internet explorer address / search / fields + - firefox address / search / fields + + * add a manifest to make the message box look like Windows XP + \ No newline at end of file diff --git a/trayicon.cpp b/trayicon.cpp new file mode 100644 index 0000000..1403dd0 --- /dev/null +++ b/trayicon.cpp @@ -0,0 +1,52 @@ +#include "stdafx.h" +#include "trayicon.h" + +void AddTrayIcon(HWND hWnd, UINT uID, UINT uCallbackMsg, UINT uIcon, LPTSTR pszToolTip) +{ + NOTIFYICONDATA nid = {0}; + nid.cbSize = sizeof(nid); + nid.hWnd = hWnd; + nid.uID = uID; + nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + nid.uCallbackMessage = uCallbackMsg; + nid.hIcon = LoadSmallIcon(GetModuleHandle(NULL), uIcon); + wcscpy_s(nid.szTip, sizeof(WCHAR)*64, pszToolTip); + Shell_NotifyIcon(NIM_ADD, &nid); +} + +void ModifyTrayIcon( HWND hWnd, UINT uID, UINT uIcon, LPTSTR pszToolTip ) +{ + NOTIFYICONDATA nid = {0}; + nid.cbSize = sizeof(nid); + nid.hWnd = hWnd; + nid.uID = uID; + + if (uIcon != (UINT)-1) + { + nid.hIcon = LoadSmallIcon(GetModuleHandle( NULL ), uIcon); + nid.uFlags |= NIF_ICON; + } + + if (pszToolTip) + { + wcscpy_s(nid.szTip, sizeof(WCHAR)*64, pszToolTip); + nid.uFlags |= NIF_TIP; + } + + if (uIcon != (UINT)-1 || pszToolTip) + Shell_NotifyIcon(NIM_MODIFY, &nid); +} + +void RemoveTrayIcon(HWND hWnd, UINT uID) +{ + NOTIFYICONDATA nid = {0}; + nid.cbSize = sizeof( nid ); + nid.hWnd = hWnd; + nid.uID = uID; + Shell_NotifyIcon( NIM_DELETE, &nid ); +} + +HICON LoadSmallIcon(HINSTANCE hInstance, UINT uID) +{ + return (HICON)LoadImage(hInstance, MAKEINTRESOURCE(uID), IMAGE_ICON, 16, 16, 0); +} diff --git a/trayicon.h b/trayicon.h new file mode 100644 index 0000000..862d88a --- /dev/null +++ b/trayicon.h @@ -0,0 +1,7 @@ +#pragma once + +// Prototypes +void AddTrayIcon( HWND hWnd, UINT uID, UINT uCallbackMsg, UINT uIcon, LPTSTR pszToolTip ); +void RemoveTrayIcon( HWND hWnd, UINT uID); +void ModifyTrayIcon( HWND hWnd, UINT uID, UINT uIcon, LPTSTR pszToolTip ); +HICON LoadSmallIcon( HINSTANCE hInstance, UINT uID ); diff --git a/utils.cpp b/utils.cpp new file mode 100644 index 0000000..304ec53 --- /dev/null +++ b/utils.cpp @@ -0,0 +1,47 @@ +#include "stdafx.h" + +#define BUFSIZE 2048 + +/////////////////////////////////////////////////////////////////////////////// +// Shows a system error message +void ShowError(const WCHAR* message) +{ + WCHAR buffer[BUFSIZE]; + + LPVOID errMessage; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&errMessage, + 0, + NULL + ); + + buffer[0] = '\0'; + wcscat_s(buffer, BUFSIZE, message); + wcscat_s(buffer, BUFSIZE, L"\n\nError : "); + wcscat_s(buffer, BUFSIZE, (const WCHAR*)errMessage); + + MessageBox(NULL, (const WCHAR*)buffer, L"Recaps Error", MB_OK | MB_ICONINFORMATION); + LocalFree(errMessage); +} + + +/////////////////////////////////////////////////////////////////////////////// +// Prints an error message to the debugger +void PrintDebugString(const char* format, ...) +{ + char buffer[2048]; + + va_list args; + va_start(args, format); + + vsprintf_s(buffer, 2048, format, args); + strcat_s(buffer, 2048, "\n"); + + OutputDebugStringA(buffer); +} \ No newline at end of file diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..549cdee --- /dev/null +++ b/utils.h @@ -0,0 +1,2 @@ +void ShowError(const WCHAR* message); +void PrintDebugString(const char* format, ...); \ No newline at end of file