diff --git a/Redcraft.FocusIME/Source/Private/Main.cpp b/Redcraft.FocusIME/Source/Private/Main.cpp index 2c12064..1bc7f64 100644 --- a/Redcraft.FocusIME/Source/Private/Main.cpp +++ b/Redcraft.FocusIME/Source/Private/Main.cpp @@ -3,14 +3,18 @@ #include #include #include +#include #include #include #include #include +#include #include +#include #pragma comment(lib, "psapi.lib") +#pragma comment(lib, "imm32.lib") #pragma warning(disable: 4996) @@ -23,14 +27,142 @@ bool GShouldExit = false; void PrintLog(const std::string& Text); -void CALLBACK FocusEventHookProc(HWINEVENTHOOK, DWORD Event, HWND Window, LONG, LONG, DWORD, DWORD); - -LRESULT CALLBACK MainWindowProc(HWND Window, UINT Message, WPARAM WParam, LPARAM LParam); - -int WINAPI WinMain(HINSTANCE Instance, HINSTANCE, LPSTR, int) +void Tick() { using namespace std::chrono_literals; + // ReSharper disable CppInconsistentNaming + // ReSharper disable CppClangTidyPerformanceEnumSize + + enum { IMC_GETCONVERSIONMODE = 0x0001 }; + enum { IMC_SETCONVERSIONMODE = 0x0002 }; + enum { IMC_GETOPENSTATUS = 0x0005 }; + + // ReSharper restore CppInconsistentNaming + // ReSharper restore CppClangTidyPerformanceEnumSize + + enum class EIMEConversionMode : std::uint8_t + { + Default, + English, + Chinese, + }; + + static std::map CachedIMEMode; + + static HWND LastWindow = nullptr; + + static std::string CachedProcess; + + if (LastWindow != nullptr) + { + do + { + if (CachedIMEMode.contains(CachedProcess) && CachedIMEMode[CachedProcess] == EIMEConversionMode::Default) break; + + HKL KeyboardLayout = GetKeyboardLayout(GetWindowThreadProcessId(LastWindow, nullptr)); + + LANGID LanguageID = LOWORD(KeyboardLayout); + + if (PRIMARYLANGID(LanguageID) != LANG_CHINESE) break; + + if (const HWND IMEWindow = ImmGetDefaultIMEWnd(LastWindow)) + { + const LRESULT ConversionMode = SendMessage(IMEWindow, WM_IME_CONTROL, IMC_GETCONVERSIONMODE, 0); + const LRESULT OpenStatus = SendMessage(IMEWindow, WM_IME_CONTROL, IMC_GETOPENSTATUS, 0); + + if (!OpenStatus) break; + + const EIMEConversionMode CurrentMode = + ConversionMode == 0x0000 ? EIMEConversionMode::English : + ConversionMode == 0x0401 ? EIMEConversionMode::Chinese : EIMEConversionMode::Default; + + if (CurrentMode == EIMEConversionMode::Default) break; + + if (!CachedIMEMode.contains(CachedProcess) || CachedIMEMode[CachedProcess] != CurrentMode) + { + CachedIMEMode[CachedProcess] = CurrentMode; + + PrintLog("Update the conversion mode cached for process '" + CachedProcess + "' to " + + (CurrentMode == EIMEConversionMode::English ? "English" : + CurrentMode == EIMEConversionMode::Chinese ? "Chinese" : "Default")); + } + } + } + while (false); + } + + const HWND ForegroundWindow = GetForegroundWindow(); + + if (ForegroundWindow == LastWindow) return; + + if (ForegroundWindow == nullptr) + { + LastWindow = nullptr; + + return; + } + + do + { + DWORD PID = 0; + + GetWindowThreadProcessId(ForegroundWindow, &PID); + + const HANDLE ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, PID); + + if (ProcessHandle == nullptr) break; + + char Buffer[MAX_PATH] = { }; + + if (GetModuleBaseName(ProcessHandle, nullptr, Buffer, sizeof(Buffer)) == 0) + { + CloseHandle(ProcessHandle); + + break; + } + + CloseHandle(ProcessHandle); + + LastWindow = ForegroundWindow; + + CachedProcess = Buffer; + + if (!CachedIMEMode.contains(CachedProcess) || CachedIMEMode[CachedProcess] == EIMEConversionMode::Default) break; + + std::this_thread::sleep_for(50ms); + + if (const HWND IMEWindow = ImmGetDefaultIMEWnd(LastWindow)) + { + const LRESULT ConversionMode = SendMessage(IMEWindow, WM_IME_CONTROL, IMC_GETCONVERSIONMODE, 0); + const LRESULT OpenStatus = SendMessage(IMEWindow, WM_IME_CONTROL, IMC_GETOPENSTATUS, 0); + + if (!OpenStatus) break; + + LPARAM TargetMode = CachedIMEMode[CachedProcess] == EIMEConversionMode::Chinese ? 0x0401 : 0x0000; + + if (ConversionMode != TargetMode) + { + if (SendMessage(IMEWindow, WM_IME_CONTROL, IMC_SETCONVERSIONMODE, TargetMode) == 0) + { + PrintLog("Successfully set conversion mode for process '" + CachedProcess + "'"); + } + else + { + PrintLog("Failed to set conversion mode for process '" + CachedProcess + "'"); + } + } + } + } + while (false); +} + +LRESULT CALLBACK MainWindowProc(HWND Window, UINT Message, WPARAM WParam, LPARAM LParam); + +void CALLBACK FocusEventHookProc(HWINEVENTHOOK, DWORD, HWND, LONG, LONG, DWORD, DWORD) { Tick(); } + +int WINAPI WinMain(HINSTANCE Instance, HINSTANCE, LPSTR, int) +{ const HANDLE Mutex = CreateMutex(nullptr, TRUE, "FocusIME_SingleInstance_Mutex"); if (Mutex == nullptr) @@ -150,7 +282,7 @@ int WINAPI WinMain(HINSTANCE Instance, HINSTANCE, LPSTR, int) while (!GShouldExit) { - BOOL bResult = GetMessage(&Message, nullptr, 0, 0); + const BOOL bResult = GetMessage(&Message, nullptr, 0, 0); if (bResult > 0) { @@ -209,9 +341,14 @@ LRESULT CALLBACK MainWindowProc(const HWND Window, const UINT Message, const WPA { static NOTIFYICONDATA NotifyIconData; - constexpr UINT TrayIconMessage = WM_USER + 1; + // ReSharper disable CppInconsistentNaming + // ReSharper disable CppClangTidyPerformanceEnumSize - constexpr UINT ExitMessage = 1001; + enum { WM_TRAY_ICON = WM_USER + 1 }; + enum { ID_TRAY_EXIT = 1001 }; + + // ReSharper restore CppInconsistentNaming + // ReSharper restore CppClangTidyPerformanceEnumSize switch (Message) { @@ -223,7 +360,7 @@ LRESULT CALLBACK MainWindowProc(const HWND Window, const UINT Message, const WPA NotifyIconData.hWnd = Window; NotifyIconData.uID = 1; NotifyIconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; - NotifyIconData.uCallbackMessage = TrayIconMessage; + NotifyIconData.uCallbackMessage = WM_TRAY_ICON; NotifyIconData.hIcon = LoadIcon(nullptr, IDI_APPLICATION); @@ -233,7 +370,7 @@ LRESULT CALLBACK MainWindowProc(const HWND Window, const UINT Message, const WPA break; - case TrayIconMessage: + case WM_TRAY_ICON: switch (LParam) { @@ -246,9 +383,9 @@ LRESULT CALLBACK MainWindowProc(const HWND Window, const UINT Message, const WPA if (const HMENU Menu = CreatePopupMenu()) { - InsertMenu(Menu, -1, MF_BYPOSITION | MF_STRING, ExitMessage, "Exit(&X)"); + InsertMenu(Menu, -1, MF_BYPOSITION | MF_STRING, ID_TRAY_EXIT, "Exit(&X)"); - SetMenuDefaultItem(Menu, ExitMessage, FALSE); + SetMenuDefaultItem(Menu, ID_TRAY_EXIT, FALSE); SetForegroundWindow(Window); @@ -268,7 +405,7 @@ LRESULT CALLBACK MainWindowProc(const HWND Window, const UINT Message, const WPA case WM_COMMAND: - if (LOWORD(WParam) == ExitMessage) + if (LOWORD(WParam) == ID_TRAY_EXIT) { PrintLog("Exit command received from tray menu"); @@ -293,59 +430,6 @@ LRESULT CALLBACK MainWindowProc(const HWND Window, const UINT Message, const WPA return 0; } -void CALLBACK FocusEventHookProc(HWINEVENTHOOK, const DWORD Event, const HWND Window, LONG, LONG, DWORD, DWORD) -{ - if (Event == EVENT_OBJECT_FOCUS && Window != nullptr) - { - std::string ProcessName; - - do - { - DWORD PID = 0; - - GetWindowThreadProcessId(Window, &PID); - - const HANDLE ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, PID); - - if (ProcessHandle == nullptr) - { - ProcessName = "Unknown"; - - break; - } - - char Buffer[MAX_PATH] = { }; - - if (GetModuleBaseName(ProcessHandle, nullptr, Buffer, sizeof(Buffer)) == 0) - { - CloseHandle(ProcessHandle); - - ProcessName = "Unknown"; - - break; - } - - CloseHandle(ProcessHandle); - - ProcessName = Buffer; - } - while (false); - - char Title[1024] = { }; - - GetWindowText(Window, Title, sizeof(Title)); - - std::string MessageBuffer = "Focus changed to: " + ProcessName; - - if (strlen(Title) > 0) - { - MessageBuffer += " (" + std::string(Title) + ")"; - } - - PrintLog(MessageBuffer); - } -} - void PrintLog(const std::string& Text) { const auto Now = std::chrono::system_clock::now();