Release v0.6.19: retry dropped copies and stabilize snippet integration
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
cmake_minimum_required(VERSION 3.24)
|
cmake_minimum_required(VERSION 3.24)
|
||||||
project(KSwitcher VERSION 0.6.18 LANGUAGES CXX RC)
|
project(KSwitcher VERSION 0.6.19 LANGUAGES CXX RC)
|
||||||
if(NOT WIN32)
|
if(NOT WIN32)
|
||||||
message(FATAL_ERROR "KSwitcher uses native Windows APIs")
|
message(FATAL_ERROR "KSwitcher uses native Windows APIs")
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -226,11 +226,16 @@ thumbnails and source labels aligned with the scaled fonts and spacing.
|
|||||||
If the editor's word boundary differs from the recorded typing, it falls back to paced character
|
If the editor's word boundary differs from the recorded typing, it falls back to paced character
|
||||||
selection: Teams can coalesce a burst of Shift+Left events into just one character.
|
selection: Teams can coalesce a burst of Shift+Left events into just one character.
|
||||||
- Clipboard snapshot/restoration with concurrent-copy, foreground and input-version checks.
|
- Clipboard snapshot/restoration with concurrent-copy, foreground and input-version checks.
|
||||||
|
A silently dropped copy is retried up to three total attempts while the clipboard and input
|
||||||
|
context remain unchanged. Temporary transaction contents are excluded from Windows clipboard
|
||||||
|
history and cloud synchronization. Paste is not blindly retried.
|
||||||
- Case cycling, Russian/Ukrainian transliteration, verified undo and repeated conversion.
|
- Case cycling, Russian/Ukrainian transliteration, verified undo and repeated conversion.
|
||||||
- Text snippets across layouts, multiline insertion and a verified `{cursor}` position;
|
- Text snippets across layouts, multiline insertion and a verified `{cursor}` position;
|
||||||
`{enter}` presses Enter after insertion and cursor placement (once per marker), before buffered typing resumes.
|
`{enter}` presses Enter after insertion and cursor placement (once per marker), before buffered typing resumes.
|
||||||
For example, `rider .{enter}` inserts `rider .` and presses Enter.
|
For example, `rider .{enter}` inserts `rider .` and presses Enter.
|
||||||
Automatic correction is suppressed during snippet expansion and for typing buffered by that expansion; the global setting is preserved.
|
Automatic correction is suppressed during snippet expansion and for typing buffered by that expansion; the global setting is preserved.
|
||||||
|
If the trigger cannot be verified after the copy attempts, it is preserved, queued typing resumes,
|
||||||
|
and no Enter action is sent.
|
||||||
`{{cursor}}` and `{{enter}}` insert literal markers. Snippets are stored as plaintext settings, as in [Typlo](https://github.com/bitcodepro/Typlo).
|
`{{cursor}}` and `{{enter}}` insert literal markers. Snippets are stored as plaintext settings, as in [Typlo](https://github.com/bitcodepro/Typlo).
|
||||||
- Clipboard history for text, HTML/RTF, file lists, DIB and PNG images; plain-text paste;
|
- Clipboard history for text, HTML/RTF, file lists, DIB and PNG images; plain-text paste;
|
||||||
full-text search, pinning, deletion, full text viewing and image previews.
|
full-text search, pinning, deletion, full text viewing and image previews.
|
||||||
|
|||||||
Binary file not shown.
+9
-9
@@ -1,18 +1,18 @@
|
|||||||
# Latest Windows release
|
# Latest Windows release
|
||||||
|
|
||||||
## v0.6.18 — 2026-10-31
|
## v0.6.19 — 2026-10-31
|
||||||
|
|
||||||
## Changes
|
## Changes
|
||||||
- Verify the actual editor text after manual conversion using read-only Windows Accessibility, without moving the caret, changing selection or copying again. Record unchanged calculations, original text remaining, unexpected readback and unavailable verification separately.
|
- Retry a silently dropped Ctrl+C up to three total attempts, only while the transaction clipboard remains unchanged and the original input context is still valid. Failed snippet verification preserves the trigger and following input without sending Enter.
|
||||||
- Replace the event-only diagnostic list with sortable Time, Program, Command, Source layout, Target layout and Result columns; add program/command/result filters and a separate panel with original, expected and read-back text and operation context.
|
- Exclude temporary transaction clipboard contents from Windows clipboard history and cloud synchronization. The original clipboard snapshot is still restored after the operation when ownership permits.
|
||||||
- Keep successful operations only in a bounded in-memory context (40 events, 30 seconds). Only problem operations create file rows; verified successes do not fill the log. Diagnostics work in normal Release builds and the additional readback check is skipped when logging is disabled.
|
- Fix snippet integration test isolation: restore a multiline editor after earlier single-line scenarios, reset layout/correction state, coordinate test-editor clipboard access and disable unrelated background update checks.
|
||||||
- Refresh English and Ukrainian Wiki documentation through v0.6.18, including previously undocumented snippet, updater and constrained-pencil changes since v0.6.14.
|
- Add deterministic coverage for one dropped copy and exhausted retries. Keep exact assertions for Enter ordering, buffered typing, clipboard restoration and subsequent auto-correction.
|
||||||
|
|
||||||
## Limitations
|
|
||||||
Some editors do not expose their text through Windows Accessibility. Changed focus, protected or unsupported controls, provider-specific character units, delayed text updates and the 4096-character readback limit can prevent verification; these cases are recorded as unverified rather than successful. Historical unstructured entries remain readable without retroactive filter fields.
|
|
||||||
|
|
||||||
## Validation
|
## Validation
|
||||||
Passed 230 native regression checks, 472 input/UI checks, screenshot editor UI and notification registration checks, 48 updater checks, updater progress checks, 45 isolated configurator process checks, conversion-specific editor integration and diagnostic viewer checks. Verified that 1000 successful operations create no log file and that a subsequent failure creates one row with bounded context. The broader editor integration suite still intermittently fails in snippet Enter/concurrent-input scenarios; similar failures reproduce with the pre-change code.
|
The fixes passed 230 native regression checks, 472 input/UI checks, screenshot editor UI and notification registration checks, 48 updater checks and updater progress checks. The full editor integration suite passed all 63 assertions; conversion-specific diagnostics also passed. The final snippet suite passed 20 consecutive runs, including injected copy failures and the three-attempt limit. The v0.6.19 staged Release build also passed the native, input/UI, screenshot and updater checks; the installed application was not replaced or restarted.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
The fix does not retry paste blindly: an editor may already have changed its selection or text. Clipboard contention, editor capabilities and input privileges can still prevent an operation. Existing accessibility readback limitations and problem-only diagnostic logging remain unchanged.
|
||||||
|
|
||||||
**Upgrade:** choose Update in the tray menu. Legacy v0.3.0–v0.4.1 clients first receive the v0.4.2 compatibility bridge, then the current full release. v0.2.3 and earlier require manual installation.
|
**Upgrade:** choose Update in the tray menu. Legacy v0.3.0–v0.4.1 clients first receive the v0.4.2 compatibility bridge, then the current full release. v0.2.3 and earlier require manual installation.
|
||||||
For manual installation, exit KSwitcher and extract the entire ZIP into its installation folder. Keep personal `data/` and customized dictionaries.
|
For manual installation, exit KSwitcher and extract the entire ZIP into its installation folder. Keep personal `data/` and customized dictionaries.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
3e76d8a566c623a3084c00cc909f4cc7c8a6edb38f9c06adbb32edfd4cfcb5b7 KSwitcher-win-x64.zip
|
8b2008e97ca625b7eebdf509c038d6d1f6c5c853dce6da1b939abaa18223e26d KSwitcher-win-x64.zip
|
||||||
|
|||||||
+3
-3
@@ -9,8 +9,8 @@ IDC_SCREENSHOT_PENCIL CURSOR "icons/lucide/pencil.cur"
|
|||||||
1 RT_MANIFEST "app.manifest"
|
1 RT_MANIFEST "app.manifest"
|
||||||
201 RCDATA "update.ps1"
|
201 RCDATA "update.ps1"
|
||||||
1 VERSIONINFO
|
1 VERSIONINFO
|
||||||
FILEVERSION 0,6,18,0
|
FILEVERSION 0,6,19,0
|
||||||
PRODUCTVERSION 0,6,18,0
|
PRODUCTVERSION 0,6,19,0
|
||||||
FILEOS VOS_NT_WINDOWS32
|
FILEOS VOS_NT_WINDOWS32
|
||||||
FILETYPE VFT_APP
|
FILETYPE VFT_APP
|
||||||
BEGIN
|
BEGIN
|
||||||
@@ -20,7 +20,7 @@ BEGIN
|
|||||||
BEGIN
|
BEGIN
|
||||||
VALUE "FileDescription", "KSwitcher — native keyboard and clipboard utility"
|
VALUE "FileDescription", "KSwitcher — native keyboard and clipboard utility"
|
||||||
VALUE "ProductName", "KSwitcher"
|
VALUE "ProductName", "KSwitcher"
|
||||||
VALUE "FileVersion", "0.6.18"
|
VALUE "FileVersion", "0.6.19"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|||||||
+24
-1
@@ -124,7 +124,13 @@ class Transaction
|
|||||||
{
|
{
|
||||||
check();
|
check();
|
||||||
adopted.clear();
|
adopted.clear();
|
||||||
app.ownSequence = expected = writeClipboard(app.window, c);
|
// These are temporary transaction contents, not a user's clipboard entry.
|
||||||
|
// In particular, do not invite the Windows history service to read our
|
||||||
|
// sentinel while the target editor is trying to open the clipboard.
|
||||||
|
auto temporary = c;
|
||||||
|
const std::wstring exclusion = L"ExcludeClipboardContentFromMonitorProcessing";
|
||||||
|
temporary.formats.push_back({RegisterClipboardFormatW(exclusion.c_str()), exclusion, Bytes{0}});
|
||||||
|
app.ownSequence = expected = writeClipboard(app.window, temporary);
|
||||||
// A concurrent writer can win immediately after CloseClipboard, before we read its sequence.
|
// A concurrent writer can win immediately after CloseClipboard, before we read its sequence.
|
||||||
// Never adopt that writer's sequence as our own and paste its content.
|
// Never adopt that writer's sequence as our own and paste its content.
|
||||||
if (GetClipboardOwner() != app.window)
|
if (GetClipboardOwner() != app.window)
|
||||||
@@ -146,6 +152,8 @@ class Transaction
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
ULONGLONG deadline = GetTickCount64() + 2000;
|
ULONGLONG deadline = GetTickCount64() + 2000;
|
||||||
|
ULONGLONG retryAt = GetTickCount64() + 250;
|
||||||
|
unsigned attempts = 1;
|
||||||
DWORD changed = before;
|
DWORD changed = before;
|
||||||
ULONGLONG stable = 0;
|
ULONGLONG stable = 0;
|
||||||
bool changedAgain = false;
|
bool changedAgain = false;
|
||||||
@@ -162,6 +170,21 @@ class Transaction
|
|||||||
}
|
}
|
||||||
if (changed != before && GetTickCount64() - stable >= 60)
|
if (changed != before && GetTickCount64() - stable >= 60)
|
||||||
break;
|
break;
|
||||||
|
// Native editors can silently drop Ctrl+C if another process has
|
||||||
|
// the clipboard open. Retry only while our sentinel is untouched;
|
||||||
|
// never overwrite a copy result or another application's content.
|
||||||
|
if (changed == before && attempts < 3 && GetTickCount64() >= retryAt &&
|
||||||
|
!GetOpenClipboardWindow())
|
||||||
|
{
|
||||||
|
check();
|
||||||
|
if (!valid() || GetClipboardSequenceNumber() != before)
|
||||||
|
return {};
|
||||||
|
if (!chord(L'C', MOD_CONTROL))
|
||||||
|
return {};
|
||||||
|
diagnosticEvent(L"Copy retry: clipboard unchanged after Ctrl+C");
|
||||||
|
++attempts;
|
||||||
|
retryAt = GetTickCount64() + 250;
|
||||||
|
}
|
||||||
Sleep(10);
|
Sleep(10);
|
||||||
}
|
}
|
||||||
if (changed == before || !valid())
|
if (changed == before || !valid())
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
namespace ks
|
namespace ks
|
||||||
{
|
{
|
||||||
inline constexpr wchar_t AppVersion[] = L"0.6.18";
|
inline constexpr wchar_t AppVersion[] = L"0.6.19";
|
||||||
inline constexpr UINT UpdateMessage = WM_APP + 7;
|
inline constexpr UINT UpdateMessage = WM_APP + 7;
|
||||||
inline constexpr UINT UpdateCheckIntervalMs = 6 * 60 * 60 * 1000;
|
inline constexpr UINT UpdateCheckIntervalMs = 6 * 60 * 60 * 1000;
|
||||||
inline constexpr UINT UpdateNotificationRetryMs = 5 * 60 * 1000;
|
inline constexpr UINT UpdateNotificationRetryMs = 5 * 60 * 1000;
|
||||||
|
|||||||
+83
-1
@@ -25,8 +25,51 @@ bool captured = false, failed = false;
|
|||||||
bool conversionOnly = false;
|
bool conversionOnly = false;
|
||||||
int pasteBehavior = 0;
|
int pasteBehavior = 0;
|
||||||
int enterPresses = 0;
|
int enterPresses = 0;
|
||||||
|
int copyFailuresRemaining = 0, copyRequests = 0;
|
||||||
|
bool temporaryClipboardExcluded = true;
|
||||||
|
bool clipboardAccessTimedOut = false;
|
||||||
LRESULT CALLBACK observeEnter(HWND w, UINT message, WPARAM wp, LPARAM lp, UINT_PTR, DWORD_PTR)
|
LRESULT CALLBACK observeEnter(HWND w, UINT message, WPARAM wp, LPARAM lp, UINT_PTR, DWORD_PTR)
|
||||||
{
|
{
|
||||||
|
// This bare EDIT control is our test fixture, not a full editor with
|
||||||
|
// clipboard retries. Keep clipboard access on its owning UI thread while
|
||||||
|
// it handles Ctrl+C/Ctrl+V, so unrelated Windows clipboard listeners cannot
|
||||||
|
// make the fixture silently discard the operation under test.
|
||||||
|
struct ClipboardAccess
|
||||||
|
{
|
||||||
|
bool opened = false;
|
||||||
|
~ClipboardAccess() { if (opened) CloseClipboard(); }
|
||||||
|
} access;
|
||||||
|
if (message == WM_CHAR && (wp == 3 || wp == 22))
|
||||||
|
{
|
||||||
|
auto deadline = GetTickCount64() + 1000;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (OpenClipboard(w)) { access.opened = true; break; }
|
||||||
|
Sleep(10);
|
||||||
|
} while (GetTickCount64() < deadline);
|
||||||
|
if (!access.opened)
|
||||||
|
{
|
||||||
|
clipboardAccessTimedOut = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((message == WM_CHAR && wp == 3) || message == WM_PASTE)
|
||||||
|
{
|
||||||
|
temporaryClipboardExcluded &= IsClipboardFormatAvailable(
|
||||||
|
RegisterClipboardFormatW(L"ExcludeClipboardContentFromMonitorProcessing")) != FALSE;
|
||||||
|
if (message == WM_CHAR)
|
||||||
|
{
|
||||||
|
++copyRequests;
|
||||||
|
// Native EDIT silently ignores Ctrl+C when OpenClipboard fails.
|
||||||
|
// Reproduce that outcome deterministically, without depending on
|
||||||
|
// the timing of the Windows clipboard-history service.
|
||||||
|
if (copyFailuresRemaining > 0)
|
||||||
|
{
|
||||||
|
--copyFailuresRemaining;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (message == WM_PASTE && pasteBehavior)
|
if (message == WM_PASTE && pasteBehavior)
|
||||||
{
|
{
|
||||||
if (pasteBehavior == 2) SendMessageW(w, EM_REPLACESEL, TRUE, (LPARAM)L"other!");
|
if (pasteBehavior == 2) SendMessageW(w, EM_REPLACESEL, TRUE, (LPARAM)L"other!");
|
||||||
@@ -51,13 +94,15 @@ std::string diagnosticLog()
|
|||||||
size_t diagnosticStart{};
|
size_t diagnosticStart{};
|
||||||
void require(bool ok, const char *name)
|
void require(bool ok, const char *name)
|
||||||
{
|
{
|
||||||
if (!ok)
|
if (!ok || clipboardAccessTimedOut)
|
||||||
{
|
{
|
||||||
wchar_t observed[2048]{};
|
wchar_t observed[2048]{};
|
||||||
GetWindowTextW(field, observed, 2048);
|
GetWindowTextW(field, observed, 2048);
|
||||||
std::cerr << "Editor at failure: " << ks::utf8(observed)
|
std::cerr << "Editor at failure: " << ks::utf8(observed)
|
||||||
<< "; foregroundSame=" << (GetForegroundWindow() == host)
|
<< "; foregroundSame=" << (GetForegroundWindow() == host)
|
||||||
<< "; inputVersion=" << app->inputVersion.load() << '\n';
|
<< "; inputVersion=" << app->inputVersion.load() << '\n';
|
||||||
|
if (clipboardAccessTimedOut)
|
||||||
|
throw std::runtime_error("Test editor could not acquire clipboard within 1000 ms");
|
||||||
throw std::runtime_error(name);
|
throw std::runtime_error(name);
|
||||||
}
|
}
|
||||||
std::cout << "PASS " << name << '\n';
|
std::cout << "PASS " << name << '\n';
|
||||||
@@ -109,6 +154,9 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
|||||||
return 0;
|
return 0;
|
||||||
if (msg == WM_TIMER)
|
if (msg == WM_TIMER)
|
||||||
{
|
{
|
||||||
|
// Input tests must not start a network update check. Its close veto can
|
||||||
|
// otherwise leave a completed test running after the editor is gone.
|
||||||
|
if (app->window) KillTimer(app->window, 5);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (GetTickCount64() > deadline)
|
if (GetTickCount64() > deadline)
|
||||||
@@ -257,6 +305,7 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
|||||||
DestroyWindow(field);
|
DestroyWindow(field);
|
||||||
field = CreateWindowW(L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_PASSWORD, 20, 20,
|
field = CreateWindowW(L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_PASSWORD, 20, 20,
|
||||||
590, 30, host, nullptr, GetModuleHandleW(nullptr), nullptr);
|
590, 30, host, nullptr, GetModuleHandleW(nullptr), nullptr);
|
||||||
|
SetWindowSubclass(field, observeEnter, 1, 0);
|
||||||
prepare(L"ghbdtn ", false);
|
prepare(L"ghbdtn ", false);
|
||||||
record(L"ghbdtn ");
|
record(L"ghbdtn ");
|
||||||
app->transform(ks::AutoCorrect);
|
app->transform(ks::AutoCorrect);
|
||||||
@@ -268,6 +317,7 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
|||||||
DestroyWindow(field);
|
DestroyWindow(field);
|
||||||
field = CreateWindowW(L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_BORDER, 20, 20, 590, 30, host,
|
field = CreateWindowW(L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_BORDER, 20, 20, 590, 30, host,
|
||||||
nullptr, GetModuleHandleW(nullptr), nullptr);
|
nullptr, GetModuleHandleW(nullptr), nullptr);
|
||||||
|
SetWindowSubclass(field, observeEnter, 1, 0);
|
||||||
prepare(L"(ljvf ", false);
|
prepare(L"(ljvf ", false);
|
||||||
record(L"(ljvf ");
|
record(L"(ljvf ");
|
||||||
app->transform(ks::AutoCorrect);
|
app->transform(ks::AutoCorrect);
|
||||||
@@ -347,6 +397,18 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
|||||||
case 30:
|
case 30:
|
||||||
case 32:
|
case 32:
|
||||||
case 34:
|
case 34:
|
||||||
|
case 37:
|
||||||
|
// Earlier password/word tests replace the original multiline
|
||||||
|
// editor with a single-line EDIT. Restore the snippet fixture:
|
||||||
|
// a single-line control cannot insert the tested Enter action.
|
||||||
|
if (!(GetWindowLongPtrW(field, GWL_STYLE) & ES_MULTILINE))
|
||||||
|
{
|
||||||
|
DestroyWindow(field);
|
||||||
|
field = CreateWindowW(L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_BORDER |
|
||||||
|
ES_MULTILINE | ES_WANTRETURN, 20, 20, 590, 230, host, nullptr,
|
||||||
|
GetModuleHandleW(nullptr), nullptr);
|
||||||
|
SetWindowSubclass(field, observeEnter, 1, 0);
|
||||||
|
}
|
||||||
if (!captured)
|
if (!captured)
|
||||||
{
|
{
|
||||||
saved = ks::readClipboard(app->window, 64 * 1024 * 1024);
|
saved = ks::readClipboard(app->window, 64 * 1024 * 1024);
|
||||||
@@ -355,9 +417,16 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
|||||||
app->ownSequence = ks::writeClipboard(app->window, ks::textClip(sentinel));
|
app->ownSequence = ks::writeClipboard(app->window, ks::textClip(sentinel));
|
||||||
app->settings.autoCorrect = stage == 35;
|
app->settings.autoCorrect = stage == 35;
|
||||||
app->settings.autoLanguages = {L"en", L"ru"};
|
app->settings.autoLanguages = {L"en", L"ru"};
|
||||||
|
app->settings.cycle = {app->layouts[en].id, app->layouts[ru].id};
|
||||||
|
app->settings.switchLayout = false;
|
||||||
|
app->autoCorrector = ks::AutoCorrector{};
|
||||||
|
app->autoPredictor = ks::AutoCorrector{};
|
||||||
app->autoPredictor.configure(L"dictionaries", app->settings.autoLanguages);
|
app->autoPredictor.configure(L"dictionaries", app->settings.autoLanguages);
|
||||||
app->settings.snippets = {{L";r", stage == 31 ? L"rider .{cursor}" : L"rider .{cursor}{enter}"}};
|
app->settings.snippets = {{L";r", stage == 31 ? L"rider .{cursor}" : L"rider .{cursor}{enter}"}};
|
||||||
enterPresses = 0;
|
enterPresses = 0;
|
||||||
|
copyRequests = 0;
|
||||||
|
copyFailuresRemaining = stage == 38 ? 10 : stage == 33 ? 1 : 0;
|
||||||
|
temporaryClipboardExcluded = true;
|
||||||
ActivateKeyboardLayout(app->layouts[en].handle, 0);
|
ActivateKeyboardLayout(app->layouts[en].handle, 0);
|
||||||
prepare(L"prefix ", false);
|
prepare(L"prefix ", false);
|
||||||
{
|
{
|
||||||
@@ -384,6 +453,10 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
|||||||
break;
|
break;
|
||||||
case 33:
|
case 33:
|
||||||
require(text() == L"prefix rider .\r\n x", "Enter action precedes buffered typing");
|
require(text() == L"prefix rider .\r\n x", "Enter action precedes buffered typing");
|
||||||
|
require(copyRequests == 2 && copyFailuresRemaining == 0,
|
||||||
|
"snippet retries a silently dropped copy without duplicating input");
|
||||||
|
require(temporaryClipboardExcluded,
|
||||||
|
"temporary snippet clipboard contents are excluded from Windows history");
|
||||||
require(enterPresses == 1, "Enter marker sends an actual Enter key press");
|
require(enterPresses == 1, "Enter marker sends an actual Enter key press");
|
||||||
require(ks::readClipboard(app->window).text() == sentinel, "Enter snippet restores clipboard");
|
require(ks::readClipboard(app->window).text() == sentinel, "Enter snippet restores clipboard");
|
||||||
stage = 34;
|
stage = 34;
|
||||||
@@ -407,6 +480,15 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
|||||||
break;
|
break;
|
||||||
case 36:
|
case 36:
|
||||||
require(text() == L"prefix rider .\r\nghbdtn привет ", "automatic correction resumes for subsequent normal typing");
|
require(text() == L"prefix rider .\r\nghbdtn привет ", "automatic correction resumes for subsequent normal typing");
|
||||||
|
stage = 37;
|
||||||
|
break;
|
||||||
|
case 38:
|
||||||
|
require(copyRequests == 3, "copy retries are bounded to three attempts");
|
||||||
|
require(text() == L"prefix ;r x" && enterPresses == 0,
|
||||||
|
"exhausted copy retries preserve trigger and following input without Enter");
|
||||||
|
require(ks::readClipboard(app->window).text() == sentinel,
|
||||||
|
"exhausted copy retries restore the clipboard");
|
||||||
|
copyFailuresRemaining = 0;
|
||||||
finish();
|
finish();
|
||||||
break;
|
break;
|
||||||
case 20:
|
case 20:
|
||||||
|
|||||||
Reference in New Issue
Block a user