From ee556fe821514389fdd84b67378c213374be7718 Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Wed, 22 Oct 2014 18:28:37 +0300 Subject: [PATCH] Initial commit --- README.md | 38 +++- build.bat | 37 ++++ fixlayouts.cpp | 311 +++++++++++++++++++++++++++++++ fixlayouts.h | 38 ++++ recaps.2005.sln | 19 ++ recaps.2005.vcproj | 316 +++++++++++++++++++++++++++++++ recaps.cpp | 453 +++++++++++++++++++++++++++++++++++++++++++++ recaps.ico | Bin 0 -> 4406 bytes recaps.sln | 21 +++ recaps.vcproj | 218 ++++++++++++++++++++++ resource.h | 16 ++ resource.rc | 72 +++++++ setup.iss | 28 +++ stdafx.cpp | 8 + stdafx.h | 28 +++ tasks.txt | 14 ++ trayicon.cpp | 52 ++++++ trayicon.h | 7 + utils.cpp | 47 +++++ utils.h | 2 + 20 files changed, 1722 insertions(+), 3 deletions(-) create mode 100644 build.bat create mode 100644 fixlayouts.cpp create mode 100644 fixlayouts.h create mode 100644 recaps.2005.sln create mode 100644 recaps.2005.vcproj create mode 100644 recaps.cpp create mode 100644 recaps.ico create mode 100644 recaps.sln create mode 100644 recaps.vcproj create mode 100644 resource.h create mode 100644 resource.rc create mode 100644 setup.iss create mode 100644 stdafx.cpp create mode 100644 stdafx.h create mode 100644 tasks.txt create mode 100644 trayicon.cpp create mode 100644 trayicon.h create mode 100644 utils.cpp create mode 100644 utils.h 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 0000000000000000000000000000000000000000..aa2d06a4b94a1e107ab3930cd773286e7701f3c5 GIT binary patch literal 4406 zcmd^CdrVVj6u+e?AdJUEMPRh@P(+^Lt7zs1LEG@AK5*cFHVvwj*_49Ev466a1D?H*5Tuqv|Vi*JT{}oxxwP4RSn%zwz zkw#zvh-5%QAS0L`=K*c&a9rjnw0E{gHWV9V<6wg*iz&!6&=YwDd7*`rg~-(0v}h*# zz*T{UD_ARD@HX;Lc78TWT$hNL@l3>8!$J`W5y+SBix#;pa-PXPHa#}HT3(F?1_z#0 zG*q~=`D}c>I#Xsr=C{w#Gt_#aRfrpMGUpuC|RIk-6B_9MU|TtAg0 z3d!iu=n(22=tdp^9>~?l^(l@)Z>jNU<3ucy$zPh08H3r8PH4@<}J_=A|%x(|iYLOE&$~+0M70Z#CZvW-+rgGqW(muWCUpTbRxHY|h&Skn~%HlZTVDr}HxZWxk=l0gQm4 zu%M905PCE{EH;c8$5j79S)m^O9@e(jFpFN^-2>e{gFR`{wDhd>ZQ^bDW%*)>Sb9*Z zIHNe*bGA#_1wRl<>q3iT@=Zu5oMQLZU<5{N42O&q`+HmgU}le0#5K@2-8j6dj7n{guljmh0t2 z3m8&shq6OdD0}@^ z>#gQl5zVU)RkthK;gs1)Yy+|ZaY!#Yk{2m$mU;(!PhaPO_5(K_-B2Nwqno2%K8awC zfFnOChqOuD91Wnkt9cj=L%M)|Qlr#eV8k;vWOf=Gk~u4ZrR5_H3=a%i6$H&c^nSYdBw`XbCFWP<^EU7- zt!J!65CBkg5(EjZ-mXR_Mnv-u%1|%Ln#xLQOQ4?4wV7)?XQJ7E*Nn4)lU0~i*;EOPR&U54e^DQOu@|b z2pR0s%}Ws5_n+VI=RaoT_mmGt z9z6c@@faGr`TNb)sjJ~bgZ5hkR{W^wf7q|V+vjsa3u&=57q5Sy?r5ggQGifUlM1sy zFM$U!3c51EbR@=rixDaTE~g`8jxz<=VuIf|7@;>5GH@vxr}w_)A*3qCxfH34#KkJI Rm_C(Jsfv#J#=#jve*um%mXQDe literal 0 HcmV?d00001 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