Hi @R Hartley ,
Thanks for getting back to me!
After looking at details you gave me this is what I can see:
1. Interface
You are calling QueryInterface(__uuidof(IMsRdpClientNonScriptable8)) and then use that pointer for SendKeys. On Windows 11, SendKeys is only guaranteed on the base interface IMsRdpClientNonScriptable, not the versioned NonScriptable8. Using v8 can fail or throw exceptions. You should change it to:
hr = pThis->m_pRdpClient->QueryInterface(__uuidof(IMsRdpClientNonScriptable),
(void**)&pThis->m_pRdpClientNonScriptable);
2. Bitfield Packing
You are using a struct lParam_type and set repeat_count = 0. Problem is that Windows expects repeat_count to be 1 for a single key press. Zero can cause invalid lParam values.
Also, the structure approach is risky because SendKeys expects an array of LONG values with bits packed exactly like WM_KEYDOWN/WM_KEYUP. A C++ bitfield may not match the required layout.
You could try:
- Use integer bit shifts or ensure the struct is packed correctly.
- Set
repeat_count = 1 for both down and up events.
3. Previous State and Transition Bits
You set these correctly for up/down, so that part is fine:
- Down:
previous_key_state = 0, transition = 0
- Up:
previous_key_state = 1, transition = 1
That's good.
4. Extended Key Flag
You set is_extended_key = 0 for “X”, which is fine because X is not an extended key. But for arrows, right Ctrl, etc., this must be 1.
5. Ensure the control is connected and focused just before sending.
This keeps your CAxWindow/CLSID flow. Changes are narrowly scoped: interface choice + integer packing:
// Assumes: m_pRdpClient is a valid IMsRdpClient (from QI).
// m_hWndRDP is your CAxWindow host HWND.
// Sends letter 'X' as DOWN+UP.
#include <windows.h>
#include <atlbase.h>
#include <atlcom.h>
#include <atlwin.h>
#include "mstscax.h" // RDP ActiveX headers
static inline LONG MakeKeyLParam(UINT scanCode, bool isKeyUp, bool isExtended = false)
{
// Pack like a Win32 keyboard message lParam:
// bits 0..15 : repeat count (1 for single press)
// bits 16..23 : scan code (low 8 bits)
// bit 24 : extended key (1 for RAlt/RCtrl, arrows, Ins/Del/Home/End/PgUp/PgDn, numpad Enter)
// bits 25..28 : reserved = 0
// bit 29 : context (Alt) = 0 here
// bit 30 : previous key state (0=DOWN, 1=UP)
// bit 31 : transition state (0=DOWN, 1=UP)
const LONG repeat = 1;
const LONG scBits = (LONG(scanCode) & 0xFF) << 16;
const LONG extBit = (isExtended ? 1L : 0L) << 24;
const LONG contextBit = 0L << 29;
const LONG prevBit = (isKeyUp ? 1L : 0L) << 30;
const LONG transBit = (isKeyUp ? 1L : 0L) << 31;
return repeat | scBits | extBit | contextBit | prevBit | transBit;
}
bool xoRDP::sendX()
{
if (!m_pRdpClient) return false;
// FIX 1: Use the base NonScriptable interface.
CComPtr<IMsRdpClientNonScriptable> spNS;
HRESULT hr = m_pRdpClient->QueryInterface(__uuidof(IMsRdpClientNonScriptable),
reinterpret_cast<void**>(&spNS));
if (FAILED(hr) || !spNS) {
do_log(L"ERROR: QI(IMsRdpClientNonScriptable) failed hr=0x" + to_wstring(hr));
return false;
}
// Optional: ensure ActiveX has focus so the control's input path is active.
if (m_hWndRDP) ::SetFocus(m_hWndRDP);
// Scan code for 'X' (not extended).
const UINT scX = MapVirtualKey(VK_X, MAPVK_VK_TO_VSC);
if (scX == 0) {
do_log(L"ERROR: MapVirtualKey(VK_X) returned 0");
return false;
}
LONG keyData[2];
VARIANT_BOOL keyUp[2];
// DOWN event: prev=0, transition=0
keyData[0] = MakeKeyLParam(scX, /*isKeyUp=*/false, /*isExtended=*/false);
keyUp[0] = VARIANT_FALSE;
// UP event: prev=1, transition=1
keyData[1] = MakeKeyLParam(scX, /*isKeyUp=*/true, /*isExtended=*/false);
keyUp[1] = VARIANT_TRUE;
const LONG numKeys = 2;
hr = spNS->SendKeys(numKeys, keyUp, keyData);
if (FAILED(hr)) {
do_log(L"WARNING: SendKeys(X) failed hr=0x" + to_wstring(hr));
return false;
}
do_log(L"SUCCESS: SendKeys(X) delivered");
return true;
}
In short:
- Interface:
IMsRdpClientNonScriptable::SendKeys is the reliable entry point. Calling the v8 interface is what’s “broken/disabled” on Win11 in your scenario.
- Bits:
SendKeys passes your events straight through as if they were WM_KEYDOWN/WM_KEYUP. The function above encodes the exact flags the control expects (repeat=1; DOWN→prev=0/transition=0; UP→prev=1/transition=1).
- Extended flag: Letters like X are not “extended” (
extended=0). Use extended=1 only for right Alt/Ctrl, arrows, Ins/Del/Home/End/PgUp/PgDn, and keypad Enter (per WM_KEYDOWN docs: https://dori-uw-1.kuma-moon.com/windows/win32/inputdev/wm-keydown).
If you need to send right Ctrl or an arrow next, flip isExtended=true for those keys and use the appropriate VK_ constant. If the remote layout differs, switch to MapVirtualKeyEx with the target HKL (not strictly required to solve this case, but good to know: https://dori-uw-1.kuma-moon.com/en-us/windows/win32/api/winuser/nf-winuser-mapvirtualkeyexa).
Hope this helps! Please keep me updated if possible.