#include "app.h" #include "ui_geometry.h" #include #include namespace ks { struct InputRegressionAccess { static void setup(App &a) { a.hookBindings = {{Toggle,{MOD_CONTROL,VK_F12}}, {Case,{MOD_CONTROL|MOD_SHIFT,'U'}}}; } static void click() { MSLLHOOKSTRUCT m{}; m.flags=LLMHF_INJECTED; App::mouseProc(HC_ACTION,WM_LBUTTONDOWN,(LPARAM)&m); } }; } int main(int argc, char **argv) { if (argc == 2 && std::string(argv[1]) == "--update-check") { try { auto result = ks::runUpdate(false, {}); std::cout << "Live update check: " << ks::utf8(result.state) << " " << ks::utf8(result.version) << " " << ks::utf8(result.error) << '\n'; return result.state == L"current" || result.state == L"available" ? 0 : 1; } catch (const std::exception &e) { std::cerr << e.what() << '\n'; return 1; } } auto temp=std::filesystem::temp_directory_path()/(L"KSwitcher-input-tests-"+std::to_wstring(GetCurrentProcessId())); int checks=0; auto require=[&](bool ok,const char* text) { if(!ok) throw std::runtime_error(text); ++checks; }; try { INITCOMMONCONTROLSEX controls{sizeof(controls),ICC_STANDARD_CLASSES|ICC_LISTVIEW_CLASSES}; InitCommonControlsEx(&controls); { ks::App app(GetModuleHandleW(nullptr),true,temp); ks::InputRegressionAccess::setup(app); // Message-only window: no hooks, synthetic input, clipboard changes or visible editor. app.window=CreateWindowExW(0,L"STATIC",L"Input tests",0,0,0,0,0,HWND_MESSAGE,nullptr,nullptr,nullptr); require(app.window!=nullptr,"message-only test window"); auto event=[](DWORD vk,DWORD scan=0,bool own=false) { KBDLLHOOKSTRUCT k{}; k.vkCode=vk; k.scanCode=scan; k.flags=LLKHF_INJECTED|LLKHF_LOWER_IL_INJECTED; k.dwExtraInfo=own?ks::inputMarker():123; return k; }; 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); 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"); require(commands()==1,"one command per press"); require(!app.handleKeyboard(event(VK_F12,0,true),false,0),"own key-up bypasses latch"); require(app.handleKeyboard(f12,true,MOD_CONTROL),"own key-up leaves remote latch intact"); require(commands()==0,"no repeat after own key-up"); require(app.handleKeyboard(f12,false,0),"remote release consumed after modifier release"); require(app.handleKeyboard(f12,true,MOD_CONTROL),"second remote press handled"); require(commands()==1,"second remote press dispatches"); app.handleKeyboard(f12,false,0); require(app.handleKeyboard(event('U'),true,MOD_CONTROL|MOD_SHIFT),"combined modifiers"); app.handleKeyboard(event('U'),false,0); require(!app.handleKeyboard(f12,true,MOD_CONTROL|MOD_SHIFT),"exact modifiers only"); app.resetInput(); app.handleKeyboard(event('A',0x1e),true,0); require(app.run.keys==L"a"||app.run.keys==L"A","external scan recorded with current CapsLock"); auto keys=app.run.keys; auto version=app.inputVersion.load(); for(WORD key:{WORD('V'),WORD(VK_SHIFT),WORD(VK_LEFT),WORD(VK_F15),WORD(0xE8)}) for(bool up:{false,true}) { auto i=ks::keyboardInput(key,up?KEYEVENTF_KEYUP:0); require(i.ki.dwExtraInfo==ks::inputMarker(),"all generated events tagged"); require(!app.handleKeyboard(event(key,0,true),!up,MOD_CONTROL),"own events bypass handling"); } require(app.run.keys==keys&&app.inputVersion==version,"own input preserves transaction and run"); app.handleKeyboard(event(VK_BACK),true,0); require(app.run.keys.empty(),"remote backspace recorded"); app.handleKeyboard(event('B'),true,MOD_SHIFT); require(!app.run.keys.empty(),"VK-only remote input resolves scan code"); require(app.inputVersion>version,"remote input invalidates pending transaction"); app.handleKeyboard(event(VK_PACKET,0x1e),true,0); require(app.run.keys.empty(),"Unicode packet is not a physical scan code"); app.handleKeyboard(event('A',0x1e),true,0); ks::InputRegressionAccess::click(); require(app.run.keys.empty(),"remote mouse resets recorded input"); auto physical=f12; physical.flags=0; physical.dwExtraInfo=ks::inputMarker(); require(app.handleKeyboard(physical,true,MOD_CONTROL),"physical hotkey still works"); app.handleKeyboard(physical,false,0); auto fitted=ks::fitWindowRect({-2500,-800,-500,1200},{0,0,1200,800}); require(fitted.left==0&&fitted.top==0&&fitted.right==1200&&fitted.bottom==800,"oversized disconnected-monitor window fits work area"); fitted=ks::fitWindowRect({2000,2000,2600,2400},{-1920,0,0,1080}); require(fitted.left==-600&&fitted.top==680&&fitted.right==0,"negative-coordinate monitor supported"); ks::showSettings(app,false); auto settings=app.settingsWindow; require(settings&&!IsWindowVisible(settings),"settings layout tested without changing user focus"); RECT frame{},client{},cancel{}; GetWindowRect(settings,&frame); MONITORINFO monitor{sizeof(monitor)}; GetMonitorInfoW(MonitorFromWindow(settings,MONITOR_DEFAULTTONEAREST),&monitor); require(frame.left>=monitor.rcWork.left&&frame.top>=monitor.rcWork.top&&frame.right<=monitor.rcWork.right&&frame.bottom<=monitor.rcWork.bottom,"initial settings frame fits active work area"); UINT dpi=GetDpiForWindow(settings); std::cout << "Hidden Settings layout checked at " << dpi << " DPI\n"; GetWindowRect(GetDlgItem(settings,IDCANCEL),&cancel); require(cancel.right-cancel.left==MulDiv(100,dpi,96),"controls use actual window DPI"); SetWindowPos(settings,nullptr,0,0,480,360,SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE); SCROLLINFO scroll{sizeof(scroll),SIF_ALL}; GetScrollInfo(settings,SB_VERT,&scroll); require(scroll.nMax+1>(int)scroll.nPage,"small settings window has vertical scrolling"); GetScrollInfo(settings,SB_HORZ,&scroll); require(scroll.nMax+1>(int)scroll.nPage,"small settings window has horizontal scrolling"); SendMessageW(settings,WM_VSCROLL,SB_BOTTOM,0); SendMessageW(settings,WM_HSCROLL,SB_RIGHT,0); GetClientRect(settings,&client); GetWindowRect(GetDlgItem(settings,IDCANCEL),&cancel); MapWindowPoints(nullptr,settings,(POINT*)&cancel,2); require(cancel.left>=0&&cancel.top>=0&&cancel.right<=client.right&&cancel.bottom<=client.bottom,"bottom actions reachable by scrolling"); SendMessageW(settings,WM_VSCROLL,SB_TOP,0); SendMessageW(settings,WM_HSCROLL,SB_LEFT,0); SendMessageW(GetDlgItem(settings,IDCANCEL),WM_SETFOCUS,0,0); GetWindowRect(GetDlgItem(settings,IDCANCEL),&cancel); MapWindowPoints(nullptr,settings,(POINT*)&cancel,2); require(cancel.left>=0&&cancel.top>=0&&cancel.right<=client.right&&cancel.bottom<=client.bottom,"keyboard focus reveals off-screen action"); SetWindowPos(settings,nullptr,-20000,-20000,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE); SendMessageW(settings,WM_DISPLAYCHANGE,0,0); GetWindowRect(settings,&frame); GetMonitorInfoW(MonitorFromWindow(settings,MONITOR_DEFAULTTONEAREST),&monitor); require(frame.left>=monitor.rcWork.left&&frame.top>=monitor.rcWork.top&&frame.right<=monitor.rcWork.right&&frame.bottom<=monitor.rcWork.bottom,"display change recovers off-screen window"); DestroyWindow(settings); DestroyWindow(app.window); app.window=nullptr; } std::filesystem::remove_all(temp); std::cout<<"Passed "<