feat: apply native cursor visual effects

This commit is contained in:
EtienneLescot
2026-05-05 21:13:02 +02:00
parent ab3d38d90f
commit d0341580d6
11 changed files with 340 additions and 19 deletions
@@ -50,6 +50,9 @@ public static class OpenScreenCursorInterop {
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetCursorInfo(ref CURSORINFO pci);
[DllImport("user32.dll")]
public static extern short GetAsyncKeyState(int vKey);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName);
@@ -276,6 +279,7 @@ while ($true) {
$visible = ($cursorInfo.flags -band 1) -ne 0
$cursorId = if ($cursorInfo.hCursor -eq [IntPtr]::Zero) { $null } else { ('0x{0:X}' -f $cursorInfo.hCursor.ToInt64()) }
$cursorType = Get-StandardCursorType $cursorInfo.hCursor
$leftButtonDown = ([OpenScreenCursorInterop]::GetAsyncKeyState(0x01) -band 0x8000) -ne 0
$asset = $null
if ($visible -and $cursorId -and $cursorId -ne $lastCursorId) {
@@ -296,6 +300,7 @@ while ($true) {
visible = $visible
handle = $cursorId
cursorType = $cursorType
leftButtonDown = $leftButtonDown
bounds = Get-TargetBounds
asset = $asset
}
@@ -32,6 +32,7 @@ export class WindowsNativeRecordingSession implements CursorRecordingSession {
private readyTimer: NodeJS.Timeout | null = null;
private sampleCount = 0;
private outOfBoundsSampleCount = 0;
private previousLeftButtonDown = false;
constructor(private readonly options: WindowsNativeRecordingSessionOptions) {}
@@ -42,6 +43,7 @@ export class WindowsNativeRecordingSession implements CursorRecordingSession {
this.startTimeMs = this.options.startTimeMs ?? Date.now();
this.sampleCount = 0;
this.outOfBoundsSampleCount = 0;
this.previousLeftButtonDown = false;
const encodedCommand = buildPowerShellCommand(
this.options.sampleIntervalMs,
@@ -208,6 +210,14 @@ export class WindowsNativeRecordingSession implements CursorRecordingSession {
const normalizedY = (payload.y - bounds.y) / height;
const withinBounds =
normalizedX >= 0 && normalizedX <= 1 && normalizedY >= 0 && normalizedY <= 1;
const leftButtonDown = payload.leftButtonDown === true;
const interactionType =
leftButtonDown && !this.previousLeftButtonDown
? "click"
: !leftButtonDown && this.previousLeftButtonDown
? "mouseup"
: "move";
this.previousLeftButtonDown = leftButtonDown;
if (this.sampleCount === 0 || (!withinBounds && this.outOfBoundsSampleCount === 0)) {
this.logDiagnostic("sample", {
@@ -231,6 +241,7 @@ export class WindowsNativeRecordingSession implements CursorRecordingSession {
assetId: payload.handle,
visible: payload.visible && withinBounds,
cursorType: payload.cursorType ?? payload.asset?.cursorType ?? null,
interactionType,
},
};
}
@@ -9,6 +9,7 @@ export interface WindowsCursorSampleEvent {
visible: boolean;
handle: string | null;
cursorType?: NativeCursorType | null;
leftButtonDown?: boolean;
bounds?: {
x: number;
y: number;