Release v0.6.43: fix forwarded input and verify snippet pastes

This commit is contained in:
2026-11-04 21:18:39 +02:00
parent a2b88f247c
commit 6621a49022
14 changed files with 239 additions and 33 deletions
+19
View File
@@ -94,6 +94,9 @@ struct InputRegressionAccess {
}
static void notificationSetup(App &a) { a.updateNotifier = notify; }
static void setup(App &a) { a.hookBindings = {{Toggle,{MOD_CONTROL,VK_F12}}, {Case,{MOD_CONTROL|MOD_SHIFT,'U'}}}; }
static LRESULT routedKey(int code, UINT message, const KBDLLHOOKSTRUCT &key, LRESULT routed) {
return App::keyboardAfterHooks(code,message,(LPARAM)&key,routed);
}
static bool updateTimer(App &a) {
a.updateRunning=true; // Exercise timer scheduling without launching a network request.
App::windowProc(a.window,WM_TIMER,5,0);
@@ -648,6 +651,22 @@ int main(int argc, char **argv) {
};
auto commands=[&] { MSG m{}; int n=0; while(PeekMessageW(&m,app.window,WM_HOTKEY,WM_HOTKEY,PM_REMOVE)) ++n; return n; };
auto f12=event(VK_F12);
{
const auto before = app.inputVersion.load();
const auto recorded = app.run.keys;
for (UINT message : {WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP})
require(ks::InputRegressionAccess::routedKey(HC_ACTION,message,event('A',30),7)==7,
"forwarded keyboard events preserve downstream suppression");
require(app.inputVersion==before && app.run.keys==recorded && commands()==0,
"forwarded events never track text, invalidate transactions or dispatch shortcuts");
require(ks::InputRegressionAccess::routedKey(-1,WM_KEYDOWN,event('A',30),0)==0 &&
app.inputVersion==before,"negative hook codes do not process keyboard input");
require(ks::InputRegressionAccess::routedKey(HC_ACTION,WM_KEYDOWN,event('A',30,true),0)==0 &&
app.inputVersion==before,"own input remains excluded after downstream routing");
require(ks::InputRegressionAccess::routedKey(HC_ACTION,WM_KEYDOWN,event('A',30),0)==0 &&
app.inputVersion>before,"delivered remote input is still processed on the receiver");
app.run.reset();
}
require(!app.handleKeyboard(event(VK_LCONTROL),true,0),"remote modifier passes through");
require(app.handleKeyboard(f12,true,MOD_CONTROL),"remote Ctrl+F12 handled");
require(app.handleKeyboard(f12,true,MOD_CONTROL),"remote repeat suppressed");
+58 -1
View File
@@ -33,6 +33,17 @@ bool trimCopiedSpaces = false;
int trimmedCopies = 0;
bool temporaryClipboardExcluded = true;
bool clipboardAccessTimedOut = false;
uint64_t forwardedVersion{};
int forwardedEvents{};
LRESULT CALLBACK forwardingHook(int code, WPARAM wp, LPARAM lp)
{
if (code == HC_ACTION && reinterpret_cast<KBDLLHOOKSTRUCT *>(lp)->dwExtraInfo == 456)
{
++forwardedEvents;
return 1; // Model MWB consuming source input routed to another machine.
}
return CallNextHookEx(nullptr, code, wp, lp);
}
LRESULT CALLBACK observeEnter(HWND w, UINT message, WPARAM wp, LPARAM lp, UINT_PTR, DWORD_PTR)
{
if (message == WM_KEYDOWN && wp == VK_LEFT && droppedSelectionKeys > 0 &&
@@ -577,10 +588,41 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
require(ks::readClipboard(app->window).text() == sentinel, "selected and repeated conversions restore clipboard");
finish();
break;
case 62:
saved = ks::readClipboard(app->window, 64 * 1024 * 1024);
captured = true;
app->ownSequence = ks::writeClipboard(app->window, ks::textClip(sentinel));
app->settings.snippets = {{L";r", L"unexpected{enter}"}};
ActivateKeyboardLayout(app->layouts[en].handle, 0);
prepare(L"prefix ", false);
forwardedVersion = app->inputVersion.load();
copyRequests = forwardedEvents = 0;
{
std::vector<INPUT> inputs;
for (WORD key : {WORD(VK_OEM_1), WORD('R')})
for (DWORD flags : {DWORD(0), DWORD(KEYEVENTF_KEYUP)})
{
auto input = ks::keyboardInput(key, flags);
input.ki.dwExtraInfo = 456;
inputs.push_back(input);
}
require(SendInput((UINT)inputs.size(),inputs.data(),sizeof(INPUT))==inputs.size(),
"forwarded snippet input submitted to real hook chain");
}
break;
case 63:
require(forwardedEvents==4,"downstream forwarding hook consumed every key event");
require(app->inputVersion==forwardedVersion && copyRequests==0,
"forwarded snippet does not start tracking or clipboard work on source computer");
require(text()==L"prefix " && ks::readClipboard(app->window).text()==sentinel,
"forwarded snippet leaves source editor and clipboard untouched");
stage = 30;
break;
case 30:
case 32:
case 34:
case 37:
case 60:
// 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.
@@ -609,6 +651,11 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
enterPresses = 0;
copyRequests = 0;
copyFailuresRemaining = stage == 38 ? 10 : stage == 33 ? 1 : 0;
if (stage == 61)
{
pasteBehavior = 2; // Editor inserts unrelated clipboard text.
app->settings.writeLog = false;
}
temporaryClipboardExcluded = true;
ActivateKeyboardLayout(app->layouts[en].handle, 0);
prepare(L"prefix ", false);
@@ -672,6 +719,13 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
require(ks::readClipboard(app->window).text() == sentinel,
"exhausted copy retries restore the clipboard");
copyFailuresRemaining = 0;
stage = 60;
break;
case 61:
require(text() == L"prefix other! x", "incorrect snippet paste preserves following typing");
require(enterPresses == 0, "unverified snippet never submits Enter even without logging");
require(ks::readClipboard(app->window).text() == sentinel, "rejected snippet restores clipboard");
pasteBehavior = 0;
finish();
break;
case 20:
@@ -985,9 +1039,12 @@ int main(int argc, char **argv)
if (typosOnly || (argc > 1 && std::string(argv[1]) == "--typing-only"))
stage = 20;
if (argc > 1 && std::string(argv[1]) == "--snippets-only")
stage = 30;
stage = 62;
SetTimer(host, 1, 100, nullptr);
auto forwarding = SetWindowsHookExW(WH_KEYBOARD_LL,forwardingHook,GetModuleHandleW(nullptr),0);
require(forwarding!=nullptr,"downstream forwarding fixture installed before application hook");
app->execute();
UnhookWindowsHookEx(forwarding);
}
std::filesystem::remove_all(root);
}
+18
View File
@@ -517,6 +517,24 @@ int main(int argc, char **argv)
auto encoded = ks::encodeClip(clip);
require(ks::decodeClip(encoded).text() == clip.text(), "clipboard Unicode roundtrip");
require(ks::decodeClip(encoded).formats[1].name == L"HTML Format", "registered format identity");
auto selection = ks::textClip(L";pa");
selection.formats.push_back({0, L"vscode-editor-data", {1, 2, 3}});
auto relayed = selection;
relayed.formats.front().data.resize(128, 0xab);
std::reverse(relayed.formats.begin(), relayed.formats.end());
require(ks::selectionClipboardIdentity(selection) == ks::selectionClipboardIdentity(relayed),
"clipboard relay tolerates Unicode allocation slack and format order");
relayed.formats.front().data[0] ^= 1;
require(ks::selectionClipboardIdentity(selection) != ks::selectionClipboardIdentity(relayed),
"clipboard relay rejects changed editor metadata");
relayed = selection;
relayed.formats.pop_back();
require(ks::selectionClipboardIdentity(selection) != ks::selectionClipboardIdentity(relayed),
"clipboard relay rejects missing editor metadata");
relayed = selection;
relayed.formats.front() = ks::textClip(L";pb").formats.front();
require(ks::selectionClipboardIdentity(selection) != ks::selectionClipboardIdentity(relayed),
"clipboard relay rejects changed Unicode text");
auto encrypted = ks::protect(encoded);
require(encrypted != encoded, "history ciphertext differs");
require(ks::unprotect(encrypted) == encoded, "Windows DPAPI roundtrip");