// Run from an isolated fixture folder containing KSwitcher.exe and Screenshot.exe, // with the normal resident stopped. Never points at the installed user's data. #include "storage.h" #include "ocr.h" #include "json.hpp" #include #include #include static HWND editor{}; static bool pastedImage{}; static LRESULT CALLBACK targetProc(HWND w,UINT message,WPARAM wp,LPARAM lp) { if(message==WM_SETFOCUS && editor) {SetFocus(editor);return 0;} return DefWindowProcW(w,message,wp,lp); } static void require(bool ok,const char *what) {if(!ok)throw std::runtime_error(what);std::cout<<"PASS "< static void until(F condition,const char *what,DWORD timeout=10000) { auto end=GetTickCount64()+timeout; while(!condition()){if(GetTickCount64()>=end)throw std::runtime_error(what);pump();Sleep(10);} } static LRESULT send(HWND w,UINT message,WPARAM wp=0,LPARAM lp=0) { DWORD_PTR result{}; if(!SendMessageTimeoutW(w,message,wp,lp,SMTO_ABORTIFHUNG,5000,&result))throw std::runtime_error("window request timed out"); return LRESULT(result); } static void command(HWND resident,int cmd) { COPYDATASTRUCT data{1,sizeof(cmd),&cmd};send(resident,WM_COPYDATA,0,LPARAM(&data)); } static size_t memory(HANDLE process) { ks::Bytes bytes(1024*1024);auto ws=reinterpret_cast(bytes.data()); if(!QueryWorkingSet(process,ws,DWORD(bytes.size())))throw std::runtime_error("memory query failed"); size_t pages=0;for(size_t i=0;iNumberOfEntries;++i)if(!ws->WorkingSetInfo[i].Shared)++pages; return pages*4096; } static std::wstring editorText() {wchar_t text[256]{};GetWindowTextW(editor,text,256);return text;} static LRESULT CALLBACK editProc(HWND w,UINT message,WPARAM wp,LPARAM lp,UINT_PTR,DWORD_PTR) { if(message==WM_PASTE && IsClipboardFormatAvailable(CF_DIB)) {pastedImage=true;return 0;} return DefSubclassProc(w,message,wp,lp); } static ks::Clip excluded(ks::Clip clip) { clip.formats.push_back({0,L"ExcludeClipboardContentFromMonitorProcessing",ks::Bytes{0}});return clip; } int main() { CoInitializeEx(nullptr,COINIT_APARTMENTTHREADED); PROCESS_INFORMATION process{};HWND resident{},target{};ks::Clip saved;bool captured=false;int result=0; try { auto folder=std::filesystem::path(ks::executable()).parent_path(); require(folder.filename().wstring().starts_with(L"history-host-fixture-"),"isolated fixture directory"); require(!FindWindowW(L"KSwitcher.Main",nullptr),"normal resident is stopped"); WNDCLASSW targetClass{};targetClass.lpfnWndProc=targetProc;targetClass.hInstance=GetModuleHandleW(nullptr); targetClass.lpszClassName=L"KSwitcher.HistoryTestDestination";RegisterClassW(&targetClass); target=CreateWindowExW(0,targetClass.lpszClassName,L"KSwitcher history test destination",WS_OVERLAPPEDWINDOW|WS_VISIBLE, 50,50,500,250,nullptr,nullptr,nullptr,nullptr); editor=CreateWindowExW(0,L"EDIT",L"",WS_CHILD|WS_VISIBLE|ES_MULTILINE,10,10,450,180,target,nullptr,nullptr,nullptr); SetWindowSubclass(editor,editProc,1,0); saved=ks::readClipboard(target,128*1024*1024);captured=true; ks::writeClipboard(target,excluded(ks::textClip(L"fixture clipboard"))); auto settings=ks::loadSettings(folder/L"missing.json",ks::readLayouts()); settings.autoCorrect=false;settings.writeLog=true;settings.history=true;settings.maxItems=500;settings.maxMiB=100; std::filesystem::create_directories(folder/L"data"); ks::saveSettings(folder/L"data/settings.json",settings); ks::History history(folder/L"data/history"); history.clear(); // This executable only runs in its dedicated fixture directory. ks::Pixels pixels{1566,1068,ks::Bytes(1566*1068*4,255)}; auto image=ks::Clip{{ks::imageDib(pixels)}}; history.add(image,L"test",settings); auto imageId=history.entries().front().id; // Seed once rather than rewriting the encrypted index 500 times. This // also avoids measuring fixture creation in the resident under test. auto indexPath=folder/L"data/history/index.dat"; auto rows=nlohmann::json::parse(ks::unprotect(ks::readFile(indexPath,1024*1024))); for(int i=0;i<499;++i) { auto id=ks::lower(ks::newSnippetId().substr(1,36)),text=L"History fixture "+std::to_wstring(i); auto bytes=ks::protect(ks::encodeClip(ks::textClip(text))); ks::atomicWrite(folder/L"data/history"/(id+L".dat"),bytes); rows.insert(rows.begin(),nlohmann::json{{"id",ks::utf8(id)},{"preview",ks::utf8(text)}, {"app","test"},{"bytes",bytes.size()},{"at",i+1},{"pinned",false},{"image",false},{"files",false}}); } auto encoded=rows.dump();ks::atomicWrite(indexPath,ks::protect(ks::Bytes(encoded.begin(),encoded.end()))); history.reload(); auto textId=history.entries().front().id; auto exe=folder/L"KSwitcher.exe";auto line=L"\""+exe.wstring()+L"\""; STARTUPINFOW startup{sizeof(startup)};startup.dwFlags=STARTF_USESHOWWINDOW;startup.wShowWindow=SW_HIDE; require(CreateProcessW(exe.c_str(),line.data(),nullptr,nullptr,FALSE,CREATE_NO_WINDOW,nullptr,folder.c_str(),&startup,&process),"start isolated resident"); CloseHandle(process.hThread);process.hThread=nullptr; until([&]{resident=FindWindowW(L"KSwitcher.Main",nullptr);return resident!=nullptr;},"resident startup"); auto base=memory(process.hProcess); std::string forged="{\"action\":\"clear\",\"id\":\"\",\"plain\":false}"; COPYDATASTRUCT forgedRequest{0x4b530200,DWORD(forged.size()),forged.data()}; require(send(resident,WM_COPYDATA,WPARAM(target),LPARAM(&forgedRequest))==0, "history rejects requests from an untrusted process"); auto open=[&] { require(ks::activateWindow(target),"destination foreground");SetFocus(editor);command(resident,5); HWND window{}; until([&]{window=FindWindowW(L"KSwitcher.History",nullptr);return window&&IsWindowVisible(window);},"history startup"); until([&]{return send(GetDlgItem(window,102),LB_GETCOUNT)>0;},"history rows"); DWORD pid{};GetWindowThreadProcessId(window,&pid); require(pid!=process.dwProcessId,"history belongs to a separate process"); return window; }; auto close=[&](HWND window) { DWORD pid{};GetWindowThreadProcessId(window,&pid);HANDLE child=OpenProcess(SYNCHRONIZE,FALSE,pid); PostMessageW(window,WM_KEYDOWN,VK_ESCAPE,0); // Escape also works with focus on the window itself. until([&]{return WaitForSingleObject(child,0)==WAIT_OBJECT_0;},"history helper exits after Escape"); CloseHandle(child); require(!FindWindowW(L"KSwitcher.History",nullptr),"Escape destroys history window"); }; for(int i=0;i<3;++i){auto w=open();until([&]{return send(GetDlgItem(w,102),LB_GETCOUNT)==500;},"all history rows loaded");require(true,"all history entries visible");close(w);} auto idle=memory(process.hProcess); std::cout<<"MEMORY resident_before="<=nextExit) { command(resident,99);nextExit=GetTickCount64()+500; } return false; },"resident accepts exit after startup update check",30000); until([&]{return WaitForSingleObject(child,0)==WAIT_OBJECT_0;},"resident shutdown closes history helper"); CloseHandle(child);resident=nullptr; std::cout<<"PASS isolated history host suite\n"; } catch(const std::exception &e){std::cerr<<"FAIL "<