fix: gate Windows cursor settings
This commit is contained in:
@@ -111,6 +111,62 @@ function Write-JsonLine($payload) {
|
||||
[Console]::Out.WriteLine(($payload | ConvertTo-Json -Compress -Depth 6))
|
||||
}
|
||||
|
||||
function Get-CustomCursorType($bitmap, $hotspotX, $hotspotY) {
|
||||
if ($bitmap.Width -lt 24 -or $bitmap.Height -lt 24 -or $bitmap.Width -gt 64 -or $bitmap.Height -gt 64) {
|
||||
return $null
|
||||
}
|
||||
|
||||
if ($hotspotX -lt ($bitmap.Width * 0.25) -or $hotspotX -gt ($bitmap.Width * 0.75) -or
|
||||
$hotspotY -lt ($bitmap.Height * 0.15) -or $hotspotY -gt ($bitmap.Height * 0.55)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$opaquePixels = 0
|
||||
$topHalfOpaquePixels = 0
|
||||
$left = $bitmap.Width
|
||||
$top = $bitmap.Height
|
||||
$right = -1
|
||||
$bottom = -1
|
||||
|
||||
for ($y = 0; $y -lt $bitmap.Height; $y++) {
|
||||
for ($x = 0; $x -lt $bitmap.Width; $x++) {
|
||||
if ($bitmap.GetPixel($x, $y).A -le 32) {
|
||||
continue
|
||||
}
|
||||
|
||||
$opaquePixels += 1
|
||||
if ($y -lt ($bitmap.Height / 2)) {
|
||||
$topHalfOpaquePixels += 1
|
||||
}
|
||||
if ($x -lt $left) { $left = $x }
|
||||
if ($x -gt $right) { $right = $x }
|
||||
if ($y -lt $top) { $top = $y }
|
||||
if ($y -gt $bottom) { $bottom = $y }
|
||||
}
|
||||
}
|
||||
|
||||
if ($opaquePixels -lt 90 -or $right -lt $left -or $bottom -lt $top) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$opaqueWidth = $right - $left + 1
|
||||
$opaqueHeight = $bottom - $top + 1
|
||||
if ($opaqueWidth -lt ($bitmap.Width * 0.35) -or $opaqueWidth -gt ($bitmap.Width * 0.9) -or
|
||||
$opaqueHeight -lt ($bitmap.Height * 0.45) -or $opaqueHeight -gt $bitmap.Height) {
|
||||
return $null
|
||||
}
|
||||
|
||||
if ($top -gt ($bitmap.Height * 0.45) -or $bottom -lt ($bitmap.Height * 0.65)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
if ($topHalfOpaquePixels -gt ($opaquePixels * 0.55)) {
|
||||
return 'closed-hand'
|
||||
}
|
||||
|
||||
return 'open-hand'
|
||||
}
|
||||
|
||||
function Get-TargetBounds() {
|
||||
if ([string]::IsNullOrWhiteSpace($targetWindowHandle)) {
|
||||
return $null
|
||||
@@ -164,6 +220,9 @@ function Get-CursorAsset($cursorHandle, $cursorId) {
|
||||
try {
|
||||
$graphics.Clear([System.Drawing.Color]::Transparent)
|
||||
$graphics.DrawIcon($icon, 0, 0)
|
||||
$hotspotX = if ($hasIconInfo) { $iconInfo.xHotspot } else { 0 }
|
||||
$hotspotY = if ($hasIconInfo) { $iconInfo.yHotspot } else { 0 }
|
||||
$customCursorType = Get-CustomCursorType -bitmap $bitmap -hotspotX $hotspotX -hotspotY $hotspotY
|
||||
$bitmap.Save($memoryStream, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
$base64 = [System.Convert]::ToBase64String($memoryStream.ToArray())
|
||||
|
||||
@@ -172,8 +231,9 @@ function Get-CursorAsset($cursorHandle, $cursorId) {
|
||||
imageDataUrl = "data:image/png;base64,$base64"
|
||||
width = $bitmap.Width
|
||||
height = $bitmap.Height
|
||||
hotspotX = if ($hasIconInfo) { $iconInfo.xHotspot } else { 0 }
|
||||
hotspotY = if ($hasIconInfo) { $iconInfo.yHotspot } else { 0 }
|
||||
hotspotX = $hotspotX
|
||||
hotspotY = $hotspotY
|
||||
cursorType = $customCursorType
|
||||
}
|
||||
}
|
||||
finally {
|
||||
@@ -218,6 +278,8 @@ while ($true) {
|
||||
$asset = Get-CursorAsset -cursorHandle $cursorInfo.hCursor -cursorId $cursorId
|
||||
if ($asset -and $cursorType) {
|
||||
$asset.cursorType = $cursorType
|
||||
} elseif ($asset -and $asset.cursorType) {
|
||||
$cursorType = $asset.cursorType
|
||||
}
|
||||
$lastCursorId = $cursorId
|
||||
}
|
||||
|
||||
@@ -195,6 +195,62 @@ function Write-JsonLine($payload) {
|
||||
[Console]::Out.WriteLine(($payload | ConvertTo-Json -Compress -Depth 6))
|
||||
}
|
||||
|
||||
function Get-CustomCursorType($bitmap, $hotspotX, $hotspotY) {
|
||||
if ($bitmap.Width -lt 24 -or $bitmap.Height -lt 24 -or $bitmap.Width -gt 64 -or $bitmap.Height -gt 64) {
|
||||
return $null
|
||||
}
|
||||
|
||||
if ($hotspotX -lt ($bitmap.Width * 0.25) -or $hotspotX -gt ($bitmap.Width * 0.75) -or
|
||||
$hotspotY -lt ($bitmap.Height * 0.15) -or $hotspotY -gt ($bitmap.Height * 0.55)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$opaquePixels = 0
|
||||
$topHalfOpaquePixels = 0
|
||||
$left = $bitmap.Width
|
||||
$top = $bitmap.Height
|
||||
$right = -1
|
||||
$bottom = -1
|
||||
|
||||
for ($y = 0; $y -lt $bitmap.Height; $y++) {
|
||||
for ($x = 0; $x -lt $bitmap.Width; $x++) {
|
||||
if ($bitmap.GetPixel($x, $y).A -le 32) {
|
||||
continue
|
||||
}
|
||||
|
||||
$opaquePixels += 1
|
||||
if ($y -lt ($bitmap.Height / 2)) {
|
||||
$topHalfOpaquePixels += 1
|
||||
}
|
||||
if ($x -lt $left) { $left = $x }
|
||||
if ($x -gt $right) { $right = $x }
|
||||
if ($y -lt $top) { $top = $y }
|
||||
if ($y -gt $bottom) { $bottom = $y }
|
||||
}
|
||||
}
|
||||
|
||||
if ($opaquePixels -lt 90 -or $right -lt $left -or $bottom -lt $top) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$opaqueWidth = $right - $left + 1
|
||||
$opaqueHeight = $bottom - $top + 1
|
||||
if ($opaqueWidth -lt ($bitmap.Width * 0.35) -or $opaqueWidth -gt ($bitmap.Width * 0.9) -or
|
||||
$opaqueHeight -lt ($bitmap.Height * 0.45) -or $opaqueHeight -gt $bitmap.Height) {
|
||||
return $null
|
||||
}
|
||||
|
||||
if ($top -gt ($bitmap.Height * 0.45) -or $bottom -lt ($bitmap.Height * 0.65)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
if ($topHalfOpaquePixels -gt ($opaquePixels * 0.55)) {
|
||||
return 'closed-hand'
|
||||
}
|
||||
|
||||
return 'open-hand'
|
||||
}
|
||||
|
||||
function Get-CursorAsset($cursorHandle, $cursorId) {
|
||||
$copiedHandle = [OpenScreenCursorDiagnosticInterop]::CopyIcon($cursorHandle)
|
||||
if ($copiedHandle -eq [IntPtr]::Zero) {
|
||||
@@ -213,6 +269,9 @@ function Get-CursorAsset($cursorHandle, $cursorId) {
|
||||
try {
|
||||
$graphics.Clear([System.Drawing.Color]::Transparent)
|
||||
$graphics.DrawIcon($icon, 0, 0)
|
||||
$hotspotX = if ($hasIconInfo) { $iconInfo.xHotspot } else { 0 }
|
||||
$hotspotY = if ($hasIconInfo) { $iconInfo.yHotspot } else { 0 }
|
||||
$customCursorType = Get-CustomCursorType -bitmap $bitmap -hotspotX $hotspotX -hotspotY $hotspotY
|
||||
$bitmap.Save($memoryStream, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
$base64 = [System.Convert]::ToBase64String($memoryStream.ToArray())
|
||||
|
||||
@@ -221,8 +280,9 @@ function Get-CursorAsset($cursorHandle, $cursorId) {
|
||||
imageDataUrl = "data:image/png;base64,$base64"
|
||||
width = $bitmap.Width
|
||||
height = $bitmap.Height
|
||||
hotspotX = if ($hasIconInfo) { $iconInfo.xHotspot } else { 0 }
|
||||
hotspotY = if ($hasIconInfo) { $iconInfo.yHotspot } else { 0 }
|
||||
hotspotX = $hotspotX
|
||||
hotspotY = $hotspotY
|
||||
cursorType = $customCursorType
|
||||
}
|
||||
}
|
||||
finally {
|
||||
@@ -268,6 +328,8 @@ while ($true) {
|
||||
$asset = Get-CursorAsset -cursorHandle $cursorInfo.hCursor -cursorId $cursorId
|
||||
if ($asset -and $cursorType) {
|
||||
$asset.cursorType = $cursorType
|
||||
} elseif ($asset -and $asset.cursorType) {
|
||||
$cursorType = $asset.cursorType
|
||||
}
|
||||
$lastCursorId = $cursorId
|
||||
}
|
||||
@@ -1068,6 +1130,7 @@ const report = {
|
||||
height: asset.height,
|
||||
hotspotX: asset.hotspotX,
|
||||
hotspotY: asset.hotspotY,
|
||||
cursorType: asset.cursorType ?? null,
|
||||
})),
|
||||
};
|
||||
const recordingData = toRecordingData(samples, assets);
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
} from "@/lib/userPreferences";
|
||||
import { BackgroundLoadError } from "@/lib/wallpaper";
|
||||
import { nativeBridgeClient, useCursorRecordingData, useCursorTelemetry } from "@/native";
|
||||
import type { NativePlatform } from "@/native/contracts";
|
||||
import {
|
||||
getAspectRatioValue,
|
||||
getNativeAspectRatioValue,
|
||||
@@ -164,6 +165,7 @@ export default function VideoEditor() {
|
||||
const [cursorSmoothing, setCursorSmoothing] = useState(DEFAULT_CURSOR_SMOOTHING);
|
||||
const [cursorMotionBlur, setCursorMotionBlur] = useState(DEFAULT_CURSOR_MOTION_BLUR);
|
||||
const [cursorClickBounce, setCursorClickBounce] = useState(DEFAULT_CURSOR_CLICK_BOUNCE);
|
||||
const [nativePlatform, setNativePlatform] = useState<NativePlatform | null>(null);
|
||||
|
||||
const videoPlaybackRef = useRef<VideoPlaybackRef>(null);
|
||||
|
||||
@@ -172,6 +174,7 @@ export default function VideoEditor() {
|
||||
const nextSpeedIdRef = useRef(1);
|
||||
|
||||
const { shortcuts, isMac } = useShortcuts();
|
||||
const showCursorSettings = nativePlatform === "win32";
|
||||
// Off-Mac doesn't have click telemetry, so force `onlyOnClicks` off for
|
||||
// renderers while keeping the persisted value intact for round-tripping.
|
||||
const effectiveCursorHighlight = useMemo(
|
||||
@@ -631,6 +634,27 @@ export default function VideoEditor() {
|
||||
};
|
||||
}, [handleLoadProject, handleSaveProject, handleSaveProjectAs]);
|
||||
|
||||
useEffect(() => {
|
||||
let canceled = false;
|
||||
nativeBridgeClient.system
|
||||
.getPlatform()
|
||||
.then((platform) => {
|
||||
if (!canceled) {
|
||||
setNativePlatform(platform);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn("Unable to resolve native platform for cursor settings:", error);
|
||||
if (!canceled) {
|
||||
setNativePlatform(null);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (cursorTelemetryError) {
|
||||
console.warn("Unable to load cursor telemetry:", cursorTelemetryError);
|
||||
@@ -1718,6 +1742,8 @@ export default function VideoEditor() {
|
||||
cursorTelemetry,
|
||||
cursorClickTimestamps,
|
||||
effectiveCursorHighlight,
|
||||
showCursor,
|
||||
cursorSize,
|
||||
t,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -2,6 +2,8 @@ import { type Container, Point } from "pixi.js";
|
||||
import appStartingUrl from "@/assets/cursors/Cursor=App-Starting.svg";
|
||||
import crosshairUrl from "@/assets/cursors/Cursor=Cross.svg";
|
||||
import arrowUrl from "@/assets/cursors/Cursor=Default.svg";
|
||||
import closedHandUrl from "@/assets/cursors/Cursor=Hand-(Grabbing).svg";
|
||||
import openHandUrl from "@/assets/cursors/Cursor=Hand-(Open).svg";
|
||||
import pointerUrl from "@/assets/cursors/Cursor=Hand-(Pointing).svg";
|
||||
import helpUrl from "@/assets/cursors/Cursor=Help.svg";
|
||||
import moveUrl from "@/assets/cursors/Cursor=Move.svg";
|
||||
@@ -78,6 +80,20 @@ const PRETTY_NATIVE_CURSOR_ASSETS: Partial<Record<NativeCursorType, PrettyNative
|
||||
hotspotX: 16,
|
||||
hotspotY: 16,
|
||||
},
|
||||
"open-hand": {
|
||||
imageDataUrl: openHandUrl,
|
||||
width: 32,
|
||||
height: 32,
|
||||
hotspotX: 16,
|
||||
hotspotY: 9,
|
||||
},
|
||||
"closed-hand": {
|
||||
imageDataUrl: closedHandUrl,
|
||||
width: 32,
|
||||
height: 32,
|
||||
hotspotX: 16,
|
||||
hotspotY: 9,
|
||||
},
|
||||
"resize-ew": {
|
||||
imageDataUrl: resizeEwUrl,
|
||||
width: 32,
|
||||
@@ -150,6 +166,22 @@ const PRETTY_NATIVE_CURSOR_ASSETS: Partial<Record<NativeCursorType, PrettyNative
|
||||
},
|
||||
};
|
||||
|
||||
function resolveUntypedPrettyNativeCursorAsset(asset: NativeCursorAsset) {
|
||||
if (asset.cursorType || asset.width < 24 || asset.width > 64 || asset.height < 24 || asset.height > 64) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hotspotXNorm = asset.hotspotX / asset.width;
|
||||
const hotspotYNorm = asset.hotspotY / asset.height;
|
||||
const looksLikeChromiumGrabCursor =
|
||||
hotspotXNorm >= 0.22 &&
|
||||
hotspotXNorm <= 0.55 &&
|
||||
hotspotYNorm >= 0.2 &&
|
||||
hotspotYNorm <= 0.45;
|
||||
|
||||
return looksLikeChromiumGrabCursor ? (PRETTY_NATIVE_CURSOR_ASSETS["open-hand"] ?? null) : null;
|
||||
}
|
||||
|
||||
export function hasNativeCursorRecordingData(
|
||||
recordingData: CursorRecordingData | null | undefined,
|
||||
): recordingData is CursorRecordingData {
|
||||
@@ -322,7 +354,9 @@ export function resolvePrettyNativeCursorAsset(
|
||||
sample?: CursorRecordingSample,
|
||||
) {
|
||||
const cursorType = sample?.cursorType ?? asset.cursorType ?? null;
|
||||
return cursorType ? (PRETTY_NATIVE_CURSOR_ASSETS[cursorType] ?? null) : null;
|
||||
return cursorType
|
||||
? (PRETTY_NATIVE_CURSOR_ASSETS[cursorType] ?? null)
|
||||
: resolveUntypedPrettyNativeCursorAsset(asset);
|
||||
}
|
||||
|
||||
export function resolveNativeCursorRenderAsset(
|
||||
|
||||
@@ -8,6 +8,8 @@ export type NativeCursorType =
|
||||
| "text"
|
||||
| "pointer"
|
||||
| "crosshair"
|
||||
| "open-hand"
|
||||
| "closed-hand"
|
||||
| "resize-ew"
|
||||
| "resize-ns"
|
||||
| "resize-nesw"
|
||||
|
||||
Reference in New Issue
Block a user