Compare commits

..

2 Commits

11 changed files with 27 additions and 37 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.24) cmake_minimum_required(VERSION 3.24)
project(KSwitcher VERSION 0.2.1 LANGUAGES CXX RC) project(KSwitcher VERSION 0.2.3 LANGUAGES CXX RC)
if(NOT WIN32) if(NOT WIN32)
message(FATAL_ERROR "KSwitcher uses native Windows APIs") message(FATAL_ERROR "KSwitcher uses native Windows APIs")
endif() endif()
+3 -3
View File
@@ -112,7 +112,7 @@ only bounded previews/metadata stay in memory. Image data is loaded on demand an
the history popup is hidden. Settings **Write log** (`writeLog`, enabled by default) controls all the history popup is hidden. Settings **Write log** (`writeLog`, enabled by default) controls all
log writes. Disabling it also clears diagnostic context from memory; existing log files remain. log writes. Disabling it also clears diagnostic context from memory; existing log files remain.
Successful operations stay in a memory buffer of at most 40 diagnostic events for 30 seconds. Successful operations stay in a memory buffer of at most 40 diagnostic events for 30 seconds.
Only failures or suspected repeated commands flush this context to `kswitcher.log`; flushed Only failures flush this context to `kswitcher.log`; flushed
context is discarded. Ordinary application errors are also logged when logging is enabled. context is discarded. Ordinary application errors are also logged when logging is enabled.
Incident context contains tracked physical input, the expected word, copied text, conversion result, Incident context contains tracked physical input, the expected word, copied text, conversion result,
and layout IDs, correlated by request ID even when verification fails. Text fields use quoted, and layout IDs, correlated by request ID even when verification fails. Text fields use quoted,
@@ -121,8 +121,8 @@ original length and a truncation flag; each field is limited to 512 UTF-16 code
Manual conversion attempts in `kswitcher.log` have request IDs and explicit outcomes, including Manual conversion attempts in `kswitcher.log` have request IDs and explicit outcomes, including
busy, empty copy, verification mismatch, unchanged conversion, and cancelled paste. Requests busy, empty copy, verification mismatch, unchanged conversion, and cancelled paste. Requests
record the process, window, input version, and repeated attempts within ten seconds without new record the process, window, input version, and repeated attempts within ten seconds without new
input. A second repeated manual command triggers a suspicion, not proof of failure; intentional input. Successful repeated commands do not flush the log; intentional layout cycling is normal.
layout cycling can also trigger it. An unchanged result containing no letters, or a direct command An unchanged result containing no letters, or a direct command
to the detected current layout, is treated as expected. `paste-sent-unverified` means Windows to the detected current layout, is treated as expected. `paste-sent-unverified` means Windows
accepted the paste shortcut; it does not prove that the editor applied the replacement. accepted the paste shortcut; it does not prove that the editor applied the replacement.
The previous 256 KiB log is retained as `kswitcher.log.1` when the current log rotates. The previous 256 KiB log is retained as `kswitcher.log.1` when the current log rotates.
Binary file not shown.
+4 -14
View File
@@ -1,21 +1,11 @@
# Latest Windows release # Latest Windows release
## v0.2.1 — 2026-10-28 ## v0.2.3 — 2026-10-28
- Accept keyboard and mouse input forwarded by Microsoft Mouse Without Borders, including hotkey - Fix overlapping Settings controls that obscured the Next layout and Change case labels.
releases, repeated presses, recent-word conversion and snippets. Mark KSwitcher's own generated - Stop treating successful repeated conversions as diagnostic incidents. Failed operations still flush their diagnostic context to the log.
input to prevent recursive commands and preserve text-operation validation.
- Add **Update** to the tray menu and a non-installing startup check against this repository's
Releases. Verify the x64 ZIP, SHA-256, executable identity and version before closing; restart
automatically and roll back on replacement/startup failure. Keep settings, history and existing dictionaries.
- Fix initial window sizing on high-DPI monitors. Size the frame and controls from the same monitor
DPI, recover windows after display changes, and provide resizing, scrolling and focus visibility
when the available work area is smaller than the contents.
Verified: 168 native checks, 51 input/layout checks (including hidden Settings at 240 DPI), Verified: 168 native checks, 51 input/layout checks, and 26 updater/metadata checks.
24 offline updater checks, live release download/checksum/package validation, and visible Settings
at 250% scaling. Two-laptop Mouse Without Borders use and physically disconnecting a monitor still
require hardware verification.
Download **KSwitcher-win-x64.zip**, extract the entire archive to a writable folder, Download **KSwitcher-win-x64.zip**, extract the entire archive to a writable folder,
and launch `KSwitcher.exe`. Keep `dictionaries/` next to the executable. and launch `KSwitcher.exe`. Keep `dictionaries/` next to the executable.
+1 -1
View File
@@ -1 +1 @@
e33c372d1401c5500f3b07b38c10aff9667a5b1db9f6e613420e7878202853b3 KSwitcher-win-x64.zip ef3f21574f02edeb63661109056480506041372f1fa4ed40b07cdd2f544d79b5 KSwitcher-win-x64.zip
+1 -1
View File
@@ -18,7 +18,7 @@ try {
& $compiler @common -c "src/$name.cpp" -o "build/$name.o" & $compiler @common -c "src/$name.cpp" -o "build/$name.o"
if ($LASTEXITCODE) { throw "Compilation failed: $name" } if ($LASTEXITCODE) { throw "Compilation failed: $name" }
} }
& windres resources/app.rc -O coff -o build/app-res.o & windres --codepage=65001 resources/app.rc -O coff -o build/app-res.o
if ($LASTEXITCODE) { throw 'Resource compilation failed' } if ($LASTEXITCODE) { throw 'Resource compilation failed' }
New-Item -ItemType Directory -Force build/release | Out-Null New-Item -ItemType Directory -Force build/release | Out-Null
& $compiler -mwindows -municode -static -s '-Wl,--gc-sections' build/core.o build/platform.o build/autostart.o build/autocorrect.o build/storage.o build/ocr.o build/richtext.o build/update.o build/app.o build/ui.o build/main.o build/app-res.o @libs -o $executablePath & $compiler -mwindows -municode -static -s '-Wl,--gc-sections' build/core.o build/platform.o build/autostart.o build/autocorrect.o build/storage.o build/ocr.o build/richtext.o build/update.o build/app.o build/ui.o build/main.o build/app-res.o @libs -o $executablePath
+4 -3
View File
@@ -1,11 +1,12 @@
#pragma code_page(65001)
#include <windows.h> #include <windows.h>
#include "resource.h" #include "resource.h"
IDI_KSWITCHER ICON "kswitcher.ico" IDI_KSWITCHER ICON "kswitcher.ico"
1 RT_MANIFEST "app.manifest" 1 RT_MANIFEST "app.manifest"
201 RCDATA "update.ps1" 201 RCDATA "update.ps1"
1 VERSIONINFO 1 VERSIONINFO
FILEVERSION 0,2,1,0 FILEVERSION 0,2,3,0
PRODUCTVERSION 0,2,1,0 PRODUCTVERSION 0,2,3,0
FILEOS VOS_NT_WINDOWS32 FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP FILETYPE VFT_APP
BEGIN BEGIN
@@ -15,7 +16,7 @@ BEGIN
BEGIN BEGIN
VALUE "FileDescription", "KSwitcher — native keyboard and clipboard utility" VALUE "FileDescription", "KSwitcher — native keyboard and clipboard utility"
VALUE "ProductName", "KSwitcher" VALUE "ProductName", "KSwitcher"
VALUE "FileVersion", "0.2.1" VALUE "FileVersion", "0.2.3"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"
+5 -10
View File
@@ -619,9 +619,6 @@ void App::transform(int command, int snippet)
L" window=" + std::to_wstring(reinterpret_cast<uintptr_t>(target)) + L" window=" + std::to_wstring(reinterpret_cast<uintptr_t>(target)) +
L" version=" + std::to_wstring(currentVersion) + L" version=" + std::to_wstring(currentVersion) +
L" repeat=" + std::to_wstring(repeatAttempts)); L" repeat=" + std::to_wstring(repeatAttempts));
if (repeatAttempts >= 2)
diagnosticEvent(L"Transform suspicion id=" + std::to_wstring(attempt) +
L" reason=repeated-command-without-input (may be intentional cycling)");
} }
if (busy.exchange(true)) if (busy.exchange(true))
{ {
@@ -643,8 +640,7 @@ void App::transform(int command, int snippet)
version = inputVersion; version = inputVersion;
} }
enqueue( enqueue(
[this, target, recent, previous, config, available, version, command, snippet, attempt, manual, requestedAt, [this, target, recent, previous, config, available, version, command, snippet, attempt, manual, requestedAt]
suspicious = manual && repeatAttempts >= 2]
{ {
struct BusyReset struct BusyReset
{ {
@@ -679,7 +675,6 @@ void App::transform(int command, int snippet)
uint64_t version, attempt; uint64_t version, attempt;
ULONGLONG start; ULONGLONG start;
bool enabled; bool enabled;
bool suspicious;
std::wstring &reason; std::wstring &reason;
std::wstring &text, &result, &expected; std::wstring &text, &result, &expected;
int &hint, &to; int &hint, &to;
@@ -693,10 +688,10 @@ void App::transform(int command, int snippet)
L" source=" + std::to_wstring(hint) + L" target=" + std::to_wstring(to) + L" source=" + std::to_wstring(hint) + L" target=" + std::to_wstring(to) +
L" expected=" + diagnosticText(expected) + L" copied=" + diagnosticText(text) + L" expected=" + diagnosticText(expected) + L" copied=" + diagnosticText(text) +
L" result=" + diagnosticText(result), L" result=" + diagnosticText(result),
suspicious || (reason != L"paste-sent-unverified" && reason != L"layout-only" && reason != L"paste-sent-unverified" && reason != L"layout-only" &&
reason != L"unchanged-expected")); reason != L"unchanged-expected");
} }
} audit{*this, target, version, attempt, requestedAt, manual, suspicious, reason, text, result, expected, hint, to}; } audit{*this, target, version, attempt, requestedAt, manual, reason, text, result, expected, hint, to};
try try
{ {
auto valid = [&] auto valid = [&]
@@ -1134,7 +1129,7 @@ void App::trayMenu()
break; break;
case 16: case 16:
MessageBoxW(window, MessageBoxW(window,
L"KSwitcher 0.2.1\nNative C++ / Win32 keyboard and clipboard utility\n\nBased on " L"KSwitcher 0.2.2\nNative C++ / Win32 keyboard and clipboard utility\n\nBased on "
L"Typlo by bitcodepro. MIT license.\nHistory is encrypted for your Windows " L"Typlo by bitcodepro. MIT license.\nHistory is encrypted for your Windows "
L"account.\nNo .NET, WebView or graphics framework.", L"account.\nNo .NET, WebView or graphics framework.",
L"About KSwitcher", MB_OK); L"About KSwitcher", MB_OK);
+2 -2
View File
@@ -1623,8 +1623,8 @@ void showSettings(App &app, bool visible)
HWND w = makeWindow(app, L"KSwitcher.Settings", L"KSwitcher settings", settingsProc, 780, 660); HWND w = makeWindow(app, L"KSwitcher.Settings", L"KSwitcher settings", settingsProc, 780, 660);
app.settingsWindow = w; app.settingsWindow = w;
label(w, L"Text conversion", 18, 16); label(w, L"Text conversion", 18, 16);
checkbox(w, L"Convert recently typed text when nothing is selected", 201, 18, 43, 440); checkbox(w, L"Convert recently typed text when nothing is selected", 201, 18, 43, 390);
checkbox(w, L"Switch Windows input layout after conversion", 202, 18, 70, 440); checkbox(w, L"Switch Windows input layout after conversion", 202, 18, 70, 390);
checkbox(w, L"Record clipboard history", 203, 18, 106); checkbox(w, L"Record clipboard history", 203, 18, 106);
checkbox(w, L"Start with Windows", 204, 18, 133); checkbox(w, L"Start with Windows", 204, 18, 133);
checkbox(w, L"Start with Windows as administrator", 206, 18, 160, 395); checkbox(w, L"Start with Windows as administrator", 206, 18, 160, 395);
+1 -1
View File
@@ -2,7 +2,7 @@
#include "platform.h" #include "platform.h"
namespace ks namespace ks
{ {
inline constexpr wchar_t AppVersion[] = L"0.2.1"; inline constexpr wchar_t AppVersion[] = L"0.2.3";
inline constexpr UINT UpdateMessage = WM_APP + 7; inline constexpr UINT UpdateMessage = WM_APP + 7;
struct UpdateResult struct UpdateResult
{ {
+5 -1
View File
@@ -7,6 +7,10 @@ function Reject([scriptblock]$Action,[string]$Name) { $rejected=$false; try { &
$root=Join-Path ([IO.Path]::GetTempPath()) ('KSwitcher-update-tests-'+[guid]::NewGuid().ToString('N')) $root=Join-Path ([IO.Path]::GetTempPath()) ('KSwitcher-update-tests-'+[guid]::NewGuid().ToString('N'))
[IO.Directory]::CreateDirectory($root) | Out-Null [IO.Directory]::CreateDirectory($root) | Out-Null
try { try {
$binaryInfo = [Diagnostics.FileVersionInfo]::GetVersionInfo((Resolve-Path -LiteralPath $Executable).Path)
$binaryVersion = Get-StableVersion $binaryInfo.FileVersion
Assert ($null -ne $binaryVersion) 'executable has a stable version'
Assert ($binaryInfo.FileDescription -ceq ('KSwitcher ' + [char]0x2014 + ' native keyboard and clipboard utility')) 'executable description preserves Unicode em dash'
Assert ((Get-StableVersion 'v0.10.0') -gt (Get-StableVersion 'v0.2.9')) 'numeric versions' Assert ((Get-StableVersion 'v0.10.0') -gt (Get-StableVersion 'v0.2.9')) 'numeric versions'
Assert ($null -eq (Get-StableVersion 'v1.0.0-beta')) 'prerelease rejected' Assert ($null -eq (Get-StableVersion 'v1.0.0-beta')) 'prerelease rejected'
Assert ($null -eq (Get-StableVersion 'v1.0.0;whoami')) 'invalid version rejected' Assert ($null -eq (Get-StableVersion 'v1.0.0;whoami')) 'invalid version rejected'
@@ -36,7 +40,7 @@ try {
((Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash+' KSwitcher-win-x64.zip') | Set-Content -LiteralPath $sum ((Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash+' KSwitcher-win-x64.zip') | Set-Content -LiteralPath $sum
Assert-Checksum $archive $sum; ++$script:checks Assert-Checksum $archive $sum; ++$script:checks
$stage=Join-Path $root 'stage' $stage=Join-Path $root 'stage'
Expand-Release $archive $stage ([version]'0.2.1'); ++$script:checks Expand-Release $archive $stage $binaryVersion; ++$script:checks
Reject { Expand-Release $archive (Join-Path $root 'wrong-version') ([version]'9.0.0') } 'version mismatch rejected' Reject { Expand-Release $archive (Join-Path $root 'wrong-version') ([version]'9.0.0') } 'version mismatch rejected'
('0'*64+' KSwitcher-win-x64.zip') | Set-Content -LiteralPath $sum ('0'*64+' KSwitcher-win-x64.zip') | Set-Content -LiteralPath $sum
Reject { Assert-Checksum $archive $sum } 'corrupt package rejected' Reject { Assert-Checksum $archive $sum } 'corrupt package rejected'