Release OpenScreen 1.4.2

This commit is contained in:
huanld
2026-05-28 10:01:22 +07:00
parent 69804c41c7
commit 198dc022b0
25 changed files with 844 additions and 82 deletions
+1 -1
View File
@@ -46,7 +46,7 @@ Build the Windows helper with:
npm run build:native:win
```
The build writes the CMake output to `electron/native/wgc-capture/build/wgc-capture.exe` and copies the redistributable binary to `electron/native/bin/win32-x64/wgc-capture.exe`.
The build writes the CMake output to `electron/native/wgc-capture/build/wgc-capture.exe` and copies the redistributable binary to `electron/native/bin/win32-x64/wgc-capture.exe`. It also builds `cursor-sampler.exe` for editable cursor telemetry and `guide-hotkey-listener.exe` for the Guide Mode global Ctrl capture hook.
The helper contract is process-based: the app starts the process with one JSON argument and sends commands on stdin. `stop\n` finalizes the recording. During migration the helper prints both newline-delimited JSON events and the legacy text messages `Recording started` / `Recording stopped. Output path: <path>`.
@@ -65,3 +65,19 @@ target_link_libraries(cursor-sampler PRIVATE
gdi32
gdiplus
)
add_executable(guide-hotkey-listener
src/guide-hotkey-listener.cpp
)
target_compile_definitions(guide-hotkey-listener PRIVATE
NOMINMAX
WIN32_LEAN_AND_MEAN
_WIN32_WINNT=0x0A00
)
target_compile_options(guide-hotkey-listener PRIVATE /EHsc /W4 /utf-8)
target_link_libraries(guide-hotkey-listener PRIVATE
user32
)
@@ -0,0 +1,91 @@
#include <windows.h>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <iostream>
#include <mutex>
#include <string>
static HHOOK g_keyboardHook = nullptr;
static DWORD g_mainThreadId = 0;
static std::atomic<bool> g_ctrlDown{false};
static std::mutex g_stdoutMutex;
static int64_t nowMs() {
return static_cast<int64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count());
}
static void writeJsonLine(const std::string& json) {
std::lock_guard<std::mutex> lock(g_stdoutMutex);
std::cout << json << '\n';
std::cout.flush();
}
static bool isCtrlKey(DWORD vkCode) {
return vkCode == VK_CONTROL || vkCode == VK_LCONTROL || vkCode == VK_RCONTROL;
}
static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode >= 0) {
const auto* event = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
if (event && isCtrlKey(event->vkCode)) {
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
const bool wasDown = g_ctrlDown.exchange(true, std::memory_order_acq_rel);
if (!wasDown) {
writeJsonLine(
"{\"event\":\"guide-hotkey\",\"key\":\"control\",\"state\":\"down\",\"timeMs\":" +
std::to_string(nowMs()) + "}");
}
} else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) {
g_ctrlDown.store(false, std::memory_order_release);
}
}
}
return CallNextHookEx(g_keyboardHook, nCode, wParam, lParam);
}
static BOOL WINAPI consoleCtrlHandler(DWORD signal) {
if (
signal == CTRL_C_EVENT ||
signal == CTRL_BREAK_EVENT ||
signal == CTRL_CLOSE_EVENT ||
signal == CTRL_LOGOFF_EVENT ||
signal == CTRL_SHUTDOWN_EVENT
) {
PostThreadMessage(g_mainThreadId, WM_QUIT, 0, 0);
return TRUE;
}
return FALSE;
}
int main() {
g_mainThreadId = GetCurrentThreadId();
SetConsoleCtrlHandler(consoleCtrlHandler, TRUE);
g_keyboardHook = SetWindowsHookExW(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandleW(nullptr), 0);
if (!g_keyboardHook) {
std::cerr << "Failed to install guide hotkey keyboard hook. error=" << GetLastError() << std::endl;
return 1;
}
writeJsonLine("{\"event\":\"ready\"}");
MSG msg{};
while (GetMessageW(&msg, nullptr, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
if (g_keyboardHook) {
UnhookWindowsHookEx(g_keyboardHook);
g_keyboardHook = nullptr;
}
return 0;
}
+1
View File
@@ -400,6 +400,7 @@ int main(int argc, char* argv[]) {
if (config.sourceType == "display") {
HMONITOR monitor = findMonitorForCapture(
config.displayId,
config.sourceId,
config.hasDisplayBounds ? &config.bounds : nullptr);
if (!monitor) {
std::cerr << "ERROR: Could not resolve monitor" << std::endl;
@@ -2,6 +2,7 @@
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
namespace {
@@ -43,9 +44,36 @@ int64_t overlapArea(const RECT& rect, const MonitorBounds& bounds) {
return static_cast<int64_t>(right - left) * static_cast<int64_t>(bottom - top);
}
int parseScreenSourceIndex(const std::string& sourceId) {
constexpr char prefix[] = "screen:";
if (sourceId.rfind(prefix, 0) != 0) {
return -1;
}
const size_t start = sizeof(prefix) - 1;
const size_t end = sourceId.find(':', start);
const std::string indexText = sourceId.substr(
start,
end == std::string::npos ? std::string::npos : end - start);
if (indexText.empty()) {
return -1;
}
try {
size_t parsed = 0;
const int index = std::stoi(indexText, &parsed, 10);
return parsed == indexText.size() && index >= 0 ? index : -1;
} catch (...) {
return -1;
}
}
} // namespace
HMONITOR findMonitorForCapture(int64_t displayId, const MonitorBounds* bounds) {
HMONITOR findMonitorForCapture(
int64_t displayId,
const std::string& sourceId,
const MonitorBounds* bounds) {
const auto monitors = enumerateMonitors();
if (monitors.empty()) {
return MonitorFromPoint({0, 0}, MONITOR_DEFAULTTOPRIMARY);
@@ -84,5 +112,10 @@ HMONITOR findMonitorForCapture(int64_t displayId, const MonitorBounds* bounds) {
}
}
const int sourceIndex = parseScreenSourceIndex(sourceId);
if (sourceIndex >= 0 && static_cast<size_t>(sourceIndex) < monitors.size()) {
return monitors[static_cast<size_t>(sourceIndex)].monitor;
}
return MonitorFromPoint({0, 0}, MONITOR_DEFAULTTOPRIMARY);
}
@@ -3,6 +3,7 @@
#include <Windows.h>
#include <cstdint>
#include <string>
struct MonitorBounds {
int x = 0;
@@ -11,4 +12,7 @@ struct MonitorBounds {
int height = 0;
};
HMONITOR findMonitorForCapture(int64_t displayId, const MonitorBounds* bounds);
HMONITOR findMonitorForCapture(
int64_t displayId,
const std::string& sourceId,
const MonitorBounds* bounds);