da933bf279
Click-through decides whether to swallow the mouse by hit-testing the document
under the cursor, and it learns where the cursor is from mousemove. Those keep
arriving while the window ignores the mouse only because of
`setIgnoreMouseEvents(true, { forward: true })`, and `forward` is
`@platform darwin,win32`. On Linux the moves stop the instant the HUD turns
click-through, so it never sees the pointer return to the bar: the bar is
visible, and clicking it hits whatever is behind.
Main can still see the cursor, so on Linux it polls and pushes the position to
the renderer, which runs its usual hit test on it. The decision and its rules
stay in one place — only the courier for that one input changes — and off-window
is sent as null, which is already how the renderer hands the mouse back.
66 lines
2.6 KiB
TypeScript
66 lines
2.6 KiB
TypeScript
/**
|
|
* Cursor→page coordinate math for the HUD's Linux click-through fallback.
|
|
*
|
|
* The HUD decides whether to swallow the mouse by hit-testing the document
|
|
* under the cursor, which needs a cursor position in CSS pixels. On macOS and
|
|
* Windows that arrives for free: `setIgnoreMouseEvents(true, { forward: true })`
|
|
* keeps mousemove flowing to the page even while the window is ignoring, so the
|
|
* renderer can always see where the pointer is and re-arm when it returns to
|
|
* the bar. `forward` is `@platform darwin,win32`. On Linux the moves stop the
|
|
* instant the window starts ignoring, the renderer's last known point freezes,
|
|
* and the HUD can never decide to be solid again — the bar goes permanently
|
|
* untouchable.
|
|
*
|
|
* Main can still see the cursor, so on Linux it polls and pushes the position
|
|
* in. This is the conversion that push needs, kept pure and separate because
|
|
* the two unit mismatches in it are exactly what silently makes a hit test miss
|
|
* by a hand's width.
|
|
*/
|
|
|
|
interface Point {
|
|
x: number
|
|
y: number
|
|
}
|
|
|
|
interface Bounds {
|
|
x: number
|
|
y: number
|
|
width: number
|
|
height: number
|
|
}
|
|
|
|
/**
|
|
* Screen-space cursor → the window's CSS pixel coordinates, or null when the
|
|
* cursor is outside the window.
|
|
*
|
|
* Two conversions, both load-bearing:
|
|
*
|
|
* - Screen to window: Electron reports the cursor in screen space and the
|
|
* window has its own origin, so the window's position has to come off
|
|
* first.
|
|
* - DIP to CSS: `getCursorScreenPoint()` and `getBounds()` both speak
|
|
* device-independent pixels, and `elementFromPoint` speaks CSS pixels. Those
|
|
* are the same number only at zoom 1. Anyone who has pressed ⌘− is reading
|
|
* a smaller page than the window is wide, and an unscaled point lands
|
|
* progressively further from the cursor the further it is from the origin.
|
|
*
|
|
* Returning null outside the window matters as much as the arithmetic: it is
|
|
* the renderer's signal that it has lost the cursor, which is what hands the
|
|
* window back to the app underneath instead of leaving it solid on a stale
|
|
* point.
|
|
*/
|
|
export function cursorPointInWindow(cursor: Point, bounds: Bounds, zoomFactor: number): Point | null {
|
|
const dx = cursor.x - bounds.x
|
|
const dy = cursor.y - bounds.y
|
|
|
|
if (dx < 0 || dy < 0 || dx >= bounds.width || dy >= bounds.height) {
|
|
return null
|
|
}
|
|
|
|
// A zero or bogus factor would divide the point into infinity; treat anything
|
|
// non-positive as unzoomed rather than poisoning the hit test.
|
|
const scale = Number.isFinite(zoomFactor) && zoomFactor > 0 ? zoomFactor : 1
|
|
|
|
return { x: dx / scale, y: dy / scale }
|
|
}
|