Release v0.2.1: support remote input, safe updates and high-DPI windows
This commit is contained in:
+122
@@ -0,0 +1,122 @@
|
||||
#include "app.h"
|
||||
#include "ui_geometry.h"
|
||||
#include <commctrl.h>
|
||||
#include <iostream>
|
||||
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 "<<checks<<" input regression checks\n"; return 0;
|
||||
} catch(const std::exception &e) { std::cerr<<e.what()<<'\n'; return 1; }
|
||||
}
|
||||
Reference in New Issue
Block a user