| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| oculixide-4.1.0-rc1-windows.jar | 2026-08-26 | 131.1 MB | |
| oculixide-4.1.0-rc1-macos.jar | 2026-08-26 | 123.0 MB | |
| oculix-mcp-server-4.1.0-rc1.jar | 2026-08-26 | 214.6 MB | |
| oculixapi-4.1.0-rc1-linux.jar | 2026-08-26 | 137.4 MB | |
| oculixapi-4.1.0-rc1-macos.jar | 2026-08-26 | 61.9 MB | |
| oculixapi-4.1.0-rc1-windows.jar | 2026-08-26 | 70.0 MB | |
| oculixide-4.1.0-rc1-linux.jar | 2026-08-26 | 198.5 MB | |
| OculiX 4.1.0-rc1 (release candidate) source code.tar.gz | 2026-08-26 | 16.4 MB | |
| OculiX 4.1.0-rc1 (release candidate) source code.zip | 2026-08-26 | 17.1 MB | |
| README.md | 2026-08-26 | 17.6 kB | |
| Totals: 10 Items | 970.0 MB | 15 | |
Pre-release for [#444] — a full architectural pass on
Regionprovenance for window-backed regions. Whole-window highlight and capture across mixed-DPI monitors already worked since v4.0.0; this release makes the same guarantees hold for every derived region (offset, grow,getInset,getRow/Col/Cell,union/intersection,Match, sub-rects built viacopy + setRect) and closes the "provenance is irreducible" gap that came out of @uwekoenig's 22/07 status-bar report. Ships as a GitHub Release marked pre-release; thelatestpointer stays on v4.0.0 stable. No Maven Central publication.
🙏 Why this release exists — @uwekoenig
@uwekoenig reported [#444] back in July, validated the initial spanning highlight fix on his 4K mixed-DPI setup within 24 h, then patiently pointed out on 22/07 that a Region derived from a straddling window via new Region(x, y, w, h) still lost provenance and fell back to the Swing biggest-part overlay. His 21/08 nudge is what triggered this full architectural pass. Every step from root-cause diagnosis to live re-testing was on his setup, on his bug report. Thank you Uwe — this one is properly yours.
🔍 Root cause
App.focusedWindow() returns a Region that carries native provenance (sourceWindow) so highlight/capture/text can route to Windows PrintWindow + WS_EX_LAYERED overlay in physical coordinate space — which is why the whole-window case has worked correctly on mixed-DPI straddling windows since v4.0.0. But any Region derived from the focused window lost that provenance:
new Region(int, int, int, int)(the natural pattern forstatusBar = new Region(window.getX(), window.getY() + ..., window.getW(), 30)) is an orphan: the 4-int constructor has no way to recover which window the coordinates came from, so the derived Region falls back to the Swing biggest-part overlay and single-monitorRobotcapture.getInset,offset,grow,right/left/above/below,getRow/Col/Cell,union,intersection, and Match funnels (toGlobalCoord,relocate) all built new Regions without carrying the parent'ssourceWindow— so aMatchfound inside a straddling window silently lost the native route too.- Direct mutators (
setX/Y/W/H,setRect,setSize,setLocation,setCenter,setTopLeft/Right/BottomLeft/Right,add) wrotex/y/w/hand then re-raninitScreen(null), which cropped a window-backed rect to a single monitor — the very shape [#444] exists to preserve. - A short-lived native capture cache (3 s TTL) in
Regionsilently frozewaitForStable,wait,existsand repeated finds on stale pixels:imageDistance(A, A) == 0produced false stability reports even while the target window was genuinely changing. - Persistent highlight (
highlight()/highlightOn()—secs <= 0) routed to the nativeWS_EX_LAYEREDbackend which destroys the overlay in itsfinallyblock right after the timedThread.sleep; onsecs = -1the overlay flashed and vanished, breaking the toggle API entirely.
🎯 What ships — before / after
| Case | Before v4.1.0-rc1 | After |
|---|---|---|
window.highlight(3) on a straddling window |
Native spanning frame across screens (already OK since v4.0.0) | Unchanged |
window.getImage() on a straddling window |
PrintWindow full capture including off-screen parts (already OK since v4.0.0) |
Unchanged |
new Region(window) copy |
Native provenance dropped | Provenance propagated; tracks propagated verbatim |
new Region(window).setRect(...) sub-rect inside window |
Orphan Region, Swing biggest-part highlight, single-monitor capture | Native ROI overlay across screens, PrintWindow + physical crop |
window.getInset(inset), offset, grow, getRow/Col/Cell, get(part) |
Orphan derived Region | Contains-guarded inheritance: native route if the derived rect fits in the window, legacy path otherwise |
union(other) / intersection(other) |
Orphan derived Region | Two-parent guard: inherits only if the other parent is not window-bound or is bound to the same window |
Match returned by find, findAll, findText, observers |
Orphan Match | toGlobalCoord and relocate hand down provenance when the match rect fits in the window |
setX/Y/W/H on a whole-window Region |
tracks stayed true, bounds re-cropped by initScreen |
tracks flips to false; if the new rect leaves the window, sourceWindow detaches; initScreen no longer clips window-backed bounds |
setCenter, setTopRight, setBottomLeft, setBottomRight, setSize, setRect(int,int,int,int), setLocation, add(int,int,int,int) |
Left tracks=true on a mutated region — incoherent state |
Same invariant guard as setX/Y/W/H via markAsDerivedWindowRegion() |
setSourceWindow(otherWindow) on a tracks=true Region |
Silently kept tracks=true — implicitly promoted to "I am the whole other window" |
tracks flips to false; only Region.forWindow may claim whole-window identity |
captureSelf on a window-backed Region |
Reused a 3 s TTL bitmap cache — waitForStable / wait / exists could false-positive on stale pixels |
Cache removed; every call photographs now via PrintWindow |
captureSelf on a ROI after the window moved externally |
Silent stale crop or truncated bitmap | Live containment revalidation before native use; detaches and falls back on legacy if the ROI escaped |
highlight() / highlightOn() (persistent, secs <= 0) on a window-backed Region |
Native overlay flashed and was destroyed immediately | Persistent mode routes to Swing Highlight (toggle API preserved); native reserved for secs > 0 |
🆕 New public API
Region.forWindow(OsWindow window) — the single canonical path that asserts the "I am this whole window" identity by setting tracksSourceWindowBounds = true. Returns a Region with:
- raw
window.getBounds()— noinitScreenclipping sourceWindow = windowtracksSourceWindowBounds = true- screen resolved via
resolveScreenNoClip()(bounds untouched,scrstill assigned for legacy paths)
Returns null on a null window or null-bounds window. App.focusedWindow, App.window(int), and App.getWindows() all now route through this factory (previously App.getWindows() forgot to attach sourceWindow, silently disabling the native route on every non-focused window).
OsWindow.highlightRegionNative(Rectangle roiLogical, int argb, double secs) — SPI counterpart of highlightNative for a sub-rectangle of the window. Same WS_EX_LAYERED + UpdateLayeredWindow mechanism, positioned on the ROI's physical coordinates via WinUtil.logicalToPhysical (manual inverse of the existing voie-1 physicalToLogical, symmetric under the same AWT/monitor resolution so logicalToPhysical(physicalToLogical(r)) == r within rounding). Default no-op on non-Windows; callers fall back to Swing Highlight.
🏛️ Architectural doctrine — the real palier
The interesting piece is not the individual bug fix but the abstraction it establishes: a visual Region that transports its window native provenance across geometric operations and Matches, distinguishes whole-window identity from sub-region identity, and refuses to route native primitives when the geometric contract would be violated.
Concretely:
tracksSourceWindowBounds(package-private, testable in-package without a public accessor) — the boolean that says "this Region is the window" (true) vs "this Region is a ROI within the window" (false). Set totrueonly byRegion.forWindow. Propagated verbatim only by the two copy paths (new Region(r)viainit(r),Region.create(Region r)). Every other path — inheritance funnel, Match funnels, direct setters — forces it tofalse.deriveWithinWindow(Rectangle desiredRect)— the private funnel every geometric derivation goes through. Contains-guarded: if the derived rect fits in the window, returns a fresh Region with the provenance attached andtracks=false; otherwise returnsnulland the caller keeps its exact historical construction path. The funnel does one thing (attempt a window-backed derivation); each caller owns its own fallback so no legacyScreen-attachment behaviour is silently altered.markAsDerivedWindowRegion()— the invariant guard called by every direct bounds mutator. Flipstrackstofalse, and if the resulting rect escapes the window it detachessourceWindowoutright. Same rule as the funnel: provenance never survives a rect that isn't contained.- Live revalidation at the point of native use —
captureSelfanddoHighlightre-checksourceWindow.getBounds().contains(requestedRect)immediately before callingcaptureNative/highlightRegionNative. Handles the case where the user drags the actual OS window between the Region's construction and its consumption, without any OculiX write firing. captureSelfis live — no timed cache. If a specific operation needs two treatments to share the same snapshot, the snapshot is scoped to that operation (via the existingScreenImage baseparameter threaded through Finder paths), not to a Region-scoped TTL.- Persistent highlight stays on Swing — the native
WS_EX_LAYEREDoverlay is timed-only (secs > 0);secs <= 0falls through to the classicHighlightsohighlight()/highlightOn()/highlightOff()keep their toggle semantics unchanged.
⚠️ Behaviour changes for existing users
None break existing scripts. Listed here for completeness:
WinUtil.physicalToLogicalunscaled-monitor shortcut now requires the AWT logical origin to match the monitor's physical origin. Previously it returned the physical rectangle as-is on any 100 %-scaled monitor. On multi-monitor topologies where AWT shifts an unscaled monitor's logical origin because another monitor is scaled, the shortcut used to silently return physical coordinates as logical. Now falls through to the full remap in that case.- Native capture cache removed. Rapid successive calls to
captureSelf/getImage/findon a window-backed Region no longer reuse a shared bitmap for 3 s. Latencies are back to per-callPrintWindowcost (typically 30–60 ms on Notepad-class targets; validated at avg 43 ms over 10 iterations on a straddling mixed-DPI window). Content variance across successive calls is zero on idle windows — the cache's original justification ("physicalToLogical drifts between calls") does not reproduce in practice. captureSelfon a window-backed ROI now returns a real crop (getSubimagefrom the full window bitmap) instead of silently returning the full window and mutating the ROI's bounds. Match coordinates stay in the caller's coordinate space.- A mutator that pushes a Region out of its
sourceWindowdetaches provenance and falls back to the classicScreen-device path on next use. Previously the provenance stayed attached and the crop/highlight would read stale or misaligned pixels.
🧑💻 Uwe's pattern
Uwe's original code (from the [#444] report of 22/07/2026) — architecturally cannot be made to work verbatim, because the 4-int constructor cannot recover provenance:
:::java
Region window = App.focusedWindow();
Region statusBar = new Region(
window.getX(),
window.getY() + window.getH() - statusBarHeightPx,
window.getW(),
statusBarHeightPx);
statusBar.highlight(5); // → Swing biggest-part overlay, single monitor only
Recommended replacement pattern, enabled by this release:
:::java
Region window = App.focusedWindow();
Region statusBar = new Region(window); // copy → sourceWindow inherited
statusBar.setRect( // setRect in ONE call (no intermediate rect)
window.getX(),
window.getY() + window.getH() - statusBarHeightPx,
window.getW(),
statusBarHeightPx);
statusBar.highlight(5); // → native WS_EX_LAYERED ROI overlay across screens
statusBar.text(); // → PrintWindow full capture + physical ROI crop
Doctrine note in the test suite: prefer setRect(x, y, w, h) over sequential setX / setY / setW / setH when replacing multiple fields at once. Sequential setters run markAsDerivedWindowRegion at each step; an intermediate rect (with only some fields updated) can transiently escape the window and detach sourceWindow mid-sequence. setRect writes all four fields, then runs the guard once on the final rect. See RegionSourceWindowTest.uweStatusBarPattern_originalConstructorIsOrphan_copyThenMutateInheritsProvenance — the two patterns are frozen side-by-side as an executable contract.
🔬 Live validation
Six-scenario matrix on a real 3-screen mixed-DPI Windows 10 setup with a Notepad instance straddling the primary and the right-adjacent screen:
| # | Scenario | Result |
|---|---|---|
| 1 | App.focusedWindow() grabs Notepad |
Bounds (1385, 332, 1049×455), sourceWindow attached |
| 2 | window.highlight(3) |
One continuous red frame across both screens (visually confirmed) |
| 3 | window.getImage() — PrintWindow whole-window |
Exact 1049×455 bitmap, no black off-screen pixels |
| 4 | new Region(window) + setRect(...) status-bar ROI, highlight(3) |
Continuous 30-px frame spanning both screens (visually confirmed) |
| 5 | Ten repeated getImage() on the ROI |
Latencies min=30, max=64, avg=43 ms; 1 distinct content hash out of 10 captures — zero variance to hide, cache removal has no downside |
| 6 | window.highlight() (persistent) then highlightOff() |
Frame stays visible until off (Swing path, native flash-and-die avoided) |
Unit test suite: 44 tests / 44 pass in RegionSourceWindowTest — covers forWindow, copy path propagation, cache lifecycle absence, contains-guarded funnel, extended setters family, HWND change flip, Match funnels, ROI crop with pixel sentinels (motif décodable pixel(x,y) = 0xFF000000 | (x<<8) | y, asserts crop origin comes from the right offset), persistent-vs-timed highlight routing, spanning-logical shapes (rect -500, 100, 3000, 600), and Uwe's pattern frozen side-by-side.
Also validated informally: App.focusedWindow().text() reads 153 chars including French accents from Notepad, and 2410 chars from a Windows Terminal instance (which happened to accidentally grab focus during setup) — proving PrintWindow + Tesseract handles complex multi-column UIs correctly across mixed-DPI screens.
🚧 Known limitations (documented, not fixed here)
new Region(int, int, int, int)remains orphan. The 4-int constructor cannot recover which window the numbers came from — provenance is irreducible without a parent object. Use the copy + mutate pattern above instead.- ROI crossing two monitors with different physical DPI.
logicalToPhysicaluses oneMonitorFromWindowresult with a single(sx, sy)pair, symmetric withphysicalToLogicaland correct for Uwe's use case (window straddling monitors of the same DPI, or ROI mostly on one physical monitor). A ROI that physically straddles two monitors of different DPI would need per-monitor decomposition — deferred to a dedicated session with a comparative test matrix. PhysicalToLogicalPointForPerMonitorDPI/LogicalToPhysicalPointForPerMonitorDPI(Win 8.1+ APIs) not adopted here. They convert according to the HWND's DPI awareness rather than to AWT's logical space, which is not guaranteed to match when the target window belongs to a process with a different DPI awareness than the JVM. Explored, benched conceptually, and left for a session that can compare them against the current voie-1 on multiple DPI-awareness cases.
👥 Reviewers & contributors
- @uwekoenig — reporter, driver, live tester on his personal 4K mixed-DPI Windows setup (both the July validation of the whole-window fix and the 22/07 status-bar report that triggered this pass)
- GPT-Sol — architectural audit (two full passes on the branch head, surfaced six architectural findings pre-merge: setter family gap,
setSourceWindowtracks retention, cache staleness onwaitForStable, ROI containment revalidation, persistent-highlight regression, comparative-framework claim over-reach)
📦 Assets
Uploaded automatically by release-rc.yml from the branch build:
oculixapi-4.1.0-rc1-windows.jar/-macos.jar/-linux.jaroculixide-4.1.0-rc1-windows.jar/-macos.jar/-linux.jaroculix-mcp-server-4.1.0-rc1.jar(cross-platform)
📡 Distribution
GitHub-only pre-release. Not published to Maven Central. /releases/latest continues to point to v4.0.0 stable — gh release view --json isLatest on this tag reports false. Users who want to try the fix on their own mixed-DPI setup download the oculixide fat jar for their OS from this release page.