Add configurable offline layout correction and latest Windows release
This commit is contained in:
+103
-9
@@ -314,6 +314,11 @@ void App::applySettings(const Settings &s)
|
||||
{
|
||||
bool autostartChanged = false;
|
||||
validateSettings(s);
|
||||
if (s.autoCorrect)
|
||||
{
|
||||
AutoCorrector probe;
|
||||
probe.configure(std::filesystem::path(executable()).parent_path() / L"dictionaries", s.autoLanguages);
|
||||
}
|
||||
if (!registerHotkeys(s))
|
||||
{
|
||||
registerHotkeys(settings, true);
|
||||
@@ -348,6 +353,8 @@ void App::applySettings(const Settings &s)
|
||||
last.reset();
|
||||
++inputVersion;
|
||||
}
|
||||
// Clear on the worker so the next check reloads edits to dictionary files.
|
||||
enqueue([this] { autoCorrector.configure({}, {}); });
|
||||
SetThreadExecutionState(ES_CONTINUOUS |
|
||||
(settings.awake ? (ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED) : 0));
|
||||
}
|
||||
@@ -435,6 +442,9 @@ void App::keyboard(const KBDLLHOOKSTRUCT &k)
|
||||
return;
|
||||
}
|
||||
run.record(ch, fg, layout, GetTickCount64());
|
||||
if (settings.autoCorrect && ch == L' ' && run.keys.size() >= (size_t)settings.autoMinLength + 1 &&
|
||||
run.keys.size() <= 41 && !busy && !run.overflow)
|
||||
PostMessageW(window, AutoCorrectMessage, (WPARAM)inputVersion.load(), 0);
|
||||
if (busy || settings.snippets.empty())
|
||||
return;
|
||||
auto keys = lower(run.keys);
|
||||
@@ -611,6 +621,8 @@ void App::transform(int command, int snippet)
|
||||
{ return GetForegroundWindow() == target && inputVersion.load() == version; };
|
||||
for (int i = 0; i < 8 && !modifiersReleased(); ++i)
|
||||
Sleep(10);
|
||||
if (!modifiersReleased() && command == AutoCorrect)
|
||||
return;
|
||||
if (!modifiersReleased())
|
||||
releaseModifiers();
|
||||
if (!valid())
|
||||
@@ -624,9 +636,24 @@ void App::transform(int command, int snippet)
|
||||
return;
|
||||
if (to >= 0 && !available[to].convertible)
|
||||
{
|
||||
PostMessageW(target, WM_INPUTLANGCHANGEREQUEST, 0, (LPARAM)available[to].handle);
|
||||
switchWindowLayout(target, available[to].handle, valid);
|
||||
return;
|
||||
}
|
||||
if (command == AutoCorrect)
|
||||
{
|
||||
if (!config.autoCorrect || recent.window != target || recent.overflow ||
|
||||
recent.keys.empty() || recent.keys.back() != L' ' || hint < 0)
|
||||
return;
|
||||
autoCorrector.configure(std::filesystem::path(executable()).parent_path() /
|
||||
L"dictionaries",
|
||||
config.autoLanguages);
|
||||
std::wstring predicted;
|
||||
for (wchar_t key : recent.keys)
|
||||
predicted += key == L' ' ? L' ' : available[hint].at(physicalLayout().code(key));
|
||||
if (!autoCorrector.suggest(predicted, hint, available, config) ||
|
||||
!autoCorrectionAllowed(target, config) || !valid())
|
||||
return;
|
||||
}
|
||||
Transaction tx(*this, target, version);
|
||||
log(L"Transform target=" + processName(target) + L" command=" + std::to_wstring(command) +
|
||||
L" recent=" + std::to_wstring(config.recent) + L" keys=" +
|
||||
@@ -634,7 +661,36 @@ void App::transform(int command, int snippet)
|
||||
std::to_wstring(recent.window == target));
|
||||
std::wstring text, result;
|
||||
bool automatic = false;
|
||||
if (command == Undo)
|
||||
if (command == AutoCorrect)
|
||||
{
|
||||
// The editor must select the complete previous word. Unlike manual
|
||||
// conversion, automatic correction must never fall back to a suffix.
|
||||
if (!tx.valid() || !chord(VK_LEFT, MOD_CONTROL | MOD_SHIFT))
|
||||
return;
|
||||
Sleep(25);
|
||||
text = tx.copy();
|
||||
// Some editors first select only ") ". Extend by whole-word
|
||||
// boundaries, then still verify against the complete tracked run.
|
||||
for (int i = 0; i < 3 && autoClosingSelection(text, config) && tx.valid(); ++i)
|
||||
{
|
||||
if (!chord(VK_LEFT, MOD_CONTROL | MOD_SHIFT))
|
||||
break;
|
||||
Sleep(25);
|
||||
text = tx.copy();
|
||||
}
|
||||
hint = matchAutoInput(recent, text, available, physicalLayout(), config);
|
||||
auto suggestion = autoCorrector.suggest(text, hint, available, config);
|
||||
if (!suggestion || !tx.valid() || !autoCorrectionAllowed(target, config))
|
||||
{
|
||||
if (tx.valid())
|
||||
chord(VK_RIGHT);
|
||||
return;
|
||||
}
|
||||
to = suggestion->target;
|
||||
result = suggestion->text;
|
||||
automatic = true;
|
||||
}
|
||||
else if (command == Undo)
|
||||
{
|
||||
if (!previous || previous->window != target || previous->version != version ||
|
||||
GetTickCount64() - previous->at > 120000)
|
||||
@@ -649,6 +705,10 @@ void App::transform(int command, int snippet)
|
||||
return;
|
||||
}
|
||||
result = previous->original;
|
||||
if (previous->autoCorrected)
|
||||
{
|
||||
to = previous->sourceLayout;
|
||||
}
|
||||
automatic = true;
|
||||
}
|
||||
else if (snippet >= 0)
|
||||
@@ -710,10 +770,10 @@ void App::transform(int command, int snippet)
|
||||
if (text.empty())
|
||||
{
|
||||
if (to >= 0 && config.switchLayout)
|
||||
PostMessageW(target, WM_INPUTLANGCHANGEREQUEST, 0, (LPARAM)available[to].handle);
|
||||
switchWindowLayout(target, available[to].handle, valid);
|
||||
return;
|
||||
}
|
||||
if (command != Undo && snippet < 0)
|
||||
if (command != Undo && command != AutoCorrect && snippet < 0)
|
||||
{
|
||||
if (command == Case)
|
||||
result = cycleCase(text);
|
||||
@@ -750,6 +810,8 @@ void App::transform(int command, int snippet)
|
||||
sent = tx.paste(textClip(result));
|
||||
else if (automatic && tx.valid())
|
||||
chord(VK_RIGHT);
|
||||
if (sent && command == Undo && previous && previous->autoCorrected)
|
||||
autoCorrector.reject(previous->original, config);
|
||||
if (tx.valid() && (sent || automatic))
|
||||
{
|
||||
std::lock_guard lock(stateMutex);
|
||||
@@ -757,8 +819,8 @@ void App::transform(int command, int snippet)
|
||||
if (command == Undo || snippet >= 0)
|
||||
last.reset();
|
||||
else
|
||||
last = LastTransform{target, text, result, to >= 0 ? to : hint,
|
||||
GetTickCount64(), version};
|
||||
last = LastTransform{target, text, result, to >= 0 ? to : hint,
|
||||
GetTickCount64(), version, hint, command == AutoCorrect};
|
||||
}
|
||||
if (snippet >= 0 && sent && tx.valid())
|
||||
{
|
||||
@@ -770,8 +832,11 @@ void App::transform(int command, int snippet)
|
||||
chord(newlines(suffix) == newlines(t.suffix) ? VK_LEFT : VK_RIGHT);
|
||||
}
|
||||
}
|
||||
if (to >= 0 && config.switchLayout && tx.valid())
|
||||
PostMessageW(target, WM_INPUTLANGCHANGEREQUEST, 0, (LPARAM)available[to].handle);
|
||||
if (to >= 0 && config.switchLayout && tx.valid() && (sent || result == text))
|
||||
{
|
||||
bool switched = switchWindowLayout(target, available[to].handle, valid);
|
||||
log(L"Layout switch confirmed=" + std::to_wstring(switched));
|
||||
}
|
||||
log(L"Text operation completed in " + std::to_wstring(GetTickCount64() - start) + L" ms");
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
@@ -1002,12 +1067,20 @@ LRESULT CALLBACK App::windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
|
||||
case TrayMessage:
|
||||
if (LOWORD(lp) == WM_CONTEXTMENU)
|
||||
app->trayMenu();
|
||||
else if (LOWORD(lp) == WM_LBUTTONDBLCLK || LOWORD(lp) == NIN_KEYSELECT)
|
||||
else if (LOWORD(lp) == WM_LBUTTONDBLCLK)
|
||||
// The shell still owns the mouse gesture on the second button-down.
|
||||
// Activate after release, so the tray cannot take focus back on button-up.
|
||||
SetTimer(hwnd, 4, 50, nullptr);
|
||||
else if (LOWORD(lp) == NIN_KEYSELECT)
|
||||
app->dispatch(20); // Settings is the tray's default action.
|
||||
return 0;
|
||||
case WM_CLIPBOARDUPDATE:
|
||||
app->captureHistory();
|
||||
return 0;
|
||||
case AutoCorrectMessage:
|
||||
app->pendingAutoVersion = (uint64_t)wp;
|
||||
SetTimer(hwnd, 3, 250, nullptr);
|
||||
return 0;
|
||||
case SnippetMessage:
|
||||
app->transform(0, (int)wp);
|
||||
return 0;
|
||||
@@ -1040,6 +1113,21 @@ LRESULT CALLBACK App::windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
|
||||
return TRUE;
|
||||
}
|
||||
case WM_TIMER:
|
||||
if (wp == 4)
|
||||
{
|
||||
if (GetAsyncKeyState(VK_LBUTTON) & 0x8000)
|
||||
return 0;
|
||||
KillTimer(hwnd, 4);
|
||||
app->dispatch(20);
|
||||
return 0;
|
||||
}
|
||||
if (wp == 3)
|
||||
{
|
||||
KillTimer(hwnd, 3);
|
||||
if (app->settings.autoCorrect && app->inputVersion.load() == app->pendingAutoVersion)
|
||||
app->transform(AutoCorrect);
|
||||
return 0;
|
||||
}
|
||||
if (wp == 2)
|
||||
{
|
||||
PostMessageW(hwnd, WM_CLOSE, 0, 0);
|
||||
@@ -1127,6 +1215,12 @@ int App::execute()
|
||||
MSG msg;
|
||||
while (GetMessageW(&msg, nullptr, 0, 0) > 0)
|
||||
{
|
||||
if (settingsWindow && !IsWindowEnabled(settingsWindow))
|
||||
{
|
||||
HWND popup = GetLastActivePopup(settingsWindow);
|
||||
if (popup != settingsWindow && IsDialogMessageW(popup, &msg))
|
||||
continue;
|
||||
}
|
||||
if (settingsWindow && IsDialogMessageW(settingsWindow, &msg))
|
||||
continue;
|
||||
if (snippetsWindow && IsDialogMessageW(snippetsWindow, &msg))
|
||||
|
||||
Reference in New Issue
Block a user