mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-27 00:05:39 +00:00
feat(windows): add native cursor type detection via cursor-monitor.exe
Build a standalone C++ helper (same pattern as wgc-capture) that polls GetCursorInfo() every 50ms and emits STATE:<type> to stdout — identical protocol to the macOS Swift helper. Windows recordings now capture correct cursor types (text, pointer, resize, etc.) instead of always falling back to arrow.
This commit is contained in:
+2
-1
@@ -29,5 +29,6 @@ release/**
|
||||
.tmp/
|
||||
.history/
|
||||
|
||||
# WGC native capture build artifacts
|
||||
# Native capture build artifacts
|
||||
electron/native/wgc-capture/build/
|
||||
electron/native/cursor-monitor/build/
|
||||
|
||||
+42
-30
@@ -710,6 +710,10 @@ function getWgcCaptureExePath() {
|
||||
return resolveUnpackedAppPath('electron', 'native', 'wgc-capture', 'build', 'Release', 'wgc-capture.exe')
|
||||
}
|
||||
|
||||
function getCursorMonitorExePath() {
|
||||
return resolveUnpackedAppPath('electron', 'native', 'cursor-monitor', 'build', 'Release', 'cursor-monitor.exe')
|
||||
}
|
||||
|
||||
async function isWgcCaptureAvailable(): Promise<boolean> {
|
||||
if (process.platform !== 'win32') return false
|
||||
|
||||
@@ -1067,50 +1071,58 @@ async function ensureNativeCursorMonitorBinary() {
|
||||
)
|
||||
}
|
||||
|
||||
function handleCursorMonitorStdout(chunk: Buffer) {
|
||||
nativeCursorMonitorOutputBuffer += chunk.toString()
|
||||
const lines = nativeCursorMonitorOutputBuffer.split(/\r?\n/)
|
||||
nativeCursorMonitorOutputBuffer = lines.pop() ?? ''
|
||||
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^STATE:(.+)$/)
|
||||
if (!match) continue
|
||||
const next = match[1].trim() as CursorVisualType
|
||||
if (
|
||||
next === 'arrow'
|
||||
|| next === 'text'
|
||||
|| next === 'pointer'
|
||||
|| next === 'crosshair'
|
||||
|| next === 'open-hand'
|
||||
|| next === 'closed-hand'
|
||||
|| next === 'resize-ew'
|
||||
|| next === 'resize-ns'
|
||||
|| next === 'not-allowed'
|
||||
) {
|
||||
if (currentCursorVisualType !== next) {
|
||||
currentCursorVisualType = next
|
||||
sampleCursorStateChange(next)
|
||||
emitCursorStateChanged(next)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function startNativeCursorMonitor() {
|
||||
stopNativeCursorMonitor()
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
if (process.platform !== 'darwin' && process.platform !== 'win32') {
|
||||
currentCursorVisualType = undefined
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const helperPath = await ensureNativeCursorMonitorBinary()
|
||||
let helperPath: string
|
||||
if (process.platform === 'win32') {
|
||||
helperPath = getCursorMonitorExePath()
|
||||
} else {
|
||||
helperPath = await ensureNativeCursorMonitorBinary()
|
||||
}
|
||||
|
||||
nativeCursorMonitorOutputBuffer = ''
|
||||
currentCursorVisualType = undefined
|
||||
nativeCursorMonitorProcess = spawn(helperPath, [], {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
})
|
||||
|
||||
nativeCursorMonitorProcess.stdout.on('data', (chunk: Buffer) => {
|
||||
nativeCursorMonitorOutputBuffer += chunk.toString()
|
||||
const lines = nativeCursorMonitorOutputBuffer.split(/\r?\n/)
|
||||
nativeCursorMonitorOutputBuffer = lines.pop() ?? ''
|
||||
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^STATE:(.+)$/)
|
||||
if (!match) continue
|
||||
const next = match[1].trim() as CursorVisualType
|
||||
if (
|
||||
next === 'arrow'
|
||||
|| next === 'text'
|
||||
|| next === 'pointer'
|
||||
|| next === 'crosshair'
|
||||
|| next === 'open-hand'
|
||||
|| next === 'closed-hand'
|
||||
|| next === 'resize-ew'
|
||||
|| next === 'resize-ns'
|
||||
|| next === 'not-allowed'
|
||||
) {
|
||||
if (currentCursorVisualType !== next) {
|
||||
currentCursorVisualType = next
|
||||
sampleCursorStateChange(next)
|
||||
emitCursorStateChanged(next)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
nativeCursorMonitorProcess.stdout.on('data', handleCursorMonitorStdout)
|
||||
|
||||
nativeCursorMonitorProcess.once('close', () => {
|
||||
nativeCursorMonitorProcess = null
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(cursor-monitor LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_executable(cursor-monitor src/main.cpp)
|
||||
|
||||
target_link_libraries(cursor-monitor PRIVATE user32)
|
||||
@@ -0,0 +1,59 @@
|
||||
#include <windows.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <unordered_map>
|
||||
|
||||
static std::atomic<bool> g_running{true};
|
||||
|
||||
static void stdinListener() {
|
||||
std::string line;
|
||||
while (std::getline(std::cin, line)) {
|
||||
if (line == "stop") {
|
||||
g_running.store(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
g_running.store(false);
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
|
||||
std::unordered_map<HCURSOR, std::string> cursorMap;
|
||||
cursorMap[LoadCursor(NULL, IDC_ARROW)] = "arrow";
|
||||
cursorMap[LoadCursor(NULL, IDC_IBEAM)] = "text";
|
||||
cursorMap[LoadCursor(NULL, IDC_HAND)] = "pointer";
|
||||
cursorMap[LoadCursor(NULL, IDC_CROSS)] = "crosshair";
|
||||
cursorMap[LoadCursor(NULL, IDC_NO)] = "not-allowed";
|
||||
cursorMap[LoadCursor(NULL, IDC_SIZEWE)] = "resize-ew";
|
||||
cursorMap[LoadCursor(NULL, IDC_SIZENS)] = "resize-ns";
|
||||
cursorMap[LoadCursor(NULL, IDC_SIZEALL)] = "open-hand";
|
||||
cursorMap[LoadCursor(NULL, IDC_WAIT)] = "arrow";
|
||||
cursorMap[LoadCursor(NULL, IDC_APPSTARTING)] = "arrow";
|
||||
|
||||
std::thread listener(stdinListener);
|
||||
listener.detach();
|
||||
|
||||
std::string lastType;
|
||||
|
||||
while (g_running.load()) {
|
||||
CURSORINFO ci = {};
|
||||
ci.cbSize = sizeof(ci);
|
||||
|
||||
if (GetCursorInfo(&ci) && (ci.flags & CURSOR_SHOWING)) {
|
||||
auto it = cursorMap.find(ci.hCursor);
|
||||
std::string type = (it != cursorMap.end()) ? it->second : "arrow";
|
||||
|
||||
if (type != lastType) {
|
||||
lastType = type;
|
||||
std::cout << "STATE:" << type << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
Sleep(50);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
+2
-1
@@ -25,8 +25,9 @@
|
||||
"rebuild:native": "electron-rebuild --force --only uiohook-napi",
|
||||
"build:native-helpers": "node scripts/build-native-helpers.mjs",
|
||||
"build:wgc-capture": "node scripts/build-wgc-capture.mjs",
|
||||
"build:cursor-monitor": "node scripts/build-cursor-monitor.mjs",
|
||||
"build:mac": "npm run build:native-helpers && tsc && vite build && electron-builder --mac",
|
||||
"build:win": "npm run build:wgc-capture && tsc && vite build && electron-builder --win",
|
||||
"build:win": "npm run build:wgc-capture && npm run build:cursor-monitor && tsc && vite build && electron-builder --win",
|
||||
"build:linux": "tsc && vite build && electron-builder --linux",
|
||||
"i18n:check": "node scripts/i18n-check.mjs",
|
||||
"test": "vitest --run",
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import { existsSync, mkdirSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const projectRoot = process.cwd();
|
||||
const sourceDir = path.join(projectRoot, "electron", "native", "cursor-monitor");
|
||||
const buildDir = path.join(sourceDir, "build");
|
||||
|
||||
if (process.platform !== "win32") {
|
||||
console.log("[build-cursor-monitor] Skipping: host platform is not Windows.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!existsSync(path.join(sourceDir, "CMakeLists.txt"))) {
|
||||
console.error("[build-cursor-monitor] CMakeLists.txt not found at", sourceDir);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function findCmake() {
|
||||
// Check PATH first
|
||||
try {
|
||||
execSync("cmake --version", { stdio: "pipe" });
|
||||
return "cmake";
|
||||
} catch {
|
||||
// not on PATH
|
||||
}
|
||||
|
||||
// VS 2022 bundled CMake
|
||||
const vsEditions = ["Community", "Professional", "Enterprise", "BuildTools"];
|
||||
for (const edition of vsEditions) {
|
||||
const cmakePath = path.join(
|
||||
"C:",
|
||||
"Program Files",
|
||||
"Microsoft Visual Studio",
|
||||
"2022",
|
||||
edition,
|
||||
"Common7",
|
||||
"IDE",
|
||||
"CommonExtensions",
|
||||
"Microsoft",
|
||||
"CMake",
|
||||
"CMake",
|
||||
"bin",
|
||||
"cmake.exe",
|
||||
);
|
||||
if (existsSync(cmakePath)) {
|
||||
return `"${cmakePath}"`;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const cmake = findCmake();
|
||||
if (!cmake) {
|
||||
console.error(
|
||||
"[build-cursor-monitor] CMake not found. Install Visual Studio with C++ CMake tools or standalone CMake.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
mkdirSync(buildDir, { recursive: true });
|
||||
|
||||
console.log("[build-cursor-monitor] Configuring CMake...");
|
||||
try {
|
||||
execSync(`${cmake} .. -G "Visual Studio 17 2022" -A x64`, {
|
||||
cwd: buildDir,
|
||||
stdio: "inherit",
|
||||
timeout: 120000,
|
||||
});
|
||||
} catch {
|
||||
console.log("[build-cursor-monitor] VS 2022 generator not found, trying VS 2019...");
|
||||
try {
|
||||
execSync(`${cmake} .. -G "Visual Studio 16 2019" -A x64`, {
|
||||
cwd: buildDir,
|
||||
stdio: "inherit",
|
||||
timeout: 120000,
|
||||
});
|
||||
} catch (innerError) {
|
||||
console.error("[build-cursor-monitor] CMake configure failed:", innerError.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("[build-cursor-monitor] Building...");
|
||||
try {
|
||||
execSync(`${cmake} --build . --config Release`, {
|
||||
cwd: buildDir,
|
||||
stdio: "inherit",
|
||||
timeout: 300000,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[build-cursor-monitor] Build failed:", error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const exePath = path.join(buildDir, "Release", "cursor-monitor.exe");
|
||||
if (existsSync(exePath)) {
|
||||
console.log(`[build-cursor-monitor] Built successfully: ${exePath}`);
|
||||
} else {
|
||||
console.error("[build-cursor-monitor] Expected exe not found at", exePath);
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user