Initial commit
This commit is contained in:
53
remote/src/services/input/InputService.js
Normal file
53
remote/src/services/input/InputService.js
Normal file
@@ -0,0 +1,53 @@
|
||||
class InputService {
|
||||
constructor() {
|
||||
if (this.constructor === InputService) {
|
||||
throw new Error('InputService is an abstract class and cannot be instantiated directly');
|
||||
}
|
||||
}
|
||||
|
||||
async mouseMove(x, y) {
|
||||
throw new Error('Method mouseMove() must be implemented');
|
||||
}
|
||||
|
||||
async mouseDown(button = 'left') {
|
||||
throw new Error('Method mouseDown() must be implemented');
|
||||
}
|
||||
|
||||
async mouseUp(button = 'left') {
|
||||
throw new Error('Method mouseUp() must be implemented');
|
||||
}
|
||||
|
||||
async mouseClick(button = 'left') {
|
||||
throw new Error('Method mouseClick() must be implemented');
|
||||
}
|
||||
|
||||
async mouseWheel(delta) {
|
||||
throw new Error('Method mouseWheel() must be implemented');
|
||||
}
|
||||
|
||||
async keyDown(key) {
|
||||
throw new Error('Method keyDown() must be implemented');
|
||||
}
|
||||
|
||||
async keyUp(key) {
|
||||
throw new Error('Method keyUp() must be implemented');
|
||||
}
|
||||
|
||||
async keyPress(key) {
|
||||
throw new Error('Method keyPress() must be implemented');
|
||||
}
|
||||
|
||||
async start() {
|
||||
throw new Error('Method start() must be implemented');
|
||||
}
|
||||
|
||||
async stop() {
|
||||
throw new Error('Method stop() must be implemented');
|
||||
}
|
||||
|
||||
isReady() {
|
||||
throw new Error('Method isReady() must be implemented');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = InputService;
|
||||
275
remote/src/services/input/PowerShellInput.js
Normal file
275
remote/src/services/input/PowerShellInput.js
Normal file
@@ -0,0 +1,275 @@
|
||||
const { spawn } = require('child_process');
|
||||
const InputService = require('./InputService');
|
||||
const logger = require('../../utils/logger');
|
||||
const config = require('../../config');
|
||||
|
||||
const VK_CODES = {
|
||||
'enter': 13, 'backspace': 8, 'tab': 9, 'escape': 27,
|
||||
'delete': 46, 'home': 36, 'end': 35, 'pageup': 33,
|
||||
'pagedown': 34, 'up': 38, 'down': 40, 'left': 37, 'right': 39,
|
||||
'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115,
|
||||
'f5': 116, 'f6': 117, 'f7': 118, 'f8': 119,
|
||||
'f9': 120, 'f10': 121, 'f11': 122, 'f12': 123,
|
||||
'ctrl': 17, 'alt': 18, 'shift': 16, 'win': 91,
|
||||
'space': 32,
|
||||
',': 188, '.': 190, '/': 191, ';': 186, "'": 222,
|
||||
'[': 219, ']': 221, '\\': 220, '-': 189, '=': 187,
|
||||
'`': 192
|
||||
};
|
||||
|
||||
const POWERSHELL_SCRIPT = `
|
||||
$env:PSModulePath += ';.'
|
||||
Add-Type -TypeDefinition @'
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
public class Input {
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool SetCursorPos(int X, int Y);
|
||||
[DllImport("user32.dll")]
|
||||
public static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);
|
||||
[DllImport("user32.dll")]
|
||||
public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);
|
||||
[DllImport("user32.dll")]
|
||||
public static extern short GetAsyncKeyState(int vKey);
|
||||
}
|
||||
'@ -Language CSharp -ErrorAction SilentlyContinue
|
||||
|
||||
while ($true) {
|
||||
$line = [Console]::In.ReadLine()
|
||||
if ($line -eq $null) { break }
|
||||
if ($line -eq '') { continue }
|
||||
|
||||
try {
|
||||
$parts = $line -split ' '
|
||||
$cmd = $parts[0]
|
||||
|
||||
switch ($cmd) {
|
||||
'move' {
|
||||
$x = [int]$parts[1]
|
||||
$y = [int]$parts[2]
|
||||
[Input]::SetCursorPos($x, $y)
|
||||
}
|
||||
'down' {
|
||||
$btn = $parts[1]
|
||||
$flag = if ($btn -eq 'right') { 8 } else { 2 }
|
||||
[Input]::mouse_event($flag, 0, 0, 0, 0)
|
||||
}
|
||||
'up' {
|
||||
$btn = $parts[1]
|
||||
$flag = if ($btn -eq 'right') { 16 } else { 4 }
|
||||
[Input]::mouse_event($flag, 0, 0, 0, 0)
|
||||
}
|
||||
'wheel' {
|
||||
$delta = [int]$parts[1]
|
||||
[Input]::mouse_event(2048, 0, 0, $delta, 0)
|
||||
}
|
||||
'kdown' {
|
||||
$vk = [int]$parts[1]
|
||||
[Input]::keybd_event([byte]$vk, 0, 0, 0)
|
||||
}
|
||||
'kup' {
|
||||
$vk = [int]$parts[1]
|
||||
[Input]::keybd_event([byte]$vk, 0, 2, 0)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-Error $_.Exception.Message
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
class PowerShellInput extends InputService {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
|
||||
this.inputConfig = config.getSection('input') || {
|
||||
mouseEnabled: true,
|
||||
keyboardEnabled: true,
|
||||
sensitivity: 1.0
|
||||
};
|
||||
|
||||
this.mouseEnabled = options.mouseEnabled !== undefined
|
||||
? options.mouseEnabled
|
||||
: this.inputConfig.mouseEnabled;
|
||||
this.keyboardEnabled = options.keyboardEnabled !== undefined
|
||||
? options.keyboardEnabled
|
||||
: this.inputConfig.keyboardEnabled;
|
||||
|
||||
this.psProcess = null;
|
||||
this._isReady = false;
|
||||
this._isStopping = false;
|
||||
this._restartTimer = null;
|
||||
}
|
||||
|
||||
_startProcess() {
|
||||
if (this.psProcess) {
|
||||
this._stopProcess();
|
||||
}
|
||||
|
||||
this.psProcess = spawn('powershell', [
|
||||
'-NoProfile',
|
||||
'-ExecutionPolicy', 'Bypass',
|
||||
'-Command', POWERSHELL_SCRIPT
|
||||
], {
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
this.psProcess.stdout.on('data', (data) => {
|
||||
logger.debug('PowerShell stdout', { output: data.toString().trim() });
|
||||
});
|
||||
|
||||
this.psProcess.stderr.on('data', (data) => {
|
||||
logger.error('PowerShell stderr', { error: data.toString().trim() });
|
||||
});
|
||||
|
||||
this.psProcess.on('close', (code) => {
|
||||
logger.warn('PowerShell process closed', { code });
|
||||
this._isReady = false;
|
||||
this.psProcess = null;
|
||||
this._scheduleRestart();
|
||||
});
|
||||
|
||||
this.psProcess.on('error', (error) => {
|
||||
logger.error('PowerShell process error', { error: error.message });
|
||||
this._isReady = false;
|
||||
});
|
||||
|
||||
this._isReady = true;
|
||||
logger.info('PowerShellInput process started');
|
||||
}
|
||||
|
||||
_stopProcess() {
|
||||
if (this.psProcess) {
|
||||
this.psProcess.kill();
|
||||
this.psProcess = null;
|
||||
this._isReady = false;
|
||||
}
|
||||
|
||||
if (this._restartTimer) {
|
||||
clearTimeout(this._restartTimer);
|
||||
this._restartTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
_scheduleRestart() {
|
||||
if (this._isStopping) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._restartTimer) {
|
||||
clearTimeout(this._restartTimer);
|
||||
}
|
||||
|
||||
this._restartTimer = setTimeout(() => {
|
||||
if (!this._isStopping && (this.mouseEnabled || this.keyboardEnabled)) {
|
||||
logger.info('Restarting PowerShell process after crash');
|
||||
this._startProcess();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
async _sendCommand(cmd) {
|
||||
if (!this._isReady || !this.psProcess) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
this.psProcess.stdin.write(cmd + '\n');
|
||||
resolve();
|
||||
} catch (error) {
|
||||
logger.error('Failed to send command', { cmd, error: error.message });
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_getVkCode(key) {
|
||||
const lowerKey = key.toLowerCase();
|
||||
if (VK_CODES[lowerKey]) {
|
||||
return VK_CODES[lowerKey];
|
||||
}
|
||||
|
||||
if (key.length === 1) {
|
||||
if (VK_CODES[key]) {
|
||||
return VK_CODES[key];
|
||||
}
|
||||
return key.toUpperCase().charCodeAt(0);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async mouseMove(x, y) {
|
||||
if (!this.mouseEnabled) return;
|
||||
await this._sendCommand(`move ${Math.floor(x)} ${Math.floor(y)}`);
|
||||
}
|
||||
|
||||
async mouseDown(button = 'left') {
|
||||
if (!this.mouseEnabled) return;
|
||||
await this._sendCommand(`down ${button}`);
|
||||
}
|
||||
|
||||
async mouseUp(button = 'left') {
|
||||
if (!this.mouseEnabled) return;
|
||||
await this._sendCommand(`up ${button}`);
|
||||
}
|
||||
|
||||
async mouseClick(button = 'left') {
|
||||
if (!this.mouseEnabled) return;
|
||||
await this.mouseDown(button);
|
||||
await new Promise(r => setTimeout(r, 10));
|
||||
await this.mouseUp(button);
|
||||
}
|
||||
|
||||
async mouseWheel(delta) {
|
||||
if (!this.mouseEnabled) return;
|
||||
await this._sendCommand(`wheel ${Math.floor(delta)}`);
|
||||
}
|
||||
|
||||
async keyDown(key) {
|
||||
if (!this.keyboardEnabled) return;
|
||||
const vk = this._getVkCode(key);
|
||||
if (vk) {
|
||||
await this._sendCommand(`kdown ${vk}`);
|
||||
}
|
||||
}
|
||||
|
||||
async keyUp(key) {
|
||||
if (!this.keyboardEnabled) return;
|
||||
const vk = this._getVkCode(key);
|
||||
if (vk) {
|
||||
await this._sendCommand(`kup ${vk}`);
|
||||
}
|
||||
}
|
||||
|
||||
async keyPress(key) {
|
||||
if (!this.keyboardEnabled) return;
|
||||
await this.keyDown(key);
|
||||
await new Promise(r => setTimeout(r, 10));
|
||||
await this.keyUp(key);
|
||||
}
|
||||
|
||||
async start() {
|
||||
if (this.mouseEnabled || this.keyboardEnabled) {
|
||||
this._isStopping = false;
|
||||
this._startProcess();
|
||||
}
|
||||
logger.info('PowerShellInput started', {
|
||||
mouseEnabled: this.mouseEnabled,
|
||||
keyboardEnabled: this.keyboardEnabled
|
||||
});
|
||||
}
|
||||
|
||||
async stop() {
|
||||
this._isStopping = true;
|
||||
this._stopProcess();
|
||||
logger.info('PowerShellInput stopped');
|
||||
}
|
||||
|
||||
isReady() {
|
||||
return this._isReady;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PowerShellInput;
|
||||
7
remote/src/services/input/index.js
Normal file
7
remote/src/services/input/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const InputService = require('./InputService');
|
||||
const PowerShellInput = require('./PowerShellInput');
|
||||
|
||||
module.exports = {
|
||||
InputService,
|
||||
PowerShellInput
|
||||
};
|
||||
Reference in New Issue
Block a user