Fix Korean IME append extension issue

Currently, there is a bug in notepad++'s add extension feature only for Korean input after it was changed to hooking-based in the commit below.
b5a5baf#diff-eeb5624a35a43795da4eb970149a9ce7d22858b678a242affd2357520ea3e9f2R607

Bug

    Attempting to save via Enter appends the last character to the extension.
    Candidate mode is similar, with more varied issues depending on IME.

Cause

    the hooking function is executed before the Hangul composition is completed and the last character is added after the extension.
    Same for Candidate mode.

Workaround

It is almost impossible to fix issue 2 while maintaining the current Enter hooking
Exiting Candidate Mode can be done by pressing Enter, ESC, number key, or clicking on a candidate character, but there is too much code to cover all of these cases.
In addition, the Windows input framework is fragmented into IMM and TSF, and various IMEs have different implementations, so it is almost impossible to determine the state of Candidate or Hangul composition through IME hooking.
I have seen differences in the events fired by different Windows versions and different IME programs for the same IME behavior.
This PR solves problem 1 and partially solves problem 2 by not saving with Enter when in Hangul mode.

Fix #11582, fix #12225, fix #12366, close #13788
This commit is contained in:
80rokwoc4j 2022-07-13 00:05:28 +09:00 committed by Don Ho
parent a647991cd7
commit 6330a688b1
1 changed files with 67 additions and 1 deletions

View File

@ -251,6 +251,12 @@ public:
IFACEMETHODIMP OnFileOk(IFileDialog* dlg) override
{
_lastUsedFolder = getDialogFolder(dlg);
// Ignore OnFileOk() as OnPreFileOk() is not called when in Hangul mode.
// check KbdProcHook() for details.
if (_isHangul)
return S_FALSE;
return S_OK;
}
IFACEMETHODIMP OnFolderChange(IFileDialog*) override
@ -368,6 +374,8 @@ public:
_lastSelectedType(fileIndex + 1), _wildcardType(wildcardIndex >= 0 ? wildcardIndex + 1 : 0)
{
installHooks();
_keyboardLayoutLanguage = getKeyboardLayout();
_isHangul = isHangul(_keyboardLayoutLanguage);
}
~FileDialogEventHandler()
@ -418,6 +426,11 @@ private:
nullptr,
::GetCurrentThreadId()
);
_langaugeDetectHook = ::SetWindowsHookEx(WH_SHELL,
reinterpret_cast<HOOKPROC>(&FileDialogEventHandler::LanguageDetectHook),
nullptr,
::GetCurrentThreadId()
);
}
void removeHooks()
@ -426,8 +439,11 @@ private:
::UnhookWindowsHookEx(_prevKbdHook);
if (_prevCallHook)
::UnhookWindowsHookEx(_prevCallHook);
if (_langaugeDetectHook)
::UnhookWindowsHookEx(_langaugeDetectHook);
_prevKbdHook = nullptr;
_prevCallHook = nullptr;
_langaugeDetectHook = nullptr;
}
void eraseHandles()
@ -504,6 +520,21 @@ private:
}
}
LANGID getKeyboardLayout()
{
return PRIMARYLANGID(LOWORD(HandleToLong(GetKeyboardLayout(0))));
}
static bool isHangul(LANGID lang)
{
auto hwnd = GetFocus();
auto himc = ImmGetContext(hwnd);
auto isLocalInputMode = ImmGetOpenStatus(himc); // return true when CJK IME is in local language input mode
ImmReleaseContext(hwnd, himc);
return (lang == LANG_KOREAN) && isLocalInputMode;
};
// Transforms a forward-slash path to a canonical Windows path.
static bool transformPath(generic_string& fileName)
{
@ -625,7 +656,39 @@ private:
HWND hwnd = GetFocus();
auto it = s_handleMap.find(hwnd);
if (it != s_handleMap.end() && it->second && hwnd == it->second->_hwndNameEdit)
it->second->onPreFileOk();
{
// Capture the state at this point because of the OnFileOk()
it->second->_isHangul = isHangul(it->second->_keyboardLayoutLanguage);
if (it->second->_isHangul)
{
// If IME is in Hangul mode, ignore VK_RETURN to complete the composition and change
// to alphabetical input mode to proceed to onPreFileOk() on the next VK_RETURN.
auto himc = ImmGetContext(hwnd);
ImmSetConversionStatus(himc, IME_CMODE_ALPHANUMERIC, IME_SMODE_NONE);
ImmReleaseContext(hwnd, himc);
}
else
{
it->second->onPreFileOk();
}
}
}
}
return ::CallNextHookEx(nullptr, nCode, wParam, lParam);
}
// Dectect language layout of keyboard when it changed
static LRESULT CALLBACK LanguageDetectHook(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HSHELL_LANGUAGE)
{
HWND hwnd = GetFocus();
auto it = s_handleMap.find(hwnd);
if (it != s_handleMap.end() && it->second && hwnd == it->second->_hwndNameEdit)
{
HKL hkl = (HKL)lParam;
it->second->_keyboardLayoutLanguage = PRIMARYLANGID(LOWORD(hkl));
}
}
return ::CallNextHookEx(nullptr, nCode, wParam, lParam);
@ -640,11 +703,14 @@ private:
generic_string _lastUsedFolder;
HHOOK _prevKbdHook = nullptr;
HHOOK _prevCallHook = nullptr;
HHOOK _langaugeDetectHook = nullptr;
HWND _hwndNameEdit = nullptr;
HWND _hwndButton = nullptr;
UINT _currentType = 0; // File type currenly selected in dialog.
UINT _lastSelectedType = 0; // Last selected non-wildcard file type.
UINT _wildcardType = 0; // Wildcard *.* file type index (usually 1).
LANGID _keyboardLayoutLanguage = LANG_NEUTRAL;
bool _isHangul = false; // Korean IME specific flag
};
std::unordered_map<HWND, FileDialogEventHandler*> FileDialogEventHandler::s_handleMap;