@@ -1134,9 +1134,12 @@ Json::Value BuildRemoteCloseInputSchema();
Json : : Value BuildRemoteCloseOutputSchema ( ) ;
Json : : Value BuildRemoteKeyboardInputSchema ( ) ;
Json : : Value BuildRemoteKeyboardOutputSchema ( ) ;
Json : : Value BuildRemoteMouseInputSchema ( ) ;
Json : : Value BuildRemoteMouseOutputSchema ( ) ;
std : : string BuildRemoteOpen ( const Json : : Value & id , const Json : : Value & args , CMy2015RemoteDlg * parent ) ;
std : : string BuildRemoteClose ( const Json : : Value & id , const Json : : Value & args , CMy2015RemoteDlg * parent ) ;
std : : string BuildRemoteKeyboard ( const Json : : Value & id , const Json : : Value & args , CMy2015RemoteDlg * parent ) ;
std : : string BuildRemoteMouse ( const Json : : Value & id , const Json : : Value & args , CMy2015RemoteDlg * parent ) ;
// tools/list
std : : string BuildToolsListResult ( const Json : : Value & id ) {
@@ -1368,6 +1371,14 @@ std::string BuildToolsListResult(const Json::Value& id) {
tool [ " outputSchema " ] = BuildRemoteKeyboardOutputSchema ( ) ;
tools . append ( tool ) ;
}
{
Json : : Value tool ( Json : : objectValue ) ;
tool [ " name " ] = " remote_mouse " ;
tool [ " description " ] = u8 " 向已建立的远程控制会话注入鼠标事件。action 可选 move / down / up / click / right_click / middle_click / drag / scroll; 坐标 x/y( 及 drag 的 x2/y2) 为归一化 0..1 浮点。button 可选 left/middle/right( 默认 left) ; click 的 clicks 可选 1/2/3( 默认 1) ; scroll 的 delta 正=向下滚、负=向上滚。 " ;
tool [ " inputSchema " ] = BuildRemoteMouseInputSchema ( ) ;
tool [ " outputSchema " ] = BuildRemoteMouseOutputSchema ( ) ;
tools . append ( tool ) ;
}
}
result [ " tools " ] = tools ;
@@ -3113,6 +3124,290 @@ std::string BuildRemoteKeyboard(const Json::Value& id, const Json::Value& args,
return BuildResult ( id , result ) ;
}
// ===== P5: remote_mouse( 鼠标注入, move/down/up/click/right_click/middle_click/drag/scroll) =====
// 读取归一化坐标( 0..1 浮点,接受 JSON number 或数字字符串),越界钳到 [0,1]。
static bool GetNormCoord ( const Json : : Value & args , const char * key , double & out , std : : string & err ) {
if ( ! args . isMember ( key ) ) { err = std : : string ( " Missing required parameter: " ) + key ; return false ; }
const Json : : Value & v = args [ key ] ;
if ( v . isNumeric ( ) ) {
out = v . asDouble ( ) ;
} else if ( v . isString ( ) ) {
const std : : string s = v . asString ( ) ;
if ( s . empty ( ) ) { err = std : : string ( " Invalid " ) + key + " : expected a number in 0..1 " ; return false ; }
char * end = nullptr ;
double d = strtod ( s . c_str ( ) , & end ) ;
if ( end = = s . c_str ( ) | | * end ! = ' \0 ' ) { err = std : : string ( " Invalid " ) + key + " : expected a number in 0..1 " ; return false ; }
out = d ;
} else {
err = std : : string ( " Invalid " ) + key + " : expected a number in 0..1 " ;
return false ;
}
if ( out ! = out ) { // NaN( 如字符串 "nan")会绕过钳制并令 (int)(n*screen) 未定义行为
err = std : : string ( " Invalid " ) + key + " : not a finite number " ;
return false ;
}
if ( out < 0.0 ) out = 0.0 ;
if ( out > 1.0 ) out = 1.0 ;
return true ;
}
// 读取可选整数参数( JSON number 或数字字符串);缺失/非法返回 false( 不写 out) 。
static bool GetIntArg ( const Json : : Value & args , const char * key , int & out ) {
if ( ! args . isMember ( key ) ) return false ;
const Json : : Value & v = args [ key ] ;
if ( v . isNumeric ( ) ) { out = v . asInt ( ) ; return true ; }
if ( v . isString ( ) ) {
const std : : string s = v . asString ( ) ;
if ( IsDigits ( s ) | | ( ! s . empty ( ) & & s [ 0 ] = = ' - ' & & IsDigits ( s . substr ( 1 ) ) ) ) {
out = atoi ( s . c_str ( ) ) ;
return true ;
}
}
return false ;
}
// button 字符串 → 0=左/1=中/2=右;空或 left 默认左。
static bool ParseButton ( const std : : string & s , int & out ) {
if ( s . empty ( ) | | s = = " left " ) { out = 0 ; return true ; }
if ( s = = " middle " ) { out = 1 ; return true ; }
if ( s = = " right " ) { out = 2 ; return true ; }
return false ;
}
// button → 按下/抬起消息 + 按下 wParam( 对齐 WebService::HandleMouse) 。
static void ButtonMessages ( int button , UINT & downMsg , UINT & upMsg , uint64_t & downWParam ) {
if ( button = = 1 ) { downMsg = WM_MBUTTONDOWN ; upMsg = WM_MBUTTONUP ; downWParam = MK_MBUTTON ; }
else if ( button = = 2 ) { downMsg = WM_RBUTTONDOWN ; upMsg = WM_RBUTTONUP ; downWParam = MK_RBUTTON ; }
else { downMsg = WM_LBUTTONDOWN ; upMsg = WM_LBUTTONUP ; downWParam = MK_LBUTTON ; }
}
Json : : Value BuildRemoteMouseInputSchema ( ) {
Json : : Value props ( Json : : objectValue ) ;
Json : : Value idProp ( Json : : objectValue ) ;
idProp [ " type " ] = " string " ;
idProp [ " description " ] = u8 " 主机 id, 取 list_online_hosts / search_hosts 返回的 id 字段 " ;
props [ " id " ] = idProp ;
Json : : Value sid ( Json : : objectValue ) ;
sid [ " type " ] = " string " ;
sid [ " description " ] = u8 " remote_open 返回的 session_id " ;
props [ " session_id " ] = sid ;
Json : : Value act ( Json : : objectValue ) ;
act [ " type " ] = " string " ;
act [ " description " ] = u8 " 动作: move / down / up / click / right_click / middle_click / drag / scroll " ;
Json : : Value actEnum ( Json : : arrayValue ) ;
actEnum . append ( " move " ) ; actEnum . append ( " down " ) ; actEnum . append ( " up " ) ; actEnum . append ( " click " ) ;
actEnum . append ( " right_click " ) ; actEnum . append ( " middle_click " ) ; actEnum . append ( " drag " ) ; actEnum . append ( " scroll " ) ;
act [ " enum " ] = actEnum ;
props [ " action " ] = act ;
Json : : Value xProp ( Json : : objectValue ) ;
xProp [ " type " ] = " number " ;
xProp [ " description " ] = u8 " 归一化 X 坐标( 0=屏幕最左, 1=最右) " ;
props [ " x " ] = xProp ;
Json : : Value yProp ( Json : : objectValue ) ;
yProp [ " type " ] = " number " ;
yProp [ " description " ] = u8 " 归一化 Y 坐标( 0=屏幕最上, 1=最下) " ;
props [ " y " ] = yProp ;
Json : : Value x2Prop ( Json : : objectValue ) ;
x2Prop [ " type " ] = " number " ;
x2Prop [ " description " ] = u8 " 拖拽终点 X( 仅 drag) " ;
props [ " x2 " ] = x2Prop ;
Json : : Value y2Prop ( Json : : objectValue ) ;
y2Prop [ " type " ] = " number " ;
y2Prop [ " description " ] = u8 " 拖拽终点 Y( 仅 drag) " ;
props [ " y2 " ] = y2Prop ;
Json : : Value btn ( Json : : objectValue ) ;
btn [ " type " ] = " string " ;
btn [ " description " ] = u8 " 按键: left / middle / right( 默认 left) " ;
Json : : Value btnEnum ( Json : : arrayValue ) ;
btnEnum . append ( " left " ) ; btnEnum . append ( " middle " ) ; btnEnum . append ( " right " ) ;
btn [ " enum " ] = btnEnum ;
props [ " button " ] = btn ;
Json : : Value clk ( Json : : objectValue ) ;
clk [ " type " ] = " integer " ;
clk [ " description " ] = u8 " 点击次数 1/2/3( 仅 click, 默认 1) " ;
props [ " clicks " ] = clk ;
Json : : Value del ( Json : : objectValue ) ;
del [ " type " ] = " integer " ;
del [ " description " ] = u8 " 滚动量(仅 scroll; 正=向下滚,负=向上滚,与 Web 控制台一致;仅垂直滚轮,忽略 button) " ;
props [ " delta " ] = del ;
Json : : Value schema ( Json : : objectValue ) ;
schema [ " type " ] = " object " ;
schema [ " properties " ] = props ;
Json : : Value required ( Json : : arrayValue ) ;
required . append ( " id " ) ;
required . append ( " session_id " ) ;
required . append ( " action " ) ;
required . append ( " x " ) ;
required . append ( " y " ) ;
schema [ " required " ] = required ;
return schema ;
}
Json : : Value BuildRemoteMouseOutputSchema ( ) {
Json : : Value props ( Json : : objectValue ) ;
Json : : Value schema ( Json : : objectValue ) ;
schema [ " type " ] = " object " ;
schema [ " properties " ] = props ;
return schema ;
}
// tools/call: remote_mouse( 鼠标注入; 归一化坐标 → 物理像素)
std : : string BuildRemoteMouse ( const Json : : Value & id , const Json : : Value & args , CMy2015RemoteDlg * parent ) {
CMcpServer & mcp = CMcpServer : : Instance ( ) ;
mcp . SweepIdleScreenCtrl ( kScreenCtrlIdleTimeoutSec ) ;
uint64_t devId = 0 ;
context * ctx = nullptr ;
std : : string errJson ;
if ( ! ResolveScreenCtrlSessionHost ( id , args , parent , devId , ctx , errJson ) )
return errJson ;
std : : string sessionId = GetStringArg ( args , " session_id " ) ;
if ( sessionId . empty ( ) )
return BuildError ( id , - 32602 , " Missing required parameter: session_id " ) ;
std : : string action = GetStringArg ( args , " action " ) ;
if ( action . empty ( ) )
return BuildError ( id , - 32602 , " Missing required parameter: action " ) ;
// ---- 纯参数解析( 不依赖会话分辨率; BeginScreenCtrlAction 之后再无提前返回)----
bool knownAction =
action = = " move " | | action = = " down " | | action = = " up " | | action = = " click " | |
action = = " right_click " | | action = = " middle_click " | | action = = " drag " | | action = = " scroll " ;
if ( ! knownAction )
return BuildError ( id , - 32602 , " Unknown action: " + action ) ;
int button = 0 ;
std : : string buttonStr = GetStringArg ( args , " button " ) ;
if ( ! ParseButton ( buttonStr , button ) )
return BuildError ( id , - 32602 , " Invalid button: " + buttonStr + " (expected left/middle/right) " ) ;
int clicks = 1 ;
if ( args . isMember ( " clicks " ) ) {
if ( ! GetIntArg ( args , " clicks " , clicks ) )
return BuildError ( id , - 32602 , " clicks must be an integer 1..3 " ) ;
if ( clicks < 1 | | clicks > 3 )
return BuildError ( id , - 32602 , " clicks must be 1, 2 or 3 " ) ;
}
int delta = 0 ;
if ( args . isMember ( " delta " ) & & ! GetIntArg ( args , " delta " , delta ) )
return BuildError ( id , - 32602 , " delta must be an integer " ) ;
int notches = 0 ; // scroll 实际注入的滚动档数(= clamp(delta, -10, 10)),审计用
double nx = 0 , ny = 0 , nx2 = 0 , ny2 = 0 ;
std : : string coordErr ;
if ( ! GetNormCoord ( args , " x " , nx , coordErr ) ) return BuildError ( id , - 32602 , coordErr ) ;
if ( ! GetNormCoord ( args , " y " , ny , coordErr ) ) return BuildError ( id , - 32602 , coordErr ) ;
if ( action = = " drag " ) {
if ( ! GetNormCoord ( args , " x2 " , nx2 , coordErr ) ) return BuildError ( id , - 32602 , coordErr ) ;
if ( ! GetNormCoord ( args , " y2 " , ny2 , coordErr ) ) return BuildError ( id , - 32602 , coordErr ) ;
}
// 校验 session_id/busy 并置 busy, 同时取物理分辨率( screenW/H>0 由会话就绪保证)。
context * subCtx = nullptr ;
int screenW = 0 , screenH = 0 ;
int r = mcp . BeginScreenCtrlAction ( devId , sessionId , subCtx , screenW , screenH ) ;
if ( r = = 2 ) return BuildError ( id , - 32003 , " Device busy: another injection is in flight for this session " ) ;
if ( r = = 1 ) return BuildError ( id , - 32002 , " Remote control session not found, not ready, or closed: " + sessionId ) ;
// 归一化坐标 → 物理像素( round + clamp 到 [0, screen-1])
auto toPxX = [ & ] ( double n ) - > int { int p = ( int ) ( n * screenW + 0.5 ) ; return p < 0 ? 0 : ( p > screenW - 1 ? screenW - 1 : p ) ; } ;
auto toPxY = [ & ] ( double n ) - > int { int p = ( int ) ( n * screenH + 0.5 ) ; return p < 0 ? 0 : ( p > screenH - 1 ? screenH - 1 : p ) ; } ;
int px = toPxX ( nx ) , py = toPxY ( ny ) ;
int px2 = 0 , py2 = 0 ;
UINT downMsg , upMsg ; uint64_t downWParam ;
ButtonMessages ( button , downMsg , upMsg , downWParam ) ;
// 组装注入批次(按发送顺序;每个 MSG64 由客户端 ProcessCommand 分发到 SendInput)
std : : vector < MSG64 > batch ;
if ( action = = " move " ) {
batch . push_back ( BuildMouseMsg64 ( px , py , WM_MOUSEMOVE , 0 ) ) ;
} else if ( action = = " down " ) {
batch . push_back ( BuildMouseMsg64 ( px , py , downMsg , downWParam ) ) ;
} else if ( action = = " up " ) {
batch . push_back ( BuildMouseMsg64 ( px , py , upMsg , 0 ) ) ;
} else if ( action = = " click " | | action = = " right_click " | | action = = " middle_click " ) {
int b = ( action = = " right_click " ) ? 2 : ( action = = " middle_click " ? 1 : button ) ;
UINT dMsg , uMsg ; uint64_t dW ;
ButtonMessages ( b , dMsg , uMsg , dW ) ;
for ( int i = 0 ; i < clicks ; + + i ) {
batch . push_back ( BuildMouseMsg64 ( px , py , dMsg , dW ) ) ;
batch . push_back ( BuildMouseMsg64 ( px , py , uMsg , 0 ) ) ;
}
} else if ( action = = " drag " ) {
px2 = toPxX ( nx2 ) ; py2 = toPxY ( ny2 ) ;
batch . push_back ( BuildMouseMsg64 ( px , py , downMsg , downWParam ) ) ;
batch . push_back ( BuildMouseMsg64 ( px2 , py2 , WM_MOUSEMOVE , 0 ) ) ;
batch . push_back ( BuildMouseMsg64 ( px2 , py2 , upMsg , 0 ) ) ;
} else if ( action = = " scroll " ) {
// delta 正=向下滚(与 Web 控制台一致) ; WM_MOUSEWHEEL 负值=向下滚,故取反。
// 客户端对 WM_MOUSEWHEEL 会先 SetCursorPos(x,y) 再 MOUSEEVENTF_WHEEL, 单条消息即可定位+滚动。
notches = delta ;
if ( notches > 10 ) notches = 10 ;
if ( notches < - 10 ) notches = - 10 ;
short wheelDelta = ( short ) ( - notches * 120 ) ;
batch . push_back ( BuildMouseMsg64 ( px , py , WM_MOUSEWHEEL , MAKEWPARAM ( 0 , wheelDelta ) ) ) ;
}
// 组包 [COMMAND_SCREEN_CONTROL][MSG64*N],经屏幕子连接发送(主连接无效,设计 P2) 。
const int len = ( int ) ( 1 + batch . size ( ) * sizeof ( MSG64 ) ) ;
std : : vector < BYTE > packet ( ( size_t ) len ) ;
packet [ 0 ] = COMMAND_SCREEN_CONTROL ;
memcpy ( packet . data ( ) + 1 , batch . data ( ) , batch . size ( ) * sizeof ( MSG64 ) ) ;
bool ok = subCtx - > Send2Client ( packet . data ( ) , ( ULONG ) len ) ! = FALSE ;
mcp . EndScreenCtrlAction ( devId , sessionId ) ;
if ( ! ok )
return BuildError ( id , - 32004 , " Failed to send mouse injection " ) ;
// 审计(不可关闭;含归一化坐标与换算后的物理像素)
if ( parent ) {
std : : string audit = " host " + std : : to_string ( devId ) + " remote-mouse: session " + sessionId
+ " action= " + action ;
char cbuf [ 96 ] ;
sprintf ( cbuf , " norm=(%.3f,%.3f) px=(%d,%d) " , nx , ny , px , py ) ;
audit + = cbuf ;
if ( action = = " drag " ) {
sprintf ( cbuf , " -> norm2=(%.3f,%.3f) px2=(%d,%d) " , nx2 , ny2 , px2 , py2 ) ;
audit + = cbuf ;
}
if ( action = = " scroll " )
audit + = " notches= " + std : : to_string ( notches ) ;
if ( action = = " click " | | action = = " right_click " | | action = = " middle_click " )
audit + = " clicks= " + std : : to_string ( clicks ) ;
parent - > PostMessageA ( WM_SHOWERRORMSG ,
( WPARAM ) new CString ( ToAnsi ( audit , 936 ) . c_str ( ) ) ,
( LPARAM ) new CString ( _TR ( " MCP远程控制 " ) ) ) ;
}
Json : : Value result ( Json : : objectValue ) ;
result [ " structuredContent " ] = Json : : Value ( Json : : objectValue ) ;
Json : : Value content ( Json : : arrayValue ) ;
Json : : Value item ( Json : : objectValue ) ;
item [ " type " ] = " text " ;
item [ " text " ] = std : : string ( u8 " ok " ) ;
content . append ( item ) ;
result [ " content " ] = content ;
result [ " isError " ] = false ;
return BuildResult ( id , result ) ;
}
// tools/call 分派
std : : string BuildToolsCall ( const Json : : Value & root , CMy2015RemoteDlg * parent ) {
const Json : : Value & id = root [ " id " ] ;
@@ -3144,6 +3439,7 @@ std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
if ( toolName = = " remote_open " ) return BuildRemoteOpen ( id , args , parent ) ;
if ( toolName = = " remote_close " ) return BuildRemoteClose ( id , args , parent ) ;
if ( toolName = = " remote_keyboard " ) return BuildRemoteKeyboard ( id , args , parent ) ;
if ( toolName = = " remote_mouse " ) return BuildRemoteMouse ( id , args , parent ) ;
return BuildError ( id , - 32602 ,
" Unknown tool: " + ( toolName . empty ( ) ? std : : string ( " (empty) " ) : toolName ) ) ;