@@ -1132,8 +1132,11 @@ Json::Value BuildRemoteOpenInputSchema();
Json : : Value BuildRemoteOpenOutputSchema ( ) ;
Json : : Value BuildRemoteCloseInputSchema ( ) ;
Json : : Value BuildRemoteCloseOutputSchema ( ) ;
Json : : Value BuildRemoteKeyboardInputSchema ( ) ;
Json : : Value BuildRemoteKeyboardOutputSchema ( ) ;
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 ) ;
// tools/list
std : : string BuildToolsListResult ( const Json : : Value & id ) {
@@ -1357,6 +1360,14 @@ std::string BuildToolsListResult(const Json::Value& id) {
tool [ " outputSchema " ] = BuildRemoteCloseOutputSchema ( ) ;
tools . append ( tool ) ;
}
{
Json : : Value tool ( Json : : objectValue ) ;
tool [ " name " ] = " remote_keyboard " ;
tool [ " description " ] = u8 " 向已建立的远程控制会话注入键盘事件。action 可选 key_down / key_up / key_press / type: 前三种需 key( Windows 虚拟键名,如 ENTER/TAB/F5/LEFT/CTRL/ALT/SHIFT/WIN) , type 需 text( 仅 ASCII 文本;非 ASCII 请用 remote_clipboard + Ctrl+V) 。modifiers 可选( CTRL/ALT/SHIFT/WIN) 。 " ;
tool [ " inputSchema " ] = BuildRemoteKeyboardInputSchema ( ) ;
tool [ " outputSchema " ] = BuildRemoteKeyboardOutputSchema ( ) ;
tools . append ( tool ) ;
}
}
result [ " tools " ] = tools ;
@@ -2845,6 +2856,263 @@ std::string BuildRemoteClose(const Json::Value& id, const Json::Value& args, CMy
return BuildResult ( id , result ) ;
}
// ===== P5: remote_keyboard( 键盘注入, key_down / key_up / key_press / type) =====
// 键名 → 虚拟键码(对齐 Windows VK_* 去掉前缀,见设计 §4.3)。
// 支持:单字符 A-Z/0-9、F1-F24、命名键、修饰键。未知返回 0( VK 0 非真实键)。
static int MapKeyNameToVk ( const std : : string & nameIn ) {
std : : string n ;
for ( char c : nameIn )
n + = ( c > = ' a ' & & c < = ' z ' ) ? ( char ) ( c - ' a ' + ' A ' ) : c ;
if ( n . size ( ) = = 1 ) {
unsigned char c = ( unsigned char ) n [ 0 ] ;
if ( c > = ' A ' & & c < = ' Z ' ) return c ; // 'A'..'Z' == VK_A..VK_Z
if ( c > = ' 0 ' & & c < = ' 9 ' ) return c ; // '0'..'9' == VK_0..VK_9
return 0 ; // 标点等请走 type
}
// F1..F24
if ( n . size ( ) > = 2 & & n . size ( ) < = 3 & & n [ 0 ] = = ' F ' ) {
int fnum = atoi ( n . c_str ( ) + 1 ) ;
if ( fnum > = 1 & & fnum < = 24 ) return VK_F1 + ( fnum - 1 ) ;
return 0 ;
}
struct Named { const char * name ; int vk ; } ;
static const Named kNames [ ] = {
{ " ENTER " , VK_RETURN } , { " RETURN " , VK_RETURN } , { " TAB " , VK_TAB } , { " SPACE " , VK_SPACE } ,
{ " ESC " , VK_ESCAPE } , { " ESCAPE " , VK_ESCAPE } , { " BACKSPACE " , VK_BACK } ,
{ " DELETE " , VK_DELETE } , { " DEL " , VK_DELETE } , { " INSERT " , VK_INSERT } , { " INS " , VK_INSERT } ,
{ " HOME " , VK_HOME } , { " END " , VK_END } , { " PAGEUP " , VK_PRIOR } , { " PRIOR " , VK_PRIOR } ,
{ " PAGEDOWN " , VK_NEXT } , { " NEXT " , VK_NEXT } ,
{ " LEFT " , VK_LEFT } , { " RIGHT " , VK_RIGHT } , { " UP " , VK_UP } , { " DOWN " , VK_DOWN } ,
{ " CAPSLOCK " , VK_CAPITAL } , { " CAPITAL " , VK_CAPITAL } , { " NUMLOCK " , VK_NUMLOCK } ,
{ " SCROLLLOCK " , VK_SCROLL } , { " PRINTSCREEN " , VK_SNAPSHOT } , { " SNAPSHOT " , VK_SNAPSHOT } ,
{ " PAUSE " , VK_PAUSE } , { " APPS " , VK_APPS } , { " CONTEXTMENU " , VK_APPS } ,
{ " CTRL " , VK_CONTROL } , { " CONTROL " , VK_CONTROL } , { " ALT " , VK_MENU } , { " MENU " , VK_MENU } ,
{ " SHIFT " , VK_SHIFT } , { " WIN " , VK_LWIN } , { " WINDOWS " , VK_LWIN } , { " CMD " , VK_LWIN } , { " SUPER " , VK_LWIN } ,
} ;
for ( const Named & e : kNames )
if ( n = = e . name ) return e . vk ;
return 0 ;
}
// 是否修饰键( modifiers 白名单: CTRL/ALT/SHIFT/WIN)
static bool IsModifierVk ( int vk ) {
return vk = = VK_CONTROL | | vk = = VK_MENU | | vk = = VK_SHIFT | | vk = = VK_LWIN | | vk = = VK_RWIN ;
}
// 解析可选 modifiers 数组( JSON 字符串数组)→ 修饰键 VK 序列。非法返回 false 并填 err。
static bool ParseModifiers ( const Json : : Value & args , std : : vector < int > & out , std : : string & err ) {
out . clear ( ) ;
if ( ! args . isMember ( " modifiers " ) ) return true ;
const Json : : Value & mods = args [ " modifiers " ] ;
if ( mods . isNull ( ) ) return true ;
if ( ! mods . isArray ( ) ) { err = " modifiers must be an array of strings " ; return false ; }
for ( const auto & m : mods ) {
if ( out . size ( ) > = 8 ) { err = " too many modifiers (max 8) " ; return false ; }
int vk = MapKeyNameToVk ( m . asString ( ) ) ;
if ( vk = = 0 | | ! IsModifierVk ( vk ) ) {
err = " invalid modifier: " + ( m . isString ( ) ? m . asString ( ) : std : : string ( " (non-string) " ) ) ;
return false ;
}
out . push_back ( vk ) ;
}
return true ;
}
// 是否纯 ASCII( type 走物理键事件,只能可靠注入 ASCII/ANSI; 见设计 §6.4)
static bool IsAscii ( const std : : string & s ) {
for ( unsigned char c : s ) if ( c > = 0x80 ) return false ;
return true ;
}
// type: 逐字符 VkKeyScanA 映射为「( Shift↓) 字符↓ 字符↑( Shift↑) 」, 回车/制表符映射为按键。
// 注: VkKeyScanA 在服务端按服务端线程键盘布局解析字符→虚拟键;仅限 ASCII( 0..127)时
// 各布局的字母/数字/常用标点虚拟键一致,跨布局差异可忽略(非 ASCII 一律走剪贴板)。
static void BuildTypeBatch ( const std : : string & text , std : : vector < MSG64 > & batch ) {
for ( unsigned char ch : text ) {
if ( ch = = ' \r ' | | ch = = ' \n ' ) {
batch . push_back ( BuildKeyMsg64 ( VK_RETURN , true , false ) ) ;
batch . push_back ( BuildKeyMsg64 ( VK_RETURN , false , false ) ) ;
continue ;
}
if ( ch = = ' \t ' ) {
batch . push_back ( BuildKeyMsg64 ( VK_TAB , true , false ) ) ;
batch . push_back ( BuildKeyMsg64 ( VK_TAB , false , false ) ) ;
continue ;
}
SHORT scan = VkKeyScanA ( ( char ) ch ) ;
if ( scan = = - 1 ) continue ; // 无法映射(控制字符等)→ 跳过
int vk = scan & 0xFF ;
bool needShift = ( scan > > 8 ) & 1 ;
if ( needShift ) batch . push_back ( BuildKeyMsg64 ( VK_SHIFT , true , false ) ) ;
batch . push_back ( BuildKeyMsg64 ( vk , true , false ) ) ;
batch . push_back ( BuildKeyMsg64 ( vk , false , false ) ) ;
if ( needShift ) batch . push_back ( BuildKeyMsg64 ( VK_SHIFT , false , false ) ) ;
}
}
Json : : Value BuildRemoteKeyboardInputSchema ( ) {
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 " 动作: key_down / key_up / key_press / type " ;
props [ " action " ] = act ;
Json : : Value key ( Json : : objectValue ) ;
key [ " type " ] = " string " ;
key [ " description " ] = u8 " 按键( key_down/key_up/key_press 用) : Windows 虚拟键名(如 ENTER/TAB/F5/LEFT/CTRL/ALT/SHIFT/WIN) 或单个字母/数字 " ;
props [ " key " ] = key ;
Json : : Value text ( Json : : objectValue ) ;
text [ " type " ] = " string " ;
text [ " description " ] = u8 " 文本( type 用,仅 ASCII; 非 ASCII 请走 remote_clipboard + Ctrl+V) " ;
props [ " text " ] = text ;
Json : : Value mods ( Json : : objectValue ) ;
mods [ " type " ] = " array " ;
Json : : Value modItem ( Json : : objectValue ) ;
modItem [ " type " ] = " string " ;
mods [ " items " ] = modItem ;
mods [ " description " ] = u8 " 修饰键( 可选) : CTRL/ALT/SHIFT/WIN " ;
props [ " modifiers " ] = mods ;
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 " ) ;
schema [ " required " ] = required ;
return schema ;
}
Json : : Value BuildRemoteKeyboardOutputSchema ( ) {
Json : : Value props ( Json : : objectValue ) ;
Json : : Value schema ( Json : : objectValue ) ;
schema [ " type " ] = " object " ;
schema [ " properties " ] = props ;
return schema ;
}
// tools/call: remote_keyboard( 键盘注入; 成功返回空对象)
std : : string BuildRemoteKeyboard ( 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 " ) ;
std : : string keyName = GetStringArg ( args , " key " ) ;
std : : string text = GetStringArg ( args , " text " ) ;
std : : vector < int > mods ;
{
std : : string mErr ;
if ( ! ParseModifiers ( args , mods , mErr ) )
return BuildError ( id , - 32602 , mErr ) ;
}
// 组装注入批次(按发送顺序)
std : : vector < MSG64 > batch ;
if ( action = = " type " ) {
if ( text . empty ( ) )
return BuildError ( id , - 32602 , " Missing required parameter: text " ) ;
if ( ! IsAscii ( text ) )
return BuildError ( id , - 32602 , " text must be ASCII; non-ASCII text requires remote_clipboard + Ctrl+V " ) ;
if ( text . size ( ) > 1024 )
return BuildError ( id , - 32602 , " text too long (max 1024 bytes); use remote_clipboard + Ctrl+V for long text " ) ;
BuildTypeBatch ( text , batch ) ;
} else if ( action = = " key_down " | | action = = " key_up " | | action = = " key_press " ) {
if ( keyName . empty ( ) )
return BuildError ( id , - 32602 , " Missing required parameter: key " ) ;
int keyVk = MapKeyNameToVk ( keyName ) ;
if ( keyVk = = 0 )
return BuildError ( id , - 32602 , " Unknown key: " + keyName ) ;
bool doDown = ( action ! = " key_up " ) ;
bool doUp = ( action ! = " key_down " ) ;
if ( doDown ) {
for ( int m : mods ) batch . push_back ( BuildKeyMsg64 ( m , true , false ) ) ;
batch . push_back ( BuildKeyMsg64 ( keyVk , true , false ) ) ;
}
if ( doUp ) {
batch . push_back ( BuildKeyMsg64 ( keyVk , false , false ) ) ;
for ( auto it = mods . rbegin ( ) ; it ! = mods . rend ( ) ; + + it )
batch . push_back ( BuildKeyMsg64 ( * it , false , false ) ) ;
}
} else {
return BuildError ( id , - 32602 , " Unknown action: " + action ) ;
}
if ( batch . empty ( ) )
return BuildError ( id , - 32602 , " No key events generated " ) ;
// 校验 session_id/busy 并置 busy( 注入期间 close/sweep/断线不擦会话,镜像终端 busy 模式)。
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 ) ;
// 组包 [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 keyboard injection " ) ;
// 审计(不可关闭)
if ( parent ) {
std : : string audit = " host " + std : : to_string ( devId ) + " remote-keyboard: session " + sessionId
+ " action= " + action ;
if ( action = = " type " ) audit + = " text= " + text ;
else audit + = " key= " + keyName ;
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 " ] ;
@@ -2875,6 +3143,7 @@ std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
if ( toolName = = " terminal_close " ) return BuildTerminalClose ( id , args , 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 ) ;
return BuildError ( id , - 32602 ,
" Unknown tool: " + ( toolName . empty ( ) ? std : : string ( " (empty) " ) : toolName ) ) ;