Release v0.6.28: typo correction, partial sync and selection diagnostics

This commit is contained in:
2026-11-02 11:19:13 +02:00
parent 738c81dce2
commit 55993c0f90
23 changed files with 676 additions and 76 deletions
+48 -5
View File
@@ -599,6 +599,9 @@ int main(int argc, char **argv) {
require(!app.handleKeyboard(f12,true,MOD_CONTROL|MOD_SHIFT),"exact modifiers only");
app.resetInput();
app.handleKeyboard(event('A',0x1e),true,0);
if (app.run.keys!=L"a" && app.run.keys!=L"A")
std::cerr << "Input fixture foreground=" << (uintptr_t)GetForegroundWindow()
<< " recorded=" << ks::utf8(app.run.keys) << '\n';
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)})
@@ -656,6 +659,23 @@ int main(int argc, char **argv) {
require((GetWindowLongPtrW(GetDlgItem(syncWindow,604),GWL_STYLE)&ES_PASSWORD)!=0,
"sync password is masked");
require(!IsWindowEnabled(app.settingsWindow),"sync dialog owns settings interaction");
for(int id:{602,603,604,605,606,607,610,611,612,613,IDOK})
require(!IsWindowEnabled(GetDlgItem(syncWindow,id)),"disabled synchronization disables dependent controls");
require(IsWindowEnabled(GetDlgItem(syncWindow,601)) && IsWindowEnabled(GetDlgItem(syncWindow,609)) &&
IsWindowEnabled(GetDlgItem(syncWindow,IDCANCEL)),"enable, forget password and close remain available");
SendMessageW(syncWindow,WM_COMMAND,612,0);
require(!FindWindowW(L"KSwitcher.SyncConflicts",nullptr),"disabled conflict action is ignored");
SendDlgItemMessageW(syncWindow,601,BM_SETCHECK,BST_CHECKED,0);
SendMessageW(syncWindow,WM_COMMAND,MAKEWPARAM(601,BN_CLICKED),0);
for(int id:{602,603,604,605,606,607,610,611,613,IDOK})
require(IsWindowEnabled(GetDlgItem(syncWindow,id)),"enabling synchronization immediately enables connection controls");
require(!ks::loadSyncConfig(app.data).enabled,"checking enable alone does not connect or persist");
auto enabledSync=ks::loadSyncConfig(app.data);
enabledSync.enabled=true;
enabledSync.server=L"https://sync.example.test";
enabledSync.username=L"test";
ks::saveSyncConfig(app.data,enabledSync);
SendMessageW(syncWindow,WM_COMMAND,MAKEWPARAM(601,BN_CLICKED),0);
ks::InputRegressionAccess::sampleConflicts(app);
SendMessageW(syncWindow,WM_COMMAND,612,0);
auto conflictsWindow=FindWindowW(L"KSwitcher.SyncConflicts",nullptr);
@@ -678,7 +698,7 @@ int main(int argc, char **argv) {
require(!IsWindowEnabled(syncWindow),"conflict dialog owns sync interaction");
SendMessageW(conflictsWindow,WM_COMMAND,IDCANCEL,0);
require(IsWindowEnabled(syncWindow),"closing conflict dialog restores sync window");
for(int id:{601,602,603,604,605,606,607,608,609,610,611,612,IDOK,IDCANCEL}) {
for(int id:{601,602,603,604,605,606,607,608,609,610,611,612,613,IDOK,IDCANCEL}) {
RECT control{},client{};
GetWindowRect(GetDlgItem(syncWindow,id),&control);
MapWindowPoints(nullptr,syncWindow,reinterpret_cast<POINT*>(&control),2);
@@ -686,8 +706,12 @@ int main(int argc, char **argv) {
require(control.left>=0&&control.top>=0&&control.right<=client.right&&control.bottom<=client.bottom,
"sync controls fit dialog");
}
SendDlgItemMessageW(syncWindow,601,BM_SETCHECK,BST_UNCHECKED,0);
SendMessageW(syncWindow,WM_COMMAND,MAKEWPARAM(601,BN_CLICKED),0);
require(IsWindowEnabled(GetDlgItem(syncWindow,IDOK)),"Save remains available to persist disabling sync");
SendMessageW(syncWindow,WM_COMMAND,IDOK,0);
require(!ks::loadSyncConfig(app.data).enabled,"saving disabled sync does not enable network access");
require(!IsWindowEnabled(GetDlgItem(syncWindow,IDOK)),"Save disables after disabled state is persisted");
SendMessageW(syncWindow,WM_COMMAND,IDCANCEL,0);
require(IsWindowEnabled(app.settingsWindow),"closing sync restores parent");
require(ks::InputRegressionAccess::staleSync(app,false),"in-flight sync cannot overwrite newer local edits");
@@ -704,21 +728,31 @@ int main(int argc, char **argv) {
GetWindowRect(autoOptions,&original);
// Use a client size above the scrolling baseline at every DPI.
UINT dpi=GetDpiForWindow(autoOptions);
SetWindowPos(autoOptions,nullptr,0,0,MulDiv(800,dpi,96),MulDiv(900,dpi,96),
SetWindowPos(autoOptions,nullptr,0,0,MulDiv(800,dpi,96),MulDiv(1100,dpi,96),
SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE);
RECT baseline{}; GetWindowRect(autoOptions,&baseline);
RECT clientBefore{}; GetClientRect(autoOptions,&clientBefore);
for(int i=0;i<3;++i) GetWindowRect(GetDlgItem(autoOptions,411+i),&before[i]);
GetWindowRect(GetDlgItem(autoOptions,IDOK),&buttonBefore);
SetWindowPos(autoOptions,nullptr,0,0,baseline.right-baseline.left+240,
baseline.bottom-baseline.top+120,SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE);
RECT clientAfter{}; GetClientRect(autoOptions,&clientAfter);
// Windows can constrain oversized windows to the current desktop.
// Check layout against the actual available client space.
int extraBeforeX=std::max(0,int(clientBefore.right)-MulDiv(560,dpi,96));
int extraAfterX=std::max(0,int(clientAfter.right)-MulDiv(560,dpi,96));
int extraY=std::max(0,int(clientAfter.bottom)-MulDiv(880,dpi,96))-
std::max(0,int(clientBefore.bottom)-MulDiv(880,dpi,96));
for(int i=0;i<3;++i) {
GetWindowRect(GetDlgItem(autoOptions,411+i),&after[i]);
require(after[i].right-after[i].left==before[i].right-before[i].left+80 &&
after[i].bottom-after[i].top==before[i].bottom-before[i].top+120,
int extraX=(extraAfterX*(i+1)/3-extraAfterX*i/3)-
(extraBeforeX*(i+1)/3-extraBeforeX*i/3);
require(after[i].right-after[i].left==before[i].right-before[i].left+extraX &&
after[i].bottom-after[i].top==before[i].bottom-before[i].top+extraY,
"dictionary columns share extra width and receive extra height");
}
GetWindowRect(GetDlgItem(autoOptions,IDOK),&buttonAfter);
require(buttonAfter.left-buttonBefore.left==240 && buttonAfter.top-buttonBefore.top==120,
require(buttonAfter.left-buttonBefore.left==extraAfterX-extraBeforeX && buttonAfter.top-buttonBefore.top==extraY,
"auto-correction buttons follow bottom-right corner");
require(after[0].right<after[1].left && after[1].right<after[2].left &&
after[2].bottom<buttonAfter.top,
@@ -735,6 +769,9 @@ int main(int argc, char **argv) {
"personal dictionary field fits options window");
}
SetDlgItemTextW(autoOptions,412,L"Релізик\r\nрелізик, словечко");
SetDlgItemTextW(autoOptions,416,L" Првиіт = Привіт\r\nhelo=hello");
SendDlgItemMessageW(autoOptions,414,BM_SETCHECK,BST_CHECKED,0);
SendDlgItemMessageW(autoOptions,415,BM_SETCHECK,BST_CHECKED,0);
SendMessageW(autoOptions,WM_COMMAND,IDOK,0);
require(!IsWindow(autoOptions),"personal words accepted without file editing");
SendMessageW(app.settingsWindow,WM_COMMAND,255,0);
@@ -744,6 +781,12 @@ int main(int argc, char **argv) {
GetDlgItemTextW(autoOptions,412,personalWords,128);
require(std::wstring(personalWords)==L"релізик, словечко",
"personal dictionary normalizes case, newlines and duplicate words");
wchar_t pairs[128]{};
GetDlgItemTextW(autoOptions,416,pairs,128);
require(std::wstring(pairs)==L"helo=hello\r\nпрвиіт=привіт\r\n" &&
SendDlgItemMessageW(autoOptions,414,BM_GETCHECK,0,0)==BST_CHECKED &&
SendDlgItemMessageW(autoOptions,415,BM_GETCHECK,0,0)==BST_CHECKED,
"typo options and normalized replacement pairs survive reopening");
SendMessageW(autoOptions,WM_COMMAND,IDCANCEL,0);
if (!app.history) app.history=std::make_unique<ks::History>(temp/L"history-layout");
ks::showHistory(app,false);