Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ jobs:
run: npm run patch && npm i
- name: Build
run: npm run build:release
- name: Test Windows host support
if: runner.os == 'Windows'
run: |
cmake -S . -B build-wsl-tests -DLIBNUT_BUILD_ADDON=OFF -DLIBNUT_BUILD_WSL_TESTS=ON
if ($LASTEXITCODE) { exit $LASTEXITCODE }
cmake --build build-wsl-tests --config Release
if ($LASTEXITCODE) { exit $LASTEXITCODE }
ctest --test-dir build-wsl-tests -C Release --output-on-failure
- name: Test Windows host protocol
if: runner.os == 'Windows'
run: npm run test:wsl-host
- name: Run tests
uses: GabrielBB/xvfb-action@v1
with:
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/tagged_release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ jobs:
run: npm run patch && npm i
- name: Build
run: npm run build:release
- name: Test Windows host support
if: runner.os == 'Windows'
run: |
cmake -S . -B build-wsl-tests -DLIBNUT_BUILD_ADDON=OFF -DLIBNUT_BUILD_WSL_TESTS=ON
if ($LASTEXITCODE) { exit $LASTEXITCODE }
cmake --build build-wsl-tests --config Release
if ($LASTEXITCODE) { exit $LASTEXITCODE }
ctest --test-dir build-wsl-tests -C Release --output-on-failure
- name: Test Windows host protocol
if: runner.os == 'Windows'
run: npm run test:wsl-host
- name: Run tests
uses: GabrielBB/xvfb-action@v1
with:
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
build/
build-wsl-tests/
node_modules/
prebuilds/
.idea
.vscode
.cache
cmake-build-debug
.DS_Store
.DS_Store
5 changes: 5 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ build/*.vcxproj*
build/*.sln
build/x64/
build/libnut.dir/
build/libnut-wsl-host.dir/
build/libnut-wsl-host-tests.dir/
build/Release/libnut-wsl-host-tests.exe
build/Debug/libnut-wsl-host-tests.exe
build-wsl-tests/
CMakeFiles
src
test
Expand Down
26 changes: 26 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,32 @@ set(CMAKE_CXX_STANDARD 17)

project(libnut)

option(LIBNUT_BUILD_ADDON "Build the Node-API addon" ON)
option(LIBNUT_BUILD_WSL_HOST "Build the persistent Windows host executable" ${WIN32})

if (LIBNUT_BUILD_WSL_HOST)
if (NOT WIN32)
message(FATAL_ERROR "The WSL host must be built with a Windows toolchain")
endif()
add_executable(libnut-wsl-host src/win32/wsl_host.cc)
target_link_libraries(libnut-wsl-host PRIVATE windowsapp user32 gdi32)
target_compile_options(libnut-wsl-host PRIVATE /W4 /EHsc /utf-8)
set_property(TARGET libnut-wsl-host PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
option(LIBNUT_BUILD_WSL_TESTS "Build Windows host support tests" OFF)
if (LIBNUT_BUILD_WSL_TESTS)
add_executable(libnut-wsl-host-tests test/wsl-host-unit.cc)
target_link_libraries(libnut-wsl-host-tests PRIVATE windowsapp user32)
target_compile_options(libnut-wsl-host-tests PRIVATE /W4 /EHsc /utf-8)
set_property(TARGET libnut-wsl-host-tests PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
enable_testing()
add_test(NAME wsl-host-support COMMAND libnut-wsl-host-tests)
endif()
endif()

if (NOT LIBNUT_BUILD_ADDON)
return()
endif()

# Source
set(SOURCE_FILES
src/main.cc
Expand Down
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,70 @@ npm run build:release
npm install
npm run build:debug
```

## Windows host helper for WSL

Windows builds also produce `build/Release/libnut-wsl-host.exe`. It is a C++17
executable used by the WSL providers in
[nut.js](https://github.com/nut-tree/nut.js). It runs independently of Node.js on
Windows and uses Windows APIs for mouse, keyboard, primary-screen capture,
clipboard text and window operations. The executable is included in the
existing Windows native package.

For a helper-only build, use a Windows SDK and Visual C++ toolchain:

```powershell
cmake -S . -B build -A x64 -DLIBNUT_BUILD_ADDON=OFF
cmake --build build --config Release
node test/wsl-host-protocol.js
```

The last command tests the protocol without capturing the desktop or sending
input. Build the helper for the same processor architecture as the Windows
native package. `LIBNUT_BUILD_WSL_HOST=OFF` disables this target when it is not
needed.

Add `-DLIBNUT_BUILD_WSL_TESTS=ON` to configure the native support tests, then run
`ctest --test-dir build -C Release --output-on-failure` after building. These
tests check key mappings, failed-release tracking, reply limits and bounded
window waits without sending input or capturing the desktop.

The helper must run in the signed-in user's interactive Windows session. A
service or SSH session is not a substitute. Windows may block input into
elevated applications or the secure desktop. Coordinates are physical pixels;
screen capture covers the primary monitor, as the nut.js screen interface
requires.

### Internal protocol

The helper reads one UTF-8 JSON request per line from standard input:

```json
{"id":1,"command":"hello","args":{}}
```

Each reply starts with a UTF-8 JSON line containing the matching `id`, either
`result` or `error`, and `byteLength`. A capture reply is followed immediately
by exactly `byteLength` raw, top-down BGRA pixel bytes. Other replies have a zero
byte length. The `hello` result reports protocol version 1; clients must check
it before sending desktop commands.

Requests execute in order. Mouse clicks and keyboard clicks are single helper
commands. Requests and JSON reply lines are limited to 1 MiB; captured pixel
data is limited to 128 MiB. An oversized reply is replaced with a bounded error
for that request, so subsequent commands can still use the connection.
Diagnostic messages go to standard error, never into the reply stream.

Key names map to the same fixed US keyboard codes as the native backend. Text
uses Unicode input instead. Window changes are requested asynchronously and
wait at most five seconds for each state change, returning false if it does not
complete. The waits also check whether the caller has disconnected.

Closing the input pipe cancels long typing or highlight operations and attempts
to release keys and mouse buttons held by the helper. Failed releases remain
tracked for another attempt while the helper runs. The explicit `releaseInput`
command reports failure, and shutdown reports rejected releases on standard
error. Windows restrictions can prevent cleanup from succeeding.
Clients must not automatically retry an interrupted command: its input may
already have reached an application.
The helper opens no network listener and performs no runtime compilation.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"patch": "node ./patch-packagename.js",
"build:debug": "cmake-js rebuild --CDCMAKE_OSX_ARCHITECTURES=\"arm64;x86_64\" --debug",
"build:release": "cmake-js rebuild --CDCMAKE_OSX_ARCHITECTURES=\"arm64;x86_64\"",
"test:wsl-host": "node test/wsl-host-protocol.js",
"prepublishOnly": "npm run build:release",
"publish:next": "npm publish --tag next"
},
Expand Down
Loading