Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7823507a18 |
+83
-24
@@ -426,6 +426,7 @@ let nativeWindowsCursorRecordingStartMs = 0;
|
||||
let nativeWindowsPauseStartedAtMs: number | null = null;
|
||||
let nativeWindowsPauseRanges: Array<{ startMs: number; endMs: number }> = [];
|
||||
let nativeWindowsIsPaused = false;
|
||||
let nativeWindowsCaptureStopping = false;
|
||||
const NATIVE_WINDOWS_CAPTURE_STOP_TIMEOUT_MS = 15_000;
|
||||
let nativeMacCaptureProcess: ChildProcessWithoutNullStreams | null = null;
|
||||
let nativeMacCaptureOutput = "";
|
||||
@@ -1337,6 +1338,81 @@ function completeNativeWindowsCursorPauseRange(endMs = Date.now()) {
|
||||
nativeWindowsPauseStartedAtMs = null;
|
||||
}
|
||||
|
||||
function resetNativeWindowsCaptureState() {
|
||||
nativeWindowsCaptureProcess = null;
|
||||
nativeWindowsCaptureTargetPath = null;
|
||||
nativeWindowsCaptureWebcamTargetPath = null;
|
||||
nativeWindowsCaptureRecordingId = null;
|
||||
nativeWindowsCursorOffsetMs = 0;
|
||||
nativeWindowsCursorCaptureMode = "editable-overlay";
|
||||
nativeWindowsCursorRecordingStartMs = 0;
|
||||
nativeWindowsPauseStartedAtMs = null;
|
||||
nativeWindowsPauseRanges = [];
|
||||
nativeWindowsIsPaused = false;
|
||||
nativeWindowsCaptureStopping = false;
|
||||
clearGuideHotkeyRecording();
|
||||
}
|
||||
|
||||
function hasActiveNativeWindowsCaptureProcess() {
|
||||
const proc = nativeWindowsCaptureProcess;
|
||||
if (!proc) {
|
||||
return false;
|
||||
}
|
||||
if (proc.exitCode === null && !proc.killed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
console.warn("[native-wgc] clearing stale Windows capture process state", {
|
||||
exitCode: proc.exitCode,
|
||||
killed: proc.killed,
|
||||
});
|
||||
resetNativeWindowsCaptureState();
|
||||
return false;
|
||||
}
|
||||
|
||||
function attachNativeWindowsCaptureLifecycle(
|
||||
proc: ChildProcessWithoutNullStreams,
|
||||
sourceName: string,
|
||||
onRecordingStateChange?: (recording: boolean, sourceName: string) => void,
|
||||
) {
|
||||
const cleanupAfterUnexpectedExit = async () => {
|
||||
try {
|
||||
await stopCursorRecording();
|
||||
} catch (error) {
|
||||
console.warn("[native-wgc] failed to stop cursor recording after helper exit", error);
|
||||
}
|
||||
pendingCursorRecordingData = null;
|
||||
resetNativeWindowsCaptureState();
|
||||
onRecordingStateChange?.(false, sourceName);
|
||||
};
|
||||
|
||||
function onClose(code: number | null, signal: NodeJS.Signals | null) {
|
||||
proc.off("error", onError);
|
||||
if (nativeWindowsCaptureProcess !== proc || nativeWindowsCaptureStopping) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn("[native-wgc] Windows capture helper exited before stop was requested", {
|
||||
code,
|
||||
signal,
|
||||
output: nativeWindowsCaptureOutput.trim(),
|
||||
});
|
||||
void cleanupAfterUnexpectedExit();
|
||||
}
|
||||
function onError(error: Error) {
|
||||
proc.off("close", onClose);
|
||||
if (nativeWindowsCaptureProcess !== proc || nativeWindowsCaptureStopping) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn("[native-wgc] Windows capture helper errored before stop was requested", error);
|
||||
void cleanupAfterUnexpectedExit();
|
||||
}
|
||||
|
||||
proc.once("close", onClose);
|
||||
proc.once("error", onError);
|
||||
}
|
||||
|
||||
function waitForNativeWindowsCaptureStart(proc: ChildProcessWithoutNullStreams) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
@@ -2001,7 +2077,7 @@ export function registerIpcHandlers(
|
||||
error: "Windows Graphics Capture requires Windows 10 build 19041 or newer.",
|
||||
};
|
||||
}
|
||||
if (nativeWindowsCaptureProcess) {
|
||||
if (hasActiveNativeWindowsCaptureProcess()) {
|
||||
return { success: false, error: "Native Windows capture is already running." };
|
||||
}
|
||||
|
||||
@@ -2150,6 +2226,7 @@ export function registerIpcHandlers(
|
||||
});
|
||||
|
||||
const source = selectedSource || { name: "Screen" };
|
||||
attachNativeWindowsCaptureLifecycle(proc, source.name, onRecordingStateChange);
|
||||
startGuideHotkeyRecording(recordingId, bounds);
|
||||
if (onRecordingStateChange) {
|
||||
onRecordingStateChange(true, source.name);
|
||||
@@ -2164,17 +2241,7 @@ export function registerIpcHandlers(
|
||||
} catch (error) {
|
||||
console.error("Failed to start native Windows recording:", error);
|
||||
nativeWindowsCaptureProcess?.kill();
|
||||
nativeWindowsCaptureProcess = null;
|
||||
nativeWindowsCaptureTargetPath = null;
|
||||
nativeWindowsCaptureWebcamTargetPath = null;
|
||||
nativeWindowsCaptureRecordingId = null;
|
||||
nativeWindowsCursorOffsetMs = 0;
|
||||
nativeWindowsCursorCaptureMode = "editable-overlay";
|
||||
nativeWindowsCursorRecordingStartMs = 0;
|
||||
nativeWindowsPauseStartedAtMs = null;
|
||||
nativeWindowsPauseRanges = [];
|
||||
nativeWindowsIsPaused = false;
|
||||
clearGuideHotkeyRecording();
|
||||
resetNativeWindowsCaptureState();
|
||||
await stopCursorRecording();
|
||||
return { success: false, error: String(error) };
|
||||
}
|
||||
@@ -2433,11 +2500,13 @@ export function registerIpcHandlers(
|
||||
const recordingId = nativeWindowsCaptureRecordingId ?? Date.now();
|
||||
const cursorCaptureMode = nativeWindowsCursorCaptureMode;
|
||||
|
||||
if (!proc) {
|
||||
if (!proc || proc.exitCode !== null || proc.killed) {
|
||||
resetNativeWindowsCaptureState();
|
||||
return { success: false, error: "Native Windows capture is not running." };
|
||||
}
|
||||
|
||||
try {
|
||||
nativeWindowsCaptureStopping = true;
|
||||
completeNativeWindowsCursorPauseRange();
|
||||
const stoppedPathPromise = waitForNativeWindowsCaptureStop(proc);
|
||||
proc.stdin.write("stop\n");
|
||||
@@ -2499,17 +2568,7 @@ export function registerIpcHandlers(
|
||||
await stopCursorRecording();
|
||||
return { success: false, error: String(error) };
|
||||
} finally {
|
||||
nativeWindowsCaptureProcess = null;
|
||||
nativeWindowsCaptureTargetPath = null;
|
||||
nativeWindowsCaptureWebcamTargetPath = null;
|
||||
nativeWindowsCaptureRecordingId = null;
|
||||
nativeWindowsCursorOffsetMs = 0;
|
||||
nativeWindowsCursorCaptureMode = "editable-overlay";
|
||||
nativeWindowsCursorRecordingStartMs = 0;
|
||||
nativeWindowsPauseStartedAtMs = null;
|
||||
nativeWindowsPauseRanges = [];
|
||||
nativeWindowsIsPaused = false;
|
||||
clearGuideHotkeyRecording();
|
||||
resetNativeWindowsCaptureState();
|
||||
const source = selectedSource || { name: "Screen" };
|
||||
if (onRecordingStateChange) {
|
||||
onRecordingStateChange(false, source.name);
|
||||
|
||||
@@ -28,6 +28,60 @@ bool succeeded(HRESULT hr, const char* label) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IDXGIAdapter1> findAdapterForMonitor(HMONITOR monitor) {
|
||||
if (!monitor) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IDXGIFactory1> factory;
|
||||
HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory));
|
||||
if (FAILED(hr) || !factory) {
|
||||
std::cerr << "WARNING: CreateDXGIFactory1 failed while resolving monitor adapter (hr=0x"
|
||||
<< std::hex << hr << std::dec << ")" << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (UINT adapterIndex = 0;; ++adapterIndex) {
|
||||
Microsoft::WRL::ComPtr<IDXGIAdapter1> adapter;
|
||||
hr = factory->EnumAdapters1(adapterIndex, adapter.GetAddressOf());
|
||||
if (hr == DXGI_ERROR_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (FAILED(hr) || !adapter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DXGI_ADAPTER_DESC1 adapterDesc{};
|
||||
if (SUCCEEDED(adapter->GetDesc1(&adapterDesc)) &&
|
||||
(adapterDesc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (UINT outputIndex = 0;; ++outputIndex) {
|
||||
Microsoft::WRL::ComPtr<IDXGIOutput> output;
|
||||
hr = adapter->EnumOutputs(outputIndex, output.GetAddressOf());
|
||||
if (hr == DXGI_ERROR_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (FAILED(hr) || !output) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DXGI_OUTPUT_DESC outputDesc{};
|
||||
if (SUCCEEDED(output->GetDesc(&outputDesc)) && outputDesc.Monitor == monitor) {
|
||||
std::cout << "{\"event\":\"display-adapter-resolved\",\"schemaVersion\":2,"
|
||||
<< "\"adapterIndex\":" << adapterIndex
|
||||
<< ",\"outputIndex\":" << outputIndex << "}" << std::endl;
|
||||
return adapter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cerr << "WARNING: Could not resolve DXGI adapter for selected monitor; using default adapter"
|
||||
<< std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int64_t timeSpanToHns(wf::TimeSpan const& value) {
|
||||
return value.count();
|
||||
}
|
||||
@@ -38,7 +92,7 @@ WgcSession::~WgcSession() {
|
||||
stop();
|
||||
}
|
||||
|
||||
bool WgcSession::createD3DDevice() {
|
||||
bool WgcSession::createD3DDevice(IDXGIAdapter* adapter) {
|
||||
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
||||
#if defined(_DEBUG)
|
||||
flags |= D3D11_CREATE_DEVICE_DEBUG;
|
||||
@@ -53,8 +107,8 @@ bool WgcSession::createD3DDevice() {
|
||||
D3D_FEATURE_LEVEL featureLevel{};
|
||||
|
||||
HRESULT hr = D3D11CreateDevice(
|
||||
nullptr,
|
||||
D3D_DRIVER_TYPE_HARDWARE,
|
||||
adapter,
|
||||
adapter ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_HARDWARE,
|
||||
nullptr,
|
||||
flags,
|
||||
featureLevels,
|
||||
@@ -67,6 +121,23 @@ bool WgcSession::createD3DDevice() {
|
||||
#if defined(_DEBUG)
|
||||
if (FAILED(hr)) {
|
||||
flags &= ~D3D11_CREATE_DEVICE_DEBUG;
|
||||
hr = D3D11CreateDevice(
|
||||
adapter,
|
||||
adapter ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_HARDWARE,
|
||||
nullptr,
|
||||
flags,
|
||||
featureLevels,
|
||||
ARRAYSIZE(featureLevels),
|
||||
D3D11_SDK_VERSION,
|
||||
&d3dDevice_,
|
||||
&featureLevel,
|
||||
&d3dContext_);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (FAILED(hr) && adapter) {
|
||||
std::cerr << "WARNING: D3D11CreateDevice failed for selected monitor adapter (hr=0x"
|
||||
<< std::hex << hr << std::dec << "); retrying default adapter" << std::endl;
|
||||
hr = D3D11CreateDevice(
|
||||
nullptr,
|
||||
D3D_DRIVER_TYPE_HARDWARE,
|
||||
@@ -79,7 +150,6 @@ bool WgcSession::createD3DDevice() {
|
||||
&featureLevel,
|
||||
&d3dContext_);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!succeeded(hr, "D3D11CreateDevice")) {
|
||||
return false;
|
||||
@@ -100,6 +170,11 @@ bool WgcSession::createD3DDevice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WgcSession::createD3DDeviceForMonitor(HMONITOR monitor) {
|
||||
auto adapter = findAdapterForMonitor(monitor);
|
||||
return createD3DDevice(adapter.Get());
|
||||
}
|
||||
|
||||
bool WgcSession::createCaptureItem(HMONITOR monitor) {
|
||||
auto factory = winrt::get_activation_factory<wgcap::GraphicsCaptureItem>();
|
||||
auto interop = factory.as<IGraphicsCaptureItemInterop>();
|
||||
@@ -188,7 +263,7 @@ bool WgcSession::applySessionOptions(bool captureCursor) {
|
||||
|
||||
bool WgcSession::initialize(HMONITOR monitor, int fps, bool captureCursor) {
|
||||
fps_ = fps > 0 ? fps : 60;
|
||||
if (!createD3DDevice()) {
|
||||
if (!createD3DDeviceForMonitor(monitor)) {
|
||||
return false;
|
||||
}
|
||||
if (!createCaptureItem(monitor)) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <Windows.h>
|
||||
#include <d3d11.h>
|
||||
#include <dxgi.h>
|
||||
#include <windows.graphics.capture.h>
|
||||
#include <windows.graphics.directx.direct3d11.interop.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
@@ -34,7 +35,8 @@ public:
|
||||
ID3D11DeviceContext* context() const;
|
||||
|
||||
private:
|
||||
bool createD3DDevice();
|
||||
bool createD3DDevice(IDXGIAdapter* adapter = nullptr);
|
||||
bool createD3DDeviceForMonitor(HMONITOR monitor);
|
||||
bool createCaptureItem(HMONITOR monitor);
|
||||
bool createCaptureItem(HWND window);
|
||||
bool applySessionOptions(bool captureCursor);
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "openscreen",
|
||||
"version": "1.4.4",
|
||||
"version": "1.4.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "openscreen",
|
||||
"version": "1.4.4",
|
||||
"version": "1.4.6",
|
||||
"dependencies": {
|
||||
"@fix-webm-duration/fix": "^1.0.1",
|
||||
"@pixi/filter-drop-shadow": "^5.2.0",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "openscreen",
|
||||
"private": true,
|
||||
"version": "1.4.4",
|
||||
"version": "1.4.6",
|
||||
"type": "module",
|
||||
"packageManager": "npm@10.9.4",
|
||||
"engines": {
|
||||
|
||||
Reference in New Issue
Block a user