diff --git a/Release/KSwitcher-win-x64.zip b/Release/KSwitcher-win-x64.zip index 435336e..a8a8a20 100644 Binary files a/Release/KSwitcher-win-x64.zip and b/Release/KSwitcher-win-x64.zip differ diff --git a/Release/README.md b/Release/README.md index 5bb6c51..a442979 100644 --- a/Release/README.md +++ b/Release/README.md @@ -1,17 +1,13 @@ # Latest Windows release -## v0.6.3 — 2026-10-29 +## v0.6.4 — 2026-10-29 -- Hide the fullscreen capture selection before submitting a notification when automatic editor opening is disabled. Previously the selection remained visible during submission, which could suppress the banner under Windows fullscreen notification rules. -- Default to capture → notification → click → editor. **Open editor after screen capture** is now off by default; explicitly saved preferences are preserved. -- Retain the editor fallback for immediate notification errors. -- Check for updates in the background every 6 hours after the startup check. Notify once per version per session, retry after network failures at the next interval, and install only after confirmation. +## Changes +- Fix accidental arrow/line detachment when dragging two linked numbered markers in turn. Clicking a visible marker now selects the marker instead of a connector endpoint underneath it, including when the connector is selected. +- Preserve intentional endpoint dragging outside the marker. -Verified: 176 native checks, 403 input/layout checks at 240 DPI, screenshot editor UI integration, -isolated notification registration checks, and 45 updater checks. The new UI regression test failed -before the fix and passes afterward: the selection is hidden at notification submission. -Banner behavior still needs confirmation on the affected laptop. Windows Do Not Disturb can -send notifications directly to the notification center; asynchronous toast failures are not yet monitored. +## Validation +176 native checks, 403 input/layout checks at 240 DPI, screenshot editor UI integration, isolated notification registration, and 45 updater checks. The new UI regression test fails before the fix and passes afterward, covering alternating marker drags with arrows, lines and freehand connectors, both endpoint links, and endpoint selection outside markers. **Upgrade:** choose Update in the tray menu. Legacy v0.3.0–v0.4.1 clients first receive the v0.4.2 compatibility bridge, then the current full release. v0.2.3 and earlier require manual installation. diff --git a/Release/SHA256SUMS.txt b/Release/SHA256SUMS.txt index c93b28b..3fbf93b 100644 --- a/Release/SHA256SUMS.txt +++ b/Release/SHA256SUMS.txt @@ -1 +1 @@ -a43786b06878da0f069dbad0fa29587a736dd2d8904c7bc991f5cf0c0570a16f KSwitcher-win-x64.zip +da86748a476deec9323c39e119c89dc008e2fe62691b207eb3b0f3bb0da32cf6 KSwitcher-win-x64.zip diff --git a/resources/app.rc b/resources/app.rc index 0a4192d..0b7619a 100644 --- a/resources/app.rc +++ b/resources/app.rc @@ -9,8 +9,8 @@ IDC_SCREENSHOT_PENCIL CURSOR "icons/lucide/pencil.cur" 1 RT_MANIFEST "app.manifest" 201 RCDATA "update.ps1" 1 VERSIONINFO - FILEVERSION 0,6,3,0 - PRODUCTVERSION 0,6,3,0 + FILEVERSION 0,6,4,0 + PRODUCTVERSION 0,6,4,0 FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_APP BEGIN @@ -20,7 +20,7 @@ BEGIN BEGIN VALUE "FileDescription", "KSwitcher — native keyboard and clipboard utility" VALUE "ProductName", "KSwitcher" - VALUE "FileVersion", "0.6.3" + VALUE "FileVersion", "0.6.4" END END BLOCK "VarFileInfo" diff --git a/src/screenshot.cpp b/src/screenshot.cpp index 3ec1056..45eac06 100644 --- a/src/screenshot.cpp +++ b/src/screenshot.cpp @@ -575,6 +575,15 @@ std::pair markAt(Capture &c, POINT p) { if((c.arrow || c.highlight || c.freehand || c.textTool) && (GetKeyState(VK_CONTROL)&0x8000)) return {-1,MarkPart::None}; double tolerance=double(std::max(6,c.unit/6))*c.image->width/std::max(1L,c.view.right-c.view.left); + // Badges are painted above connectors. Their visible bodies must also win + // hit testing, even when an attached connector is currently selected. + for(int i=int(c.marks.size())-1;i>=0;--i) { + const auto &m=c.marks[i]; + if(m.number && std::hypot(double(p.x-m.from.x),double(p.y-m.from.y))<=m.radius) { + auto part=hitScreenshotMark(m,p,tolerance); + return {i,part}; + } + } if(c.selected>=0) { const auto &m=c.marks[c.selected]; diff --git a/src/update.h b/src/update.h index 6164310..9e50848 100644 --- a/src/update.h +++ b/src/update.h @@ -2,7 +2,7 @@ #include "platform.h" namespace ks { -inline constexpr wchar_t AppVersion[] = L"0.6.3"; +inline constexpr wchar_t AppVersion[] = L"0.6.4"; inline constexpr UINT UpdateMessage = WM_APP + 7; inline constexpr UINT UpdateCheckIntervalMs = 6 * 60 * 60 * 1000; struct UpdateResult diff --git a/tests/screenshot_ui.cpp b/tests/screenshot_ui.cpp index c609774..f878d21 100644 --- a/tests/screenshot_ui.cpp +++ b/tests/screenshot_ui.cpp @@ -94,6 +94,37 @@ int main() c.hoveredPart=ks::MarkPart::Rotation; ks::updateStatus(c); require(c.statusText.find(L"15-degree")!=std::wstring::npos,"rotation hint explains Shift snapping"); c.hoveredPart=ks::MarkPart::None; + // Drag the two badges in turn, including a click near the attached endpoint. + for(bool freehand : {false,true}) for(auto ends : {ks::LineEnds::End,ks::LineEnds::None}) { + c.marks={{{60,80},{60,80},1},{{230,80},{230,80},2},{{60,80},{230,80},0}}; + auto &connector=c.marks[2]; connector.fromNumber=1; connector.toNumber=2; + connector.ends=ends; connector.freehand=freehand; + if(freehand) connector.points={{60,80},{140,80},{230,80}}; + ks::resolveScreenshotLinks(c.marks); c.selected=-1; c.selectedLabel=false; + auto mouse=[&](UINT message,POINT p) { + LONG x=c.view.left+MulDiv(p.x,c.view.right-c.view.left,c.image->width); + LONG y=c.view.top+MulDiv(p.y,c.view.bottom-c.view.top,c.image->height); + SendMessageW(window,message,message==WM_LBUTTONUP?0:MK_LBUTTON,MAKELPARAM(x,y)); + }; + for(int badge : {0,1,0,1}) { + auto origin=c.marks[badge].from; + // The second badge's inner edge overlaps the connector endpoint hit area. + if(badge==1) origin.x-=12; + mouse(WM_LBUTTONDOWN,origin); + require(c.selected==badge && c.part==ks::MarkPart::Body,"click on linked badge selects badge instead of connector endpoint"); + mouse(WM_MOUSEMOVE,{origin.x,origin.y+20}); + mouse(WM_LBUTTONUP,{origin.x,origin.y+20}); + require(c.marks[2].fromNumber==1 && c.marks[2].toNumber==2,"alternating badge drags preserve both connector links"); + auto start=c.marks[0].from,end=c.marks[1].from; + require(std::abs(std::hypot(double(c.marks[2].from.x-start.x),double(c.marks[2].from.y-start.y))-15)<1 && + std::abs(std::hypot(double(c.marks[2].to.x-end.x),double(c.marks[2].to.y-end.y))-15)<1, + "connector endpoints follow both badge rims"); + } + c.selected=2; + require(ks::markAt(c,c.marks[1].from).first==1,"selected connector cannot steal a badge click"); + auto outside=c.marks[2].to; outside.x-=4; + require(ks::markAt(c,outside)==std::pair{2,ks::MarkPart::End},"endpoint remains draggable outside badge"); + } std::vector nudgeMarks; nudgeMarks.push_back({{60,60},{60,60},1}); nudgeMarks.push_back({{60,60},{120,100},0});