Add the feature that repeated smart punctuation input switches to Chinese punctuation.

This commit is contained in:
fanlusky committed 2026-08-10 03:23:36 +08:00
1 parent 8a84cdc178
commit 04eda22a40
6 files changed
+186 -18

No files matched your search

+84 -10
View File
@@ -142,13 +142,64 @@ WCHAR CMetasequoiaIME::_GetPrecedingCharForSmartPunctuation(TfEditCookie ec, _In
return _GetPrecedingDocumentChar(ec, pContext);
}
void CMetasequoiaIME::_ResetSmartPunctuationHistory()
{
void CMetasequoiaIME::_ResetSmartPunctuationHistory()
{
_smartPunctuationKey = 0;
_smartPunctuationPrecedingChar = 0;
_smartPunctuationCommittedAscii = false;
_smartPunctuationAsciiRejected = false;
}
_smartPunctuationCommittedAscii = false;
_smartPunctuationAsciiRejected = false;
_smartPunctuationCommitTick = 0;
_smartPunctuationFocusToken = 0;
_smartPunctuationForegroundWindow = nullptr;
}
bool CMetasequoiaIME::_QueueRepeatedSmartPunctuationReplacement(WCHAR wch)
{
if (!_smartPunctuationCommittedAscii || _smartPunctuationKey != wch ||
_smartPunctuationCommitTick == 0 || _msgWndHandle == nullptr ||
_pCompositionProcessorEngine == nullptr || _IsComposing() ||
_candidateMode != CANDIDATE_NONE ||
!Global::SmartPunctuationEnabled.load(std::memory_order_relaxed) ||
!Global::SmartPunctuationRepeatToChineseEnabled.load(std::memory_order_relaxed))
{
return false;
}
const ULONGLONG now = GetTickCount64();
if (now - _smartPunctuationCommitTick > SMART_PUNCTUATION_REPEAT_INTERVAL_MS ||
!_IsFocusSessionCurrent(_smartPunctuationFocusToken) ||
GetForegroundWindow() != _smartPunctuationForegroundWindow)
{
return false;
}
const WCHAR *chinese = _pCompositionProcessorEngine->GetPunctuation(wch);
if (chinese == nullptr || chinese[0] == L'\0' || chinese[1] != L'\0')
{
return false;
}
_pendingSmartPunctuationReplacement = chinese[0];
_pendingSmartPunctuationFocusToken = _smartPunctuationFocusToken;
_pendingSmartPunctuationForegroundWindow = _smartPunctuationForegroundWindow;
_pendingSmartPunctuationDeadline =
_smartPunctuationCommitTick + SMART_PUNCTUATION_REPEAT_INTERVAL_MS;
const uint64_t focusToken = _pendingSmartPunctuationFocusToken;
if (!PostMessage(_msgWndHandle, WM_ReplaceRepeatedSmartPunctuation,
static_cast<WPARAM>(focusToken & 0xFFFFFFFFULL),
static_cast<LPARAM>((focusToken >> 32) & 0xFFFFFFFFULL)))
{
_pendingSmartPunctuationReplacement = 0;
_pendingSmartPunctuationFocusToken = 0;
_pendingSmartPunctuationForegroundWindow = nullptr;
_pendingSmartPunctuationDeadline = 0;
return false;
}
_ResetSmartPunctuationHistory();
return true;
}
void CMetasequoiaIME::_InvalidateSmartPunctuationShadow()
{
@@ -211,9 +262,20 @@ void CMetasequoiaIME::_UpdateSmartPunctuationShadow(UINT code, WCHAR wch, bool i
_smartPunctuationShadowValid = true;
}
void CMetasequoiaIME::_NoteKeyForSmartPunctuation(UINT code, WCHAR wch, bool isEaten)
{
_UpdateSmartPunctuationShadow(code, wch, isEaten);
void CMetasequoiaIME::_NoteKeyForSmartPunctuation(UINT code, WCHAR wch, bool isEaten)
{
// The replacement message normally runs before another input event. If it
// does not, never let a later key leave the queued Backspace targeting an
// unrelated character.
if (_pendingSmartPunctuationReplacement != 0)
{
_pendingSmartPunctuationReplacement = 0;
_pendingSmartPunctuationFocusToken = 0;
_pendingSmartPunctuationForegroundWindow = nullptr;
_pendingSmartPunctuationDeadline = 0;
}
_UpdateSmartPunctuationShadow(code, wch, isEaten);
if (_smartPunctuationKey == 0)
{
@@ -296,8 +358,20 @@ std::wstring CMetasequoiaIME::_ResolveSmartPunctuation(WCHAR wch, WCHAR precedin
_smartPunctuationKey = wch;
_smartPunctuationPrecedingChar = precedingChar;
_smartPunctuationCommittedAscii = committedAscii;
_smartPunctuationAsciiRejected = asciiRejected;
_smartPunctuationCommittedAscii = committedAscii;
_smartPunctuationAsciiRejected = asciiRejected;
if (committedAscii)
{
_smartPunctuationCommitTick = GetTickCount64();
_smartPunctuationFocusToken = _CaptureFocusSessionToken();
_smartPunctuationForegroundWindow = GetForegroundWindow();
}
else
{
_smartPunctuationCommitTick = 0;
_smartPunctuationFocusToken = 0;
_smartPunctuationForegroundWindow = nullptr;
}
if (!resolved.empty())
{
_smartPunctuationShadowChar = resolved.back();
+53
View File
@@ -1671,6 +1671,8 @@ void CMetasequoiaIME::IpcWorkerThread(CMetasequoiaIME *pIME)
}
}
if (validFrame && (buf.msg_type == Global::DataToTsfWorkerThreadMsgType::SmartPunctuationChanged ||
buf.msg_type ==
Global::DataToTsfWorkerThreadMsgType::SmartPunctuationRepeatToChineseChanged ||
buf.msg_type == Global::DataToTsfWorkerThreadMsgType::PairedPunctuationChanged ||
buf.msg_type == Global::DataToTsfWorkerThreadMsgType::MicrosoftShuangpinChanged))
{
@@ -1720,6 +1722,7 @@ void CMetasequoiaIME::IpcWorkerThread(CMetasequoiaIME *pIME)
// Soft config/control frames: drop without killing the worker pipe.
if (buf.msg_type == Global::DataToTsfWorkerThreadMsgType::PagingCommaPeriodChanged ||
buf.msg_type == Global::DataToTsfWorkerThreadMsgType::SmartPunctuationChanged ||
buf.msg_type == Global::DataToTsfWorkerThreadMsgType::SmartPunctuationRepeatToChineseChanged ||
buf.msg_type == Global::DataToTsfWorkerThreadMsgType::PairedPunctuationChanged ||
buf.msg_type == Global::DataToTsfWorkerThreadMsgType::MicrosoftShuangpinChanged ||
buf.msg_type == Global::DataToTsfWorkerThreadMsgType::PipeReady ||
@@ -1777,6 +1780,10 @@ void CMetasequoiaIME::IpcWorkerThread(CMetasequoiaIME *pIME)
{
Global::SmartPunctuationEnabled.store(buf.data[0] == L'1', std::memory_order_relaxed);
}
else if (buf.msg_type == Global::DataToTsfWorkerThreadMsgType::SmartPunctuationRepeatToChineseChanged)
{
Global::SmartPunctuationRepeatToChineseEnabled.store(buf.data[0] == L'1', std::memory_order_relaxed);
}
else if (buf.msg_type == Global::DataToTsfWorkerThreadMsgType::PairedPunctuationChanged)
{
Global::PairedPunctuationEnabled.store(buf.data[0] == L'1', std::memory_order_relaxed);
@@ -2506,6 +2513,52 @@ LRESULT CALLBACK CMetasequoiaIME_WindowProc(HWND hWnd, UINT message, WPARAM wPar
SendInput(ARRAYSIZE(inputs), inputs, sizeof(INPUT));
break;
}
case WM_ReplaceRepeatedSmartPunctuation: {
const uint64_t focusToken = static_cast<uint64_t>(static_cast<uint32_t>(wParam)) |
(static_cast<uint64_t>(static_cast<uint32_t>(lParam)) << 32);
const WCHAR replacement = pIME->_pendingSmartPunctuationReplacement;
const bool requestCurrent =
replacement != 0 && focusToken != 0 &&
focusToken == pIME->_pendingSmartPunctuationFocusToken &&
Global::SmartPunctuationRepeatToChineseEnabled.load(std::memory_order_relaxed) &&
pIME->_IsFocusSessionCurrent(focusToken) &&
GetForegroundWindow() == pIME->_pendingSmartPunctuationForegroundWindow &&
GetTickCount64() <= pIME->_pendingSmartPunctuationDeadline;
pIME->_pendingSmartPunctuationReplacement = 0;
pIME->_pendingSmartPunctuationFocusToken = 0;
pIME->_pendingSmartPunctuationForegroundWindow = nullptr;
pIME->_pendingSmartPunctuationDeadline = 0;
if (!requestCurrent)
{
break;
}
INPUT inputs[4] = {};
inputs[0].type = INPUT_KEYBOARD;
inputs[0].ki.wVk = VK_BACK;
inputs[0].ki.dwExtraInfo = SMART_PUNCTUATION_SENDINPUT_EXTRA_INFO;
inputs[1] = inputs[0];
inputs[1].ki.dwFlags = KEYEVENTF_KEYUP;
inputs[2].type = INPUT_KEYBOARD;
inputs[2].ki.wScan = replacement;
inputs[2].ki.dwFlags = KEYEVENTF_UNICODE;
inputs[2].ki.dwExtraInfo = SMART_PUNCTUATION_SENDINPUT_EXTRA_INFO;
inputs[3] = inputs[2];
inputs[3].ki.dwFlags |= KEYEVENTF_KEYUP;
if (SendInput(ARRAYSIZE(inputs), inputs, sizeof(INPUT)) == ARRAYSIZE(inputs))
{
pIME->_smartPunctuationShadowChar = replacement;
pIME->_smartPunctuationShadowValid = true;
}
else
{
pIME->_InvalidateSmartPunctuationShadow();
}
break;
}
case WM_SETTINGCHANGE: {
// Registry may not be flushed yet when the broadcast arrives.
if (lParam && _wcsicmp(reinterpret_cast<LPCWSTR>(lParam), L"ImmersiveColorSet") == 0)
+18 -7
View File
@@ -38,6 +38,9 @@ const DWORD WM_DrainDeferredKeyDown = WM_USER + 18;
const DWORD WM_InsertText = WM_USER + 19;
const DWORD WM_RefreshLanguageBarTheme = WM_USER + 20;
const DWORD WM_PairedPunctuationMoveLeft = WM_USER + 21;
const DWORD WM_ReplaceRepeatedSmartPunctuation = WM_USER + 22;
constexpr ULONG_PTR SMART_PUNCTUATION_SENDINPUT_EXTRA_INFO = 0x4D535050u;
constexpr ULONGLONG SMART_PUNCTUATION_REPEAT_INTERVAL_MS = 2000;
constexpr UINT_PTR TIMER_CONNECT_ALL_NAMEDPIPE = 1;
constexpr UINT_PTR TIMER_CONNECT_TO_TSF_NAMEDPIPE = 2;
constexpr UINT_PTR TIMER_REFRESH_LANG_BAR_THEME = 3;
@@ -165,10 +168,11 @@ class CMetasequoiaIME : public ITfTextInputProcessorEx,
// Shadow first, document read as the fallback. See _smartPunctuationShadowChar.
WCHAR _GetPrecedingCharForSmartPunctuation(TfEditCookie ec, _In_ ITfContext *pContext);
// Smart punctuation: backspacing the ASCII punctuation we just committed
// means that form was unwanted, so the spot stays on Chinese punctuation.
std::wstring _ResolveSmartPunctuation(WCHAR wch, WCHAR precedingChar);
void _NoteKeyForSmartPunctuation(UINT code, WCHAR wch, bool isEaten);
// Smart punctuation: backspacing the ASCII punctuation we just committed
// means that form was unwanted, so the spot stays on Chinese punctuation.
std::wstring _ResolveSmartPunctuation(WCHAR wch, WCHAR precedingChar);
bool _QueueRepeatedSmartPunctuationReplacement(WCHAR wch);
void _NoteKeyForSmartPunctuation(UINT code, WCHAR wch, bool isEaten);
void _ResetSmartPunctuationHistory();
void _UpdateSmartPunctuationShadow(UINT code, WCHAR wch, bool isEaten);
void _InvalidateSmartPunctuationShadow();
@@ -491,9 +495,16 @@ class CMetasequoiaIME : public ITfTextInputProcessorEx,
// Last smart-punctuation commit, used to detect a backspace rejection.
WCHAR _smartPunctuationKey = 0;
WCHAR _smartPunctuationPrecedingChar = 0;
bool _smartPunctuationCommittedAscii = false;
bool _smartPunctuationAsciiRejected = false;
WCHAR _smartPunctuationPrecedingChar = 0;
bool _smartPunctuationCommittedAscii = false;
bool _smartPunctuationAsciiRejected = false;
ULONGLONG _smartPunctuationCommitTick = 0;
uint64_t _smartPunctuationFocusToken = 0;
HWND _smartPunctuationForegroundWindow = nullptr;
WCHAR _pendingSmartPunctuationReplacement = 0;
uint64_t _pendingSmartPunctuationFocusToken = 0;
HWND _pendingSmartPunctuationForegroundWindow = nullptr;
ULONGLONG _pendingSmartPunctuationDeadline = 0;
// Last character known to have reached the application. Hosts such as the
// VS Code terminal back the context with a proxy text store that only ever
+5 -1
View File
@@ -322,15 +322,19 @@ constexpr UINT SmartPunctuationChanged = 11;
constexpr UINT PairedPunctuationChanged = 12;
// Whether ';' is an input key for the Microsoft shuangpin profile.
constexpr UINT MicrosoftShuangpinChanged = 13;
// Replace a repeated smart ASCII punctuation with its Chinese mapping.
constexpr UINT SmartPunctuationRepeatToChineseChanged = 14;
// Highest opcode this build understands. Unknown higher opcodes must be
// ignored by the worker reader (never tear down the pipe).
constexpr UINT MaxKnown = MicrosoftShuangpinChanged;
constexpr UINT MaxKnown = SmartPunctuationRepeatToChineseChanged;
} // namespace DataToTsfWorkerThreadMsgType
inline std::atomic_bool PagingCommaPeriodEnabled{false};
// Default on: matches rime-ice digit_separators behavior until Server syncs.
inline std::atomic_bool SmartPunctuationEnabled{true};
// Default on until the Server sends the persisted setting.
inline std::atomic_bool SmartPunctuationRepeatToChineseEnabled{true};
// Default on until the Server sends the persisted setting.
inline std::atomic_bool PairedPunctuationEnabled{true};
inline std::atomic_bool MicrosoftShuangpinEnabled{false};
inline thread_local bool g_connected = false;
+20
View File
@@ -1128,6 +1128,11 @@ STDAPI CMetasequoiaIME::OnTestKeyDown(ITfContext *pContext, WPARAM wParam, LPARA
{
return E_INVALIDARG;
}
if (static_cast<ULONG_PTR>(GetMessageExtraInfo()) == SMART_PUNCTUATION_SENDINPUT_EXTRA_INFO)
{
*pIsEaten = FALSE;
return S_OK;
}
PerfTimer onTestKeyDownTimer;
Global::UpdateModifiers(wParam, lParam);
_TrackModifierHotkeyArming(wParam, lParam, false);
@@ -1649,6 +1654,11 @@ STDAPI CMetasequoiaIME::OnKeyDown(ITfContext *pContext, WPARAM wParam, LPARAM lP
{
return E_INVALIDARG;
}
if (static_cast<ULONG_PTR>(GetMessageExtraInfo()) == SMART_PUNCTUATION_SENDINPUT_EXTRA_INFO)
{
*pIsEaten = FALSE;
return S_OK;
}
PerfTimer onKeyDownTimer;
const uint64_t focusGeneration = _deferredKeyFocusGeneration;
(void)_DispatchKeyDown(pContext, wParam, lParam, pIsEaten, nullptr, nullptr,
@@ -2016,6 +2026,11 @@ STDAPI CMetasequoiaIME::OnTestKeyUp(ITfContext *pContext, WPARAM wParam, LPARAM
{
return E_INVALIDARG;
}
if (static_cast<ULONG_PTR>(GetMessageExtraInfo()) == SMART_PUNCTUATION_SENDINPUT_EXTRA_INFO)
{
*pIsEaten = FALSE;
return S_OK;
}
Global::UpdateModifiers(wParam, lParam);
@@ -2059,6 +2074,11 @@ STDAPI CMetasequoiaIME::OnKeyUp(ITfContext *pContext, WPARAM wParam, LPARAM lPar
{
return E_INVALIDARG;
}
if (static_cast<ULONG_PTR>(GetMessageExtraInfo()) == SMART_PUNCTUATION_SENDINPUT_EXTRA_INFO)
{
*pIsEaten = FALSE;
return S_OK;
}
Global::UpdateModifiers(wParam, lParam);
if (_HasDeferredKeyBarrier())
+6
View File
@@ -988,6 +988,12 @@ HRESULT CMetasequoiaIME::_HandleCompositionPunctuation(TfEditCookie ec, _In_ ITf
{
HRESULT hr = S_OK;
PerfTimer timer;
if (_QueueRepeatedSmartPunctuationReplacement(wch))
{
return S_OK;
}
//
// Get punctuation char from composition processor engine
//