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