// Interactive clipboard test. Run with the resident stopped; the original // clipboard is restored. The helper must be beside this test executable. #include "storage.h" #include "ocr.h" #include #include static void require(bool ok,const char *name) { if(!ok) throw std::runtime_error(name); std::cout << "PASS " << name << '\n'; } int main() { CoInitializeEx(nullptr,COINIT_MULTITHREADED); HWND owner=CreateWindowExW(0,L"STATIC",L"Capture test",0,0,0,0,0,HWND_MESSAGE,nullptr,nullptr,nullptr); ks::Clip saved; bool captured=false; int result=0; auto folder=std::filesystem::temp_directory_path()/(L"KSwitcher-image-capture-"+std::to_wstring(GetCurrentProcessId())); try { saved=ks::readClipboard(owner,128*1024*1024); captured=true; ks::Settings settings; settings.history=true; ks::History history(folder); history.add(ks::textClip(L"keep pinned"),L"test",settings); auto pinned=history.entries().front().id; history.pin(pinned); ks::Pixels pixels{32,16,ks::Bytes(32*16*4,255)}; ks::Clip image{{ks::imageDib(pixels),{0,L"PNG",ks::encodePng(pixels)}}}; auto sequence=ks::writeClipboard(owner,image); require(history.captureImage(sequence,L"capture test",settings),"helper captures image"); auto entries=history.entries(); require(entries.size()==2 && entries.front().image && entries.front().ocrState==1,"resident reloads metadata with pending OCR"); require(entries.front().contentHash.size()==64 && !entries.front().pixelHash.empty(), "helper persists fingerprints and resident reloads them"); require(entries.back().id==pinned && entries.back().pinned,"helper preserves existing pinned entry"); auto restored=history.load(entries.front().id); auto has=[&](const ks::Format &expected) { return std::any_of(restored.formats.begin(),restored.formats.end(),[&](const ks::Format &actual) { return (expected.name.empty()?actual.id==expected.id:actual.name==expected.name) && actual.data==expected.data; }); }; require(has(image.formats[0]) && has(image.formats[1]), "encrypted history preserves both image representations"); require(ks::readFile(folder/(entries.front().id+L".dat"),1024*1024)!=ks::encodeClip(restored),"saved image is encrypted"); require(GetClipboardSequenceNumber()==sequence,"capture leaves clipboard unchanged"); require(history.captureImage(sequence,L"capture test",settings) && history.entries().size()==2,"duplicate capture is not added"); ks::writeClipboard(owner,ks::textClip(L"new clipboard")); require(!history.captureImage(sequence,L"stale",settings) && history.entries().size()==2,"stale request does not save another clipboard"); for(auto name : {L"ExcludeClipboardContentFromMonitorProcessing",L"Clipboard Viewer Ignore",L"CanIncludeInClipboardHistory"}) { auto blocked=image; blocked.formats.push_back({0,name,ks::Bytes(4,0)}); sequence=ks::writeClipboard(owner,blocked); require(!history.captureImage(sequence,L"excluded",settings) && history.entries().size()==2,"clipboard privacy flag prevents capture"); } pixels={1024,512,ks::Bytes(1024*512*4,255)}; sequence=ks::writeClipboard(owner,ks::Clip{{ks::imageDib(pixels)}}); settings.maxImageMiB=1; bool rejected=false; try {history.captureImage(sequence,L"oversized",settings);} catch(const std::exception &e) { rejected=std::string(e.what())=="Clipboard exceeds the configured size limit"; } require(rejected && history.entries().size()==2,"oversized image is rejected without changing index"); require(GetClipboardSequenceNumber()==sequence,"rejection leaves clipboard unchanged"); for(const auto &file:std::filesystem::directory_iterator(folder)) require(file.path().extension()!=L".request","encrypted capture request is cleaned up"); } catch(const std::exception &e) {std::cerr << "FAIL " << e.what() << '\n';result=1;} if(captured) try {ks::writeClipboard(owner,saved);} catch(...) {result=1;} DestroyWindow(owner); CoUninitialize(); // Remove only the exact PID-specific fixture under the system temp directory. std::error_code ignored; if(std::filesystem::weakly_canonical(folder).parent_path()==std::filesystem::weakly_canonical(std::filesystem::temp_directory_path())) std::filesystem::remove_all(folder,ignored); return result; }