node-native-win-utils 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,25 +2,23 @@
2
2
 
3
3
  # Node Native Win Utils
4
4
 
5
- I did it for myself because I didn't feel like dealing with libraries like 'node-ffi' to implement this functionality. Maybe someone will find it useful. I'ts WINDOWS OS ONLY
5
+ I did it for myself because I didn't feel like dealing with libraries like 'node-ffi' to implement this functionality. Maybe someone will find it useful. It's WINDOWS OS ONLY
6
6
 
7
- This package provides a native addon for Node.js that allows you to perform various utility operations on Windows systems. It includes key event listeners, window data retrieval, and window screenshot capture functionality.
7
+ This package is a native addon for Node.js that allows you to perform various utility operations on Windows systems. It includes key event listeners, window data retrieval, window screenshot capture functionality, mouse movement, mouse click, mouse drag, and typing functionality.
8
8
 
9
9
  ## Installation
10
10
 
11
11
  You can install the package using npm:
12
12
 
13
13
  ```shell
14
-
15
- npm install node-native-win-utils
16
-
14
+ npm install node-native-win-utils
17
15
  ```
18
16
 
19
17
  ## Usage
20
18
 
21
19
  ### Importing the Package
22
20
 
23
- To use the package, import the necessary functions and types:
21
+ To use the package, import the necessary functions, types, and classes:
24
22
 
25
23
  ```javascript
26
24
  import {
@@ -28,6 +26,10 @@ import {
28
26
  keyUpHandler,
29
27
  getWindowData,
30
28
  captureWindow,
29
+ mouseMove,
30
+ mouseClick,
31
+ mouseDrag,
32
+ typeString,
31
33
  KeyListener,
32
34
  } from "node-native-win-utils";
33
35
  ```
@@ -72,6 +74,43 @@ captureWindow("Window Name", "output.png");
72
74
  // Output: output.png with a screenshot of the window
73
75
  ```
74
76
 
77
+ ### Mouse Movement
78
+
79
+ The `mouseMove` function allows you to move the mouse to a specific position on the screen. Provide the `posX` and `posY` coordinates as parameters:
80
+
81
+ ```javascript
82
+ mouseMove(100, 200);
83
+ ```
84
+
85
+ ### Mouse Click
86
+
87
+ The `mouseClick` function allows you to perform a mouse click event. Optionally, you can specify the mouse button as a parameter (`"left"`, `"middle"`, or `"right"`). If no button is specified, a left mouse click is performed by default:
88
+
89
+ ```javascript
90
+ mouseClick(); // Left mouse click
91
+ mouseClick("right"); // Right mouse click
92
+ ```
93
+
94
+ ### Mouse Drag
95
+
96
+ The `mouseDrag` function allows you to simulate dragging the mouse from one position to another. Provide the starting and ending coordinates (`startX`, `startY`, `endX`, `endY`) as parameters. Optionally, you can specify the speed at which the mouse should be dragged:
97
+
98
+ ```javascript
99
+ mouseDrag(100, 200, 300, 400);
100
+ mouseDrag(100, 200, 300, 400, 100); // Drag with speed 100
101
+ ```
102
+
103
+ ### Typing
104
+
105
+ The `typeString` function allows you to simulate typing a string of characters. Provide the string to type as the `stringToType` parameter. Optionally, you can specify
106
+
107
+ a delay between each character (in milliseconds) using the `delay` parameter:
108
+
109
+ ```javascript
110
+ typeString("Hello, world!");
111
+ typeString("Hello, world!", 100); // Type with a delay of 100ms between characters
112
+ ```
113
+
75
114
  ### Key Listener Class
76
115
 
77
116
  The `KeyListener` class extends the EventEmitter class and simplifies working with the `keyDownHandler` and `keyUpHandler` functions. You can register event listeners for the "keyDown" and "keyUp" events using the `on` method:
@@ -100,6 +139,10 @@ listener.on("keyUp", (data) => {
100
139
  | keyUpHandler | `callback: (keyCode: number) => void` | `void` |
101
140
  | getWindowData | `windowName: string` | `WindowData` |
102
141
  | captureWindow | `windowName: string, outputPath: string` | `void` |
142
+ | mouseMove | `posX: number, posY: number` | `boolean` |
143
+ | mouseClick | `button?: "left" \| "middle" \| "right"` | `boolean` |
144
+ | mouseDrag | `startX: number, startY: number, endX: number, endY: number, speed?: number` | `boolean` |
145
+ | typeString | `stringToType: string, delay?: number` | `boolean` |
103
146
  | KeyListener.on | `event: "keyDown"`,<br>`callback: (data: { keyCode: number; keyName: string }) => void` | `this` (EventListener) |
104
147
  | KeyListener.on | `event: "keyUp"`,<br>`callback: (data: { keyCode: number; keyName: string }) => void` | `this` (EventListener) |
105
148
 
@@ -113,6 +156,10 @@ import {
113
156
  keyUpHandler,
114
157
  getWindowData,
115
158
  captureWindow,
159
+ mouseMove,
160
+ mouseClick,
161
+ mouseDrag,
162
+ typeString,
116
163
  KeyListener,
117
164
  } from "node-native-win-utils";
118
165
 
@@ -144,6 +191,25 @@ captureWindow("My Window", "output.png");
144
191
 
145
192
  // Output: output.png with a screenshot of the window
146
193
 
194
+ // Move the mouse
195
+
196
+ mouseMove(100, 200);
197
+
198
+ // Perform mouse click
199
+
200
+ mouseClick(); // Left mouse click
201
+ mouseClick("right"); // Right mouse click
202
+
203
+ // Simulate mouse drag
204
+
205
+ mouseDrag(100, 200, 300, 400);
206
+ mouseDrag(100, 200, 300, 400, 100); // Drag with speed 100
207
+
208
+ // Simulate typing
209
+
210
+ typeString("Hello, world!");
211
+ typeString("Hello, world!", 100); // Type with a delay of 100ms between characters
212
+
147
213
  // Use KeyListener class
148
214
 
149
215
  const listener = new KeyListener();
package/dist/index.d.ts CHANGED
@@ -10,6 +10,10 @@ export type GetWindowData = (windowName: string) => WindowData;
10
10
  export type CaptureWindow = (windowName: string, outputPath: string) => void;
11
11
  export type KeyDownHandler = (callback: (keyCode: number) => void) => void;
12
12
  export type KeyUpHandler = (callback: (keyCode: number) => void) => void;
13
+ export type MouseMove = (posX: number, posY: number) => boolean;
14
+ export type MouseClick = (button?: "left" | "middle" | "right") => boolean;
15
+ export type TypeString = (stringToType: string, delay?: number) => boolean;
16
+ export type MouseDrag = (starX: number, startY: number, endX: Number, endY: number, speed?: number) => boolean;
13
17
  export interface KeyListener extends EventEmitter {
14
18
  on(event: "keyDown", callback: (data: {
15
19
  keyCode: number;
@@ -20,8 +24,8 @@ export interface KeyListener extends EventEmitter {
20
24
  keyName: string;
21
25
  }) => void): this;
22
26
  }
23
- declare const keyDownHandler: KeyDownHandler, keyUpHandler: KeyUpHandler, getWindowData: GetWindowData, captureWindow: CaptureWindow;
27
+ declare const keyDownHandler: KeyDownHandler, keyUpHandler: KeyUpHandler, getWindowData: GetWindowData, captureWindow: CaptureWindow, mouseMove: MouseMove, mouseClick: MouseClick, mouseDrag: MouseDrag, typeString: TypeString;
24
28
  export declare class KeyListener extends EventEmitter {
25
29
  constructor();
26
30
  }
27
- export { keyDownHandler, keyUpHandler, getWindowData, captureWindow };
31
+ export { keyDownHandler, keyUpHandler, getWindowData, captureWindow, mouseMove, mouseClick, mouseDrag, typeString, };
package/dist/index.js CHANGED
@@ -3,16 +3,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.captureWindow = exports.getWindowData = exports.keyUpHandler = exports.keyDownHandler = exports.KeyListener = void 0;
6
+ exports.typeString = exports.mouseDrag = exports.mouseClick = exports.mouseMove = exports.captureWindow = exports.getWindowData = exports.keyUpHandler = exports.keyDownHandler = exports.KeyListener = void 0;
7
7
  const EventEmitter = require("events");
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const bindings = require("node-gyp-build")(path_1.default.resolve(__dirname, ".."));
10
10
  const keyCodes_1 = __importDefault(require("./keyCodes"));
11
- const { keyDownHandler, keyUpHandler, getWindowData, captureWindow, } = bindings;
11
+ const { keyDownHandler, keyUpHandler, getWindowData, captureWindow, mouseMove, mouseClick, mouseDrag, typeString, } = bindings;
12
12
  exports.keyDownHandler = keyDownHandler;
13
13
  exports.keyUpHandler = keyUpHandler;
14
14
  exports.getWindowData = getWindowData;
15
15
  exports.captureWindow = captureWindow;
16
+ exports.mouseMove = mouseMove;
17
+ exports.mouseClick = mouseClick;
18
+ exports.mouseDrag = mouseDrag;
19
+ exports.typeString = typeString;
16
20
  class KeyListener extends EventEmitter {
17
21
  constructor() {
18
22
  super();
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "node-native-win-utils",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "author": "RynerNO",
5
5
  "license": "MIT",
6
+ "git": "https://github.com/RynerNO/node-native-win-utils.git",
6
7
  "description": "Native addon for Node.js providing utility operations on Windows systems",
7
8
  "keywords": [
8
9
  "node.js",
Binary file
@@ -0,0 +1,183 @@
1
+ #include <napi.h>
2
+ #include <iostream>
3
+ #include <Windows.h>
4
+ #include <dxgi.h>
5
+ #include <inspectable.h>
6
+ #include <dxgi1_2.h>
7
+ #include <d3d11.h>
8
+ #include <winrt/Windows.Foundation.h>
9
+ #include <winrt/Windows.System.h>
10
+ #include <winrt/Windows.Graphics.Capture.h>
11
+ #include <windows.graphics.capture.interop.h>
12
+ #include <windows.graphics.directx.direct3d11.interop.h>
13
+ #include <roerrorapi.h>
14
+ #include <shlobj_core.h>
15
+ #include <dwmapi.h>
16
+ #include <helpers.h>
17
+
18
+ #pragma comment(lib, "Dwmapi.lib")
19
+ #pragma comment(lib, "windowsapp.lib")
20
+
21
+ void CaptureWindow(const Napi::CallbackInfo &info)
22
+ {
23
+
24
+ Napi::Env env = info.Env();
25
+
26
+ if (info.Length() < 2 || !info[0].IsString() || !info[1].IsString())
27
+ {
28
+ Napi::TypeError::New(env, "Window name and file path must be provided as strings").ThrowAsJavaScriptException();
29
+ return;
30
+ }
31
+
32
+ std::string windowName = info[0].As<Napi::String>().Utf8Value();
33
+ std::string filePath = info[1].As<Napi::String>().Utf8Value();
34
+ std::wstring outputFilePath = ToWChar(filePath);
35
+ HWND hwndTarget = GetWindowByName(windowName.c_str());
36
+ // Init COM
37
+ winrt::init_apartment(winrt::apartment_type::multi_threaded);
38
+
39
+ // Create Direct 3D Device
40
+ winrt::com_ptr<ID3D11Device> d3dDevice;
41
+
42
+ winrt::check_hresult(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
43
+ nullptr, 0, D3D11_SDK_VERSION, d3dDevice.put(), nullptr, nullptr));
44
+
45
+ winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice device;
46
+ const auto dxgiDevice = d3dDevice.as<IDXGIDevice>();
47
+ {
48
+ winrt::com_ptr<::IInspectable> inspectable;
49
+ winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.get(), inspectable.put()));
50
+ device = inspectable.as<winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice>();
51
+ }
52
+
53
+ auto idxgiDevice2 = dxgiDevice.as<IDXGIDevice2>();
54
+ winrt::com_ptr<IDXGIAdapter> adapter;
55
+ winrt::check_hresult(idxgiDevice2->GetParent(winrt::guid_of<IDXGIAdapter>(), adapter.put_void()));
56
+ winrt::com_ptr<IDXGIFactory2> factory;
57
+ winrt::check_hresult(adapter->GetParent(winrt::guid_of<IDXGIFactory2>(), factory.put_void()));
58
+
59
+ ID3D11DeviceContext *d3dContext = nullptr;
60
+ d3dDevice->GetImmediateContext(&d3dContext);
61
+
62
+ RECT rect{};
63
+ DwmGetWindowAttribute(hwndTarget, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(RECT));
64
+ const auto size = winrt::Windows::Graphics::SizeInt32{rect.right - rect.left, rect.bottom - rect.top};
65
+
66
+ winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool m_framePool =
67
+ winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::Create(
68
+ device,
69
+ winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized,
70
+ 2,
71
+ size);
72
+
73
+ const auto activationFactory = winrt::get_activation_factory<
74
+ winrt::Windows::Graphics::Capture::GraphicsCaptureItem>();
75
+ auto interopFactory = activationFactory.as<IGraphicsCaptureItemInterop>();
76
+ winrt::Windows::Graphics::Capture::GraphicsCaptureItem captureItem = {nullptr};
77
+ interopFactory->CreateForWindow(hwndTarget, winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
78
+ reinterpret_cast<void **>(winrt::put_abi(captureItem)));
79
+
80
+ auto isFrameArrived = false;
81
+ winrt::com_ptr<ID3D11Texture2D> texture;
82
+ const auto session = m_framePool.CreateCaptureSession(captureItem);
83
+ m_framePool.FrameArrived([&](auto &framePool, auto &)
84
+ {
85
+ if (isFrameArrived) return;
86
+ auto frame = framePool.TryGetNextFrame();
87
+
88
+ struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1"))
89
+ IDirect3DDxgiInterfaceAccess : ::IUnknown
90
+ {
91
+ virtual HRESULT __stdcall GetInterface(GUID const& id, void** object) = 0;
92
+ };
93
+
94
+ auto access = frame.Surface().as<IDirect3DDxgiInterfaceAccess>();
95
+ access->GetInterface(winrt::guid_of<ID3D11Texture2D>(), texture.put_void());
96
+ isFrameArrived = true;
97
+ return; });
98
+
99
+ session.IsCursorCaptureEnabled(false);
100
+ session.StartCapture();
101
+
102
+ // Message pump
103
+ MSG msg;
104
+ clock_t timer = clock();
105
+ while (!isFrameArrived)
106
+ {
107
+ if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0)
108
+ DispatchMessage(&msg);
109
+
110
+ if (clock() - timer > 20000)
111
+ {
112
+ // TODO: try to make here a better error handling
113
+ return;
114
+ }
115
+ }
116
+
117
+ session.Close();
118
+
119
+ D3D11_TEXTURE2D_DESC capturedTextureDesc;
120
+ texture->GetDesc(&capturedTextureDesc);
121
+
122
+ capturedTextureDesc.Usage = D3D11_USAGE_STAGING;
123
+ capturedTextureDesc.BindFlags = 0;
124
+ capturedTextureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
125
+ capturedTextureDesc.MiscFlags = 0;
126
+
127
+ winrt::com_ptr<ID3D11Texture2D> userTexture = nullptr;
128
+ winrt::check_hresult(d3dDevice->CreateTexture2D(&capturedTextureDesc, NULL, userTexture.put()));
129
+
130
+ d3dContext->CopyResource(userTexture.get(), texture.get());
131
+
132
+ D3D11_MAPPED_SUBRESOURCE resource;
133
+ winrt::check_hresult(d3dContext->Map(userTexture.get(), NULL, D3D11_MAP_READ, 0, &resource));
134
+
135
+ BITMAPINFO lBmpInfo;
136
+
137
+ // BMP 32 bpp
138
+ ZeroMemory(&lBmpInfo, sizeof(BITMAPINFO));
139
+ lBmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
140
+ lBmpInfo.bmiHeader.biBitCount = 32;
141
+ lBmpInfo.bmiHeader.biCompression = BI_RGB;
142
+ lBmpInfo.bmiHeader.biWidth = capturedTextureDesc.Width;
143
+ lBmpInfo.bmiHeader.biHeight = capturedTextureDesc.Height;
144
+ lBmpInfo.bmiHeader.biPlanes = 1;
145
+ lBmpInfo.bmiHeader.biSizeImage = capturedTextureDesc.Width * capturedTextureDesc.Height * 4;
146
+
147
+ std::unique_ptr<BYTE> pBuf(new BYTE[lBmpInfo.bmiHeader.biSizeImage]);
148
+ UINT lBmpRowPitch = capturedTextureDesc.Width * 4;
149
+ auto sptr = static_cast<BYTE *>(resource.pData);
150
+ auto dptr = pBuf.get() + lBmpInfo.bmiHeader.biSizeImage - lBmpRowPitch;
151
+
152
+ UINT lRowPitch = std::min<UINT>(lBmpRowPitch, resource.RowPitch);
153
+
154
+ for (size_t h = 0; h < capturedTextureDesc.Height; ++h)
155
+ {
156
+ memcpy_s(dptr, lBmpRowPitch, sptr, lRowPitch);
157
+ sptr += resource.RowPitch;
158
+ dptr -= lBmpRowPitch;
159
+ }
160
+
161
+ // Save bitmap buffer into the provided output file path
162
+ FILE *lfile = nullptr;
163
+
164
+ if (auto lerr = _wfopen_s(&lfile, outputFilePath.c_str(), L"wb"); lerr != 0)
165
+ return;
166
+
167
+ if (lfile != nullptr)
168
+ {
169
+ BITMAPFILEHEADER bmpFileHeader;
170
+
171
+ bmpFileHeader.bfReserved1 = 0;
172
+ bmpFileHeader.bfReserved2 = 0;
173
+ bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + lBmpInfo.bmiHeader.biSizeImage;
174
+ bmpFileHeader.bfType = 'MB';
175
+ bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
176
+
177
+ fwrite(&bmpFileHeader, sizeof(BITMAPFILEHEADER), 1, lfile);
178
+ fwrite(&lBmpInfo.bmiHeader, sizeof(BITMAPINFOHEADER), 1, lfile);
179
+ fwrite(pBuf.get(), lBmpInfo.bmiHeader.biSizeImage, 1, lfile);
180
+
181
+ fclose(lfile);
182
+ }
183
+ }
@@ -0,0 +1,46 @@
1
+ #include <napi.h>
2
+ #include <Windows.h>
3
+ #include <iostream>
4
+ #include <dwmapi.h>
5
+ #include <helpers.h>
6
+
7
+ Napi::Value GetWindowData(const Napi::CallbackInfo &info)
8
+ {
9
+ Napi::Env env = info.Env();
10
+
11
+ if (info.Length() < 1 || !info[0].IsString())
12
+ {
13
+ Napi::TypeError::New(env, "Window name must be provided as a string").ThrowAsJavaScriptException();
14
+ return env.Null();
15
+ }
16
+
17
+ std::string windowName = info[0].As<Napi::String>().Utf8Value();
18
+
19
+ HWND windowHandle = GetWindowByName(windowName.c_str());
20
+
21
+ if (windowHandle == NULL)
22
+ {
23
+ Napi::Error::New(env, "Window not found").ThrowAsJavaScriptException();
24
+ return env.Null();
25
+ }
26
+
27
+ RECT windowRect;
28
+ DwmGetWindowAttribute(windowHandle, DWMWA_EXTENDED_FRAME_BOUNDS, &windowRect, sizeof(windowRect));
29
+ HDC hdc = GetDC(windowHandle);
30
+ int dpiX = GetDeviceCaps(hdc, LOGPIXELSX);
31
+ int dpiY = GetDeviceCaps(hdc, LOGPIXELSY);
32
+ ReleaseDC(windowHandle, hdc);
33
+
34
+ int width = windowRect.right - windowRect.left;
35
+ int height = windowRect.bottom - windowRect.top;
36
+ int x = windowRect.left;
37
+ int y = windowRect.top;
38
+
39
+ Napi::Object result = Napi::Object::New(env);
40
+ result.Set("width", Napi::Number::New(env, width));
41
+ result.Set("height", Napi::Number::New(env, height));
42
+ result.Set("x", Napi::Number::New(env, x));
43
+ result.Set("y", Napi::Number::New(env, y));
44
+
45
+ return result;
46
+ }
@@ -0,0 +1,15 @@
1
+
2
+ #include "helpers.h"
3
+
4
+ std::wstring ToWChar(const std::string &str)
5
+ {
6
+ int bufferSize = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, NULL, 0);
7
+ std::wstring wstr(bufferSize, 0);
8
+ MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &wstr[0], bufferSize);
9
+ return wstr;
10
+ }
11
+
12
+ HWND GetWindowByName(const char *windowName)
13
+ {
14
+ return FindWindowA(NULL, windowName);
15
+ }
@@ -0,0 +1,8 @@
1
+ #include <Windows.h>
2
+ #include <iostream>
3
+ #include <codecvt>
4
+ #include <Windows.h>
5
+ #include <napi.h>
6
+
7
+ std::wstring ToWChar(const std::string &str);
8
+ HWND GetWindowByName(const char *windowName);
@@ -0,0 +1,205 @@
1
+ #include <napi.h>
2
+ #include <Windows.h>
3
+ #include <thread>
4
+ #include <iostream>
5
+
6
+ // Global variable to store the JavaScript callback function
7
+ Napi::ThreadSafeFunction globalJsCallbackKeyDown;
8
+ Napi::ThreadSafeFunction globalJsCallbackKeyUp;
9
+
10
+ // Global variable to store the previous key state
11
+ bool isKeyPressed = false;
12
+ bool handleKeyUp = false;
13
+ bool handleKeyDown = false;
14
+ bool monitorThreadRunning = false;
15
+ int previousKeyState;
16
+ LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
17
+ {
18
+ if (nCode >= 0)
19
+ {
20
+ if (wParam == WM_KEYDOWN)
21
+ {
22
+ KBDLLHOOKSTRUCT *kbdStruct = (KBDLLHOOKSTRUCT *)lParam;
23
+ int keyCode = kbdStruct->vkCode;
24
+
25
+ // Handle the keyCode here or call your JavaScript callback
26
+ // std::cout << "Key Code: " << keyCode << std::endl;
27
+
28
+ if (keyCode != previousKeyState || !isKeyPressed)
29
+ {
30
+ isKeyPressed = true;
31
+ previousKeyState = keyCode;
32
+ if (handleKeyDown)
33
+ {
34
+ int *keyCodeCopy = new int(keyCode); // Create a copy of the keyCode value
35
+
36
+ napi_status status = globalJsCallbackKeyDown.BlockingCall(
37
+ keyCodeCopy,
38
+ [](Napi::Env env, Napi::Function jsCallback, int *keyCodePtr)
39
+ {
40
+ Napi::HandleScope scope(env);
41
+ int keyCode = *keyCodePtr;
42
+ jsCallback.Call({Napi::Number::New(env, keyCode)});
43
+ delete keyCodePtr; // Clean up the dynamically allocated keyCode copy
44
+ });
45
+
46
+ if (status != napi_ok)
47
+ {
48
+ // Handle the error
49
+ }
50
+ }
51
+ }
52
+ }
53
+ else if (wParam == WM_KEYUP)
54
+ {
55
+ KBDLLHOOKSTRUCT *kbdStruct = (KBDLLHOOKSTRUCT *)lParam;
56
+ int keyCode = kbdStruct->vkCode;
57
+
58
+ if (keyCode == previousKeyState)
59
+ {
60
+ isKeyPressed = false;
61
+ if (handleKeyUp)
62
+ {
63
+ int *keyCodeCopy = new int(keyCode); // Create a copy of the keyCode value
64
+ napi_status status = globalJsCallbackKeyUp.BlockingCall(
65
+ keyCodeCopy,
66
+ [](Napi::Env env, Napi::Function jsCallback, int *keyCodePtr)
67
+ {
68
+ Napi::HandleScope scope(env);
69
+ int keyCode = *keyCodePtr;
70
+ jsCallback.Call({Napi::Number::New(env, keyCode)});
71
+ delete keyCodePtr; // Clean up the dynamically allocated keyCode copy
72
+ });
73
+
74
+ if (status != napi_ok)
75
+ {
76
+ // Handle the error
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ return CallNextHookEx(NULL, nCode, wParam, lParam);
84
+ }
85
+
86
+ // Function to monitor keyboard events
87
+ void MonitorKeyboardEvents()
88
+ {
89
+ HHOOK keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProc, NULL, 0);
90
+ if (keyboardHook == NULL)
91
+ {
92
+ // Error setting hook
93
+ return;
94
+ }
95
+
96
+ // Main loop to process keyboard events
97
+ MSG message;
98
+ while (GetMessage(&message, NULL, 0, 0) > 0)
99
+ {
100
+ TranslateMessage(&message);
101
+ DispatchMessage(&message);
102
+ }
103
+
104
+ UnhookWindowsHookEx(keyboardHook);
105
+ }
106
+
107
+ // Function called from JavaScript to set the callback function
108
+ Napi::Value SetKeyDownCallback(const Napi::CallbackInfo &info)
109
+ {
110
+ Napi::Env env = info.Env();
111
+
112
+ // Get the callback function from the arguments
113
+ Napi::Function jsCallback = info[0].As<Napi::Function>();
114
+
115
+ // Create a ThreadSafeFunction with the JavaScript callback
116
+ globalJsCallbackKeyDown = Napi::ThreadSafeFunction::New(
117
+ env,
118
+ jsCallback,
119
+ "KeyDownCallback",
120
+ 0,
121
+ 1,
122
+ [](Napi::Env)
123
+ {
124
+ // Finalizer callback (optional)
125
+ });
126
+ handleKeyDown = true;
127
+ if (!monitorThreadRunning)
128
+ {
129
+ std::thread monitorThread(MonitorKeyboardEvents);
130
+ monitorThread.detach();
131
+
132
+ monitorThreadRunning = true;
133
+ }
134
+
135
+ return env.Undefined();
136
+ }
137
+
138
+ // Function called from JavaScript to set the callback function
139
+ Napi::Value SetKeyUpCallback(const Napi::CallbackInfo &info)
140
+ {
141
+ Napi::Env env = info.Env();
142
+
143
+ // Get the callback function from the arguments
144
+ Napi::Function jsCallback = info[0].As<Napi::Function>();
145
+
146
+ // Create a ThreadSafeFunction with the JavaScript callback
147
+ globalJsCallbackKeyUp = Napi::ThreadSafeFunction::New(
148
+ env,
149
+ jsCallback,
150
+ "KeyUpCallback",
151
+ 0,
152
+ 1,
153
+ [](Napi::Env)
154
+ {
155
+ // Finalizer callback (optional)
156
+ });
157
+ handleKeyUp = true;
158
+ if (!monitorThreadRunning)
159
+ {
160
+ std::thread monitorThread(MonitorKeyboardEvents);
161
+ monitorThread.detach();
162
+ monitorThreadRunning = true;
163
+ }
164
+ return env.Undefined();
165
+ }
166
+
167
+ Napi::Value TypeString(const Napi::CallbackInfo &info)
168
+ {
169
+ Napi::Env env = info.Env();
170
+
171
+ if (info.Length() < 1 || !info[0].IsString())
172
+ {
173
+ Napi::TypeError::New(env, "You should provide a string to type").ThrowAsJavaScriptException();
174
+ return env.Null();
175
+ }
176
+ int delay = 15;
177
+ std::string text = info[0].As<Napi::String>();
178
+ if (info.Length() > 1 && info[1].IsNumber())
179
+ {
180
+ delay = info[1].As<Napi::Number>();
181
+ }
182
+ // Convert the string to wide string
183
+ std::wstring wideText(text.begin(), text.end());
184
+
185
+ // Simulate typing by sending keyboard events
186
+ for (const wchar_t &character : wideText)
187
+ {
188
+ INPUT keyboardInput = {0};
189
+ keyboardInput.type = INPUT_KEYBOARD;
190
+ keyboardInput.ki.wVk = 0;
191
+ keyboardInput.ki.wScan = character;
192
+ keyboardInput.ki.dwFlags = KEYEVENTF_UNICODE;
193
+ keyboardInput.ki.time = 0; // System will provide the timestamp
194
+
195
+ SendInput(1, &keyboardInput, sizeof(keyboardInput));
196
+
197
+ keyboardInput.ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP;
198
+ SendInput(1, &keyboardInput, sizeof(keyboardInput));
199
+
200
+ // Sleep for 15 milliseconds between each character input
201
+ Sleep(delay);
202
+ }
203
+
204
+ return Napi::Boolean::New(env, true);
205
+ }
@@ -0,0 +1,21 @@
1
+ #include <napi.h>
2
+ #include <helpers.cpp>
3
+ #include "captureWindow.cpp"
4
+ #include <getWindowData.cpp>
5
+ #include <keyboard.cpp>
6
+ #include <mouse.cpp>
7
+
8
+ Napi::Object Init(Napi::Env env, Napi::Object exports)
9
+ {
10
+ exports.Set("getWindowData", Napi::Function::New(env, GetWindowData));
11
+ exports.Set("captureWindow", Napi::Function::New(env, CaptureWindow));
12
+ exports.Set("keyDownHandler", Napi::Function::New(env, SetKeyDownCallback));
13
+ exports.Set("keyUpHandler", Napi::Function::New(env, SetKeyUpCallback));
14
+ exports.Set("mouseMove", Napi::Function::New(env, MoveMouse));
15
+ exports.Set("mouseClick", Napi::Function::New(env, ClickMouse));
16
+ exports.Set("mouseDrag", Napi::Function::New(env, DragMouse));
17
+ exports.Set("typeString", Napi::Function::New(env, TypeString));
18
+ return exports;
19
+ }
20
+
21
+ NODE_API_MODULE(addon, Init)
@@ -0,0 +1,175 @@
1
+ // patrially used code from https://github.com/octalmage/robotjs witch is under MIT License Copyright (c) 2014 Jason Stallings
2
+ #include <napi.h>
3
+ #include <windows.h>
4
+ /**
5
+ * Move the mouse to a specific point.
6
+ * @param point The coordinates to move the mouse to (x, y).
7
+ */
8
+
9
+ Napi::Value MoveMouse(const Napi::CallbackInfo &info)
10
+ {
11
+ Napi::Env env = info.Env();
12
+
13
+ if (info.Length() < 2 || !info[0].IsNumber() || !info[1].IsNumber())
14
+ {
15
+ Napi::TypeError::New(env, "You should provide x and y position of type number").ThrowAsJavaScriptException();
16
+ return env.Null();
17
+ }
18
+
19
+ int posX = info[0].As<Napi::Number>();
20
+ int posY = info[1].As<Napi::Number>();
21
+
22
+ // Get the screen metrics
23
+ int screenWidth = GetSystemMetrics(SM_CXSCREEN);
24
+ int screenHeight = GetSystemMetrics(SM_CYSCREEN);
25
+
26
+ // Convert coordinates to absolute values
27
+ int absoluteX = static_cast<int>((65536 * posX) / screenWidth);
28
+ int absoluteY = static_cast<int>((65536 * posY) / screenHeight);
29
+
30
+ // Move the mouse
31
+ INPUT mouseInput = {0};
32
+ mouseInput.type = INPUT_MOUSE;
33
+ mouseInput.mi.dx = absoluteX;
34
+ mouseInput.mi.dy = absoluteY;
35
+ mouseInput.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE | MOUSEEVENTF_VIRTUALDESK;
36
+ mouseInput.mi.time = 0; // System will provide the timestamp
37
+
38
+ SendInput(1, &mouseInput, sizeof(mouseInput));
39
+
40
+ return Napi::Boolean::New(env, true);
41
+ }
42
+
43
+ Napi::Value ClickMouse(const Napi::CallbackInfo &info)
44
+ {
45
+ Napi::Env env = info.Env();
46
+ std::string button;
47
+
48
+ if (info.Length() < 1 || !info[0].IsString())
49
+ button = "left";
50
+ else
51
+ button = info[0].As<Napi::String>();
52
+
53
+ WORD mouseEvent = 0;
54
+
55
+ if (button == "left")
56
+ {
57
+ mouseEvent = MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP;
58
+ }
59
+ else if (button == "right")
60
+ {
61
+ mouseEvent = MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_RIGHTUP;
62
+ }
63
+ else if (button == "middle")
64
+ {
65
+ mouseEvent = MOUSEEVENTF_MIDDLEDOWN | MOUSEEVENTF_MIDDLEUP;
66
+ }
67
+ else
68
+ {
69
+ Napi::TypeError::New(env, "Invalid button name").ThrowAsJavaScriptException();
70
+ return env.Null();
71
+ }
72
+
73
+ // Perform the mouse click
74
+ INPUT mouseInput = {0};
75
+ mouseInput.type = INPUT_MOUSE;
76
+ mouseInput.mi.dwFlags = mouseEvent;
77
+ mouseInput.mi.time = 0; // System will provide the timestamp
78
+
79
+ SendInput(1, &mouseInput, sizeof(mouseInput));
80
+
81
+ return Napi::Boolean::New(env, true);
82
+ }
83
+
84
+ Napi::Value DragMouse(const Napi::CallbackInfo &info)
85
+ {
86
+ Napi::Env env = info.Env();
87
+
88
+ if (info.Length() < 4 || !info[0].IsNumber() || !info[1].IsNumber() || !info[2].IsNumber() || !info[3].IsNumber())
89
+ {
90
+ Napi::TypeError::New(env, "You should provide startX, startY, endX, endY").ThrowAsJavaScriptException();
91
+ return env.Null();
92
+ }
93
+
94
+ int startX = info[0].As<Napi::Number>();
95
+ int startY = info[1].As<Napi::Number>();
96
+ int endX = info[2].As<Napi::Number>();
97
+ int endY = info[3].As<Napi::Number>();
98
+ int speed = 100;
99
+ if (info.Length() > 4 && info[4].IsNumber())
100
+ {
101
+ speed = info[4].As<Napi::Number>();
102
+ }
103
+
104
+ // Get the screen metrics
105
+ int screenWidth = GetSystemMetrics(SM_CXSCREEN);
106
+ int screenHeight = GetSystemMetrics(SM_CYSCREEN);
107
+
108
+ // Convert coordinates to absolute values
109
+ int absoluteStartX = static_cast<int>((65536 * startX) / screenWidth);
110
+ int absoluteStartY = static_cast<int>((65536 * startY) / screenHeight);
111
+ int absoluteEndX = static_cast<int>((65536 * endX) / screenWidth);
112
+ int absoluteEndY = static_cast<int>((65536 * endY) / screenHeight);
113
+
114
+ // Calculate the distance and duration based on speed
115
+ double distanceX = absoluteEndX - absoluteStartX;
116
+ double distanceY = absoluteEndY - absoluteStartY;
117
+ double distance = sqrt(distanceX * distanceX + distanceY * distanceY);
118
+ double duration = distance / speed;
119
+
120
+ // Move the mouse to the starting position
121
+ INPUT startMouseInput = {0};
122
+ startMouseInput.type = INPUT_MOUSE;
123
+ startMouseInput.mi.dx = absoluteStartX;
124
+ startMouseInput.mi.dy = absoluteStartY;
125
+ startMouseInput.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE | MOUSEEVENTF_VIRTUALDESK;
126
+ startMouseInput.mi.time = 0; // System will provide the timestamp
127
+
128
+ SendInput(1, &startMouseInput, sizeof(startMouseInput));
129
+
130
+ // Perform mouse button down event
131
+ INPUT mouseDownInput = {0};
132
+ mouseDownInput.type = INPUT_MOUSE;
133
+ mouseDownInput.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
134
+ mouseDownInput.mi.time = 0; // System will provide the timestamp
135
+
136
+ SendInput(1, &mouseDownInput, sizeof(mouseDownInput));
137
+
138
+ // Calculate the number of steps based on the duration and desired speed
139
+ const int steps = 100; // Adjust the number of steps for smoother movement
140
+
141
+ // Calculate the incremental values for each step
142
+ double stepX = distanceX / steps;
143
+ double stepY = distanceY / steps;
144
+
145
+ // Move the mouse in increments to simulate dragging with speed control
146
+ for (int i = 0; i < steps; ++i)
147
+ {
148
+ // Calculate the position for the current step
149
+ int currentX = static_cast<int>(absoluteStartX + (stepX * i));
150
+ int currentY = static_cast<int>(absoluteStartY + (stepY * i));
151
+
152
+ // Move the mouse to the current position
153
+ INPUT mouseMoveInput = {0};
154
+ mouseMoveInput.type = INPUT_MOUSE;
155
+ mouseMoveInput.mi.dx = currentX;
156
+ mouseMoveInput.mi.dy = currentY;
157
+ mouseMoveInput.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE | MOUSEEVENTF_VIRTUALDESK;
158
+ mouseMoveInput.mi.time = 0; // System will provide the timestamp
159
+
160
+ SendInput(1, &mouseMoveInput, sizeof(mouseMoveInput));
161
+
162
+ // Sleep for a short duration to control the speed
163
+ Sleep(static_cast<DWORD>(duration / steps));
164
+ }
165
+
166
+ // Perform mouse button up event
167
+ INPUT mouseUpInput = {0};
168
+ mouseUpInput.type = INPUT_MOUSE;
169
+ mouseUpInput.mi.dwFlags = MOUSEEVENTF_LEFTUP;
170
+ mouseUpInput.mi.time = 0; // System will provide the timestamp
171
+
172
+ SendInput(1, &mouseUpInput, sizeof(mouseUpInput));
173
+
174
+ return Napi::Boolean::New(env, true);
175
+ }