[libvirt] [RFC PATCH 0/2] Vbox: Add support for virDomainSendKey

Hello, Those small patches implement virDomainSendKey support in VBOX driver. However, the VBOX SDK does not support "holdtime" so I used usleep to wait for that time before sending "key-up" scancodes. This makes it behave similarly to QEMU driver, however I'm not sure if that way of handling this would be preferred. Another option, would be to ignore holdtime argument and make virDomainSendKey work the same as via VBoxManage cli tool where one has to send "key-down" scancodes followed by "key-up" scancodes. For this RFC paches, I've choosen to make it work as close to the public API documentation. Dawid Zamirski (2): vbox: Register IKeyboard with the unified API. vbox: Implement virDomainSendKey src/vbox/vbox_common.c | 107 ++++++++++++++++++++++++++++++++++++++++++ src/vbox/vbox_common.h | 1 + src/vbox/vbox_tmpl.c | 27 +++++++++++ src/vbox/vbox_uniformed_api.h | 8 ++++ 4 files changed, 143 insertions(+) -- 2.3.3

The IKeyboard COM object is needed to implement virDomainSendKey and is available in all supported VBOX versions. --- src/vbox/vbox_tmpl.c | 27 +++++++++++++++++++++++++++ src/vbox/vbox_uniformed_api.h | 8 ++++++++ 2 files changed, 35 insertions(+) diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index 37ec8e1..22eecd4 100644 --- a/src/vbox/vbox_tmpl.c +++ b/src/vbox/vbox_tmpl.c @@ -3437,6 +3437,12 @@ _consoleGetDisplay(IConsole *console, IDisplay **display) } static nsresult +_consoleGetKeyboard(IConsole *console, IKeyboard **keyboard) +{ + return console->vtbl->GetKeyboard(console, keyboard); +} + +static nsresult _progressWaitForCompletion(IProgress *progress, PRInt32 timeout) { return progress->vtbl->WaitForCompletion(progress, timeout); @@ -4599,6 +4605,20 @@ _hardDiskGetFormat(IHardDisk *hardDisk, PRUnichar **format) return hardDisk->vtbl->GetFormat(hardDisk, format); } +static nsresult +_keyboardPutScancode(IKeyboard *keyboard, PRInt32 scancode) +{ + return keyboard->vtbl->PutScancode(keyboard, scancode); +} + +static nsresult +_keyboardPutScancodes(IKeyboard *keyboard, PRUint32 scancodesSize, + PRInt32 *scanCodes, PRUint32 *codesStored) +{ + return keyboard->vtbl->PutScancodes(keyboard, scancodesSize, scanCodes, + codesStored); +} + static bool _machineStateOnline(PRUint32 state) { return ((state >= MachineState_FirstOnline) && @@ -4757,6 +4777,7 @@ static vboxUniformedIConsole _UIConsole = { .TakeSnapshot = _consoleTakeSnapshot, .DeleteSnapshot = _consoleDeleteSnapshot, .GetDisplay = _consoleGetDisplay, + .GetKeyboard = _consoleGetKeyboard, }; static vboxUniformedIProgress _UIProgress = { @@ -4951,6 +4972,11 @@ static vboxUniformedIHardDisk _UIHardDisk = { .GetFormat = _hardDiskGetFormat, }; +static vboxUniformedIKeyboard _UIKeyboard = { + .PutScancode = _keyboardPutScancode, + .PutScancodes = _keyboardPutScancodes, +}; + static uniformedMachineStateChecker _machineStateChecker = { .Online = _machineStateOnline, .Inactive = _machineStateInactive, @@ -5008,6 +5034,7 @@ void NAME(InstallUniformedAPI)(vboxUniformedAPI *pVBoxAPI) pVBoxAPI->UIHNInterface = _UIHNInterface; pVBoxAPI->UIDHCPServer = _UIDHCPServer; pVBoxAPI->UIHardDisk = _UIHardDisk; + pVBoxAPI->UIKeyboard = _UIKeyboard; pVBoxAPI->machineStateChecker = _machineStateChecker; #if VBOX_API_VERSION <= 2002000 || VBOX_API_VERSION >= 4000000 diff --git a/src/vbox/vbox_uniformed_api.h b/src/vbox/vbox_uniformed_api.h index babc1e6..5d190ce 100644 --- a/src/vbox/vbox_uniformed_api.h +++ b/src/vbox/vbox_uniformed_api.h @@ -286,6 +286,7 @@ typedef struct { PRUnichar *description, IProgress **progress); nsresult (*DeleteSnapshot)(IConsole *console, vboxIIDUnion *iidu, IProgress **progress); nsresult (*GetDisplay)(IConsole *console, IDisplay **display); + nsresult (*GetKeyboard)(IConsole *console, IKeyboard **keyboard); } vboxUniformedIConsole; /* Functions for IProgress */ @@ -534,6 +535,12 @@ typedef struct { } vboxUniformedIHardDisk; typedef struct { + nsresult (*PutScancode)(IKeyboard *keyboard, PRInt32 scancode); + nsresult (*PutScancodes)(IKeyboard *keyboard, PRUint32 scancodesSize, + PRInt32 *scanCodes, PRUint32 *codesStored); +} vboxUniformedIKeyboard; + +typedef struct { bool (*Online)(PRUint32 state); bool (*Inactive)(PRUint32 state); bool (*NotStart)(PRUint32 state); @@ -591,6 +598,7 @@ typedef struct { vboxUniformedIHNInterface UIHNInterface; vboxUniformedIDHCPServer UIDHCPServer; vboxUniformedIHardDisk UIHardDisk; + vboxUniformedIKeyboard UIKeyboard; uniformedMachineStateChecker machineStateChecker; /* vbox API features */ bool domainEventCallbacks; -- 2.3.3

Since the holdtime is not supported by VBOX SDK, it's being simulated by sleeping before sending the key-up codes. The key-up codes are auto-generated based on XT codeset rules (adding of 0x80 to key-down) which results in the same behavior as for QEMU implementation. --- src/vbox/vbox_common.c | 107 +++++++++++++++++++++++++++++++++++++++++++++++++ src/vbox/vbox_common.h | 1 + 2 files changed, 108 insertions(+) diff --git a/src/vbox/vbox_common.c b/src/vbox/vbox_common.c index 0fb53aa..88bd226 100644 --- a/src/vbox/vbox_common.c +++ b/src/vbox/vbox_common.c @@ -33,6 +33,7 @@ #include "virstring.h" #include "virfile.h" #include "virtime.h" +#include "virkeycode.h" #include "snapshot_conf.h" #include "vbox_snapshot_conf.h" #include "fdstream.h" @@ -7668,6 +7669,111 @@ vboxDomainHasManagedSaveImage(virDomainPtr dom, unsigned int flags) return ret; } +static int +vboxDomainSendKey(virDomainPtr dom, + unsigned int codeset, + unsigned int holdtime, + unsigned int *keycodes, + int nkeycodes, + unsigned int flags) +{ + int ret = -1; + vboxGlobalData *data = dom->conn->privateData; + IConsole *console = NULL; + vboxIIDUnion iid; + IMachine *machine = NULL; + IKeyboard *keyboard = NULL; + PRInt32 *keyDownCodes = NULL; + PRInt32 *keyUpCodes = NULL; + PRUint32 codesStored = 0; + nsresult rc; + size_t i; + int keycode; + + if (!data->vboxObj) + return ret; + + virCheckFlags(0, -1); + + keyDownCodes = (PRInt32 *) keycodes; + + if (VIR_ALLOC_N(keyUpCodes, nkeycodes) < 0) + return ret; + + /* translate keycodes to xt and generate keyup scancodes */ + for (i = 0; i < nkeycodes; i++) { + if (codeset != VIR_KEYCODE_SET_XT) { + keycode = virKeycodeValueTranslate(codeset, VIR_KEYCODE_SET_XT, + keyDownCodes[i]); + if (keycode < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("cannot translate keycode %u of %s codeset to" + " xt keycode"), + keyDownCodes[i], + virKeycodeSetTypeToString(codeset)); + goto cleanup; + } + keyDownCodes[i] = keycode; + } + + keyUpCodes[i] = keyDownCodes[i] + 0x80; + } + + if (openSessionForMachine(data, dom->uuid, &iid, &machine, false) < 0) + goto cleanup; + + rc = gVBoxAPI.UISession.OpenExisting(data, &iid, machine); + + if (NS_FAILED(rc)) + goto cleanup; + + rc = gVBoxAPI.UISession.GetConsole(data->vboxSession, &console); + + if (NS_FAILED(rc) || !console) + goto cleanup; + + rc = gVBoxAPI.UIConsole.GetKeyboard(console, &keyboard); + + if (NS_FAILED(rc)) + goto cleanup; + + rc = gVBoxAPI.UIKeyboard.PutScancodes(keyboard, nkeycodes, keyDownCodes, + &codesStored); + + if (NS_FAILED(rc)) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Unable to send keyboard scancodes")); + goto cleanup; + } + + /* since VBOX does not support holdtime, simulate it by sleeping and + then sending the release key scancodes */ + if (holdtime > 0) { + usleep(holdtime * 1000); + } + + rc = gVBoxAPI.UIKeyboard.PutScancodes(keyboard, nkeycodes, keyUpCodes, + &codesStored); + + if (NS_FAILED(rc)) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Unable to send keyboard scan codes")); + goto cleanup; + } + + ret = 0; + + cleanup: + VIR_FREE(keyUpCodes); + VBOX_RELEASE(keyboard); + VBOX_RELEASE(console); + gVBoxAPI.UISession.Close(data->vboxSession); + VBOX_RELEASE(machine); + vboxIIDUnalloc(&iid); + + return ret; +} + /** * Function Tables @@ -7742,6 +7848,7 @@ virHypervisorDriver vboxCommonDriver = { .nodeGetFreePages = vboxNodeGetFreePages, /* 1.2.6 */ .nodeAllocPages = vboxNodeAllocPages, /* 1.2.9 */ .domainHasManagedSaveImage = vboxDomainHasManagedSaveImage, /* 1.2.13 */ + .domainSendKey = vboxDomainSendKey, /* 1.2.14 */ }; static void updateDriver(void) diff --git a/src/vbox/vbox_common.h b/src/vbox/vbox_common.h index d318921..ba19b1a 100644 --- a/src/vbox/vbox_common.h +++ b/src/vbox/vbox_common.h @@ -341,6 +341,7 @@ typedef nsISupports IHost; typedef nsISupports IHostNetworkInterface; typedef nsISupports IDHCPServer; typedef IMedium IHardDisk; +typedef nsISupports IKeyboard; /* Macros for all vbox drivers. */ -- 2.3.3

On Thu, Mar 19, 2015 at 05:43:57PM -0400, Dawid Zamirski wrote:
Since the holdtime is not supported by VBOX SDK, it's being simulated by sleeping before sending the key-up codes. The key-up codes are auto-generated based on XT codeset rules (adding of 0x80 to key-down) which results in the same behavior as for QEMU implementation. --- src/vbox/vbox_common.c | 107 +++++++++++++++++++++++++++++++++++++++++++++++++ src/vbox/vbox_common.h | 1 + 2 files changed, 108 insertions(+)
Looks good to me, a few nits inline:
diff --git a/src/vbox/vbox_common.c b/src/vbox/vbox_common.c index 0fb53aa..88bd226 100644 --- a/src/vbox/vbox_common.c +++ b/src/vbox/vbox_common.c @@ -33,6 +33,7 @@ #include "virstring.h" #include "virfile.h" #include "virtime.h" +#include "virkeycode.h" #include "snapshot_conf.h" #include "vbox_snapshot_conf.h" #include "fdstream.h" @@ -7668,6 +7669,111 @@ vboxDomainHasManagedSaveImage(virDomainPtr dom, unsigned int flags) return ret; }
+static int +vboxDomainSendKey(virDomainPtr dom, + unsigned int codeset, + unsigned int holdtime, + unsigned int *keycodes, + int nkeycodes, + unsigned int flags) +{ + int ret = -1; + vboxGlobalData *data = dom->conn->privateData; + IConsole *console = NULL; + vboxIIDUnion iid; + IMachine *machine = NULL; + IKeyboard *keyboard = NULL; + PRInt32 *keyDownCodes = NULL; + PRInt32 *keyUpCodes = NULL; + PRUint32 codesStored = 0; + nsresult rc; + size_t i; + int keycode; + + if (!data->vboxObj) + return ret; + + virCheckFlags(0, -1); + + keyDownCodes = (PRInt32 *) keycodes; + + if (VIR_ALLOC_N(keyUpCodes, nkeycodes) < 0) + return ret; + + /* translate keycodes to xt and generate keyup scancodes */ + for (i = 0; i < nkeycodes; i++) { + if (codeset != VIR_KEYCODE_SET_XT) { + keycode = virKeycodeValueTranslate(codeset, VIR_KEYCODE_SET_XT, + keyDownCodes[i]); + if (keycode < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("cannot translate keycode %u of %s codeset to" + " xt keycode"), + keyDownCodes[i], + virKeycodeSetTypeToString(codeset)); + goto cleanup; + } + keyDownCodes[i] = keycode; + } + + keyUpCodes[i] = keyDownCodes[i] + 0x80; + } + + if (openSessionForMachine(data, dom->uuid, &iid, &machine, false) < 0) + goto cleanup; + + rc = gVBoxAPI.UISession.OpenExisting(data, &iid, machine); + + if (NS_FAILED(rc))
Missing virReportError here, ...
+ goto cleanup; + + rc = gVBoxAPI.UISession.GetConsole(data->vboxSession, &console); + + if (NS_FAILED(rc) || !console)
... here ...
+ goto cleanup; + + rc = gVBoxAPI.UIConsole.GetKeyboard(console, &keyboard); + + if (NS_FAILED(rc))
... and here.
+ goto cleanup; + + rc = gVBoxAPI.UIKeyboard.PutScancodes(keyboard, nkeycodes, keyDownCodes, + &codesStored); + + if (NS_FAILED(rc)) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Unable to send keyboard scancodes")); + goto cleanup; + } + + /* since VBOX does not support holdtime, simulate it by sleeping and + then sending the release key scancodes */ + if (holdtime > 0) { + usleep(holdtime * 1000); + }
This fails syntax-check for me: Curly brackets around single-line body: src/vbox/vbox_common.c:7755-7757: if (holdtime > 0) { usleep(holdtime * 1000); } maint.mk: incorrect formatting, see HACKING for rules cfg.mk:1059: recipe for target 'bracket-spacing-check' failed
+ + rc = gVBoxAPI.UIKeyboard.PutScancodes(keyboard, nkeycodes, keyUpCodes, + &codesStored); + + if (NS_FAILED(rc)) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Unable to send keyboard scan codes")); + goto cleanup; + } + + ret = 0; + + cleanup: + VIR_FREE(keyUpCodes); + VBOX_RELEASE(keyboard); + VBOX_RELEASE(console); + gVBoxAPI.UISession.Close(data->vboxSession); + VBOX_RELEASE(machine); + vboxIIDUnalloc(&iid); + + return ret; +} +
/** * Function Tables @@ -7742,6 +7848,7 @@ virHypervisorDriver vboxCommonDriver = { .nodeGetFreePages = vboxNodeGetFreePages, /* 1.2.6 */ .nodeAllocPages = vboxNodeAllocPages, /* 1.2.9 */ .domainHasManagedSaveImage = vboxDomainHasManagedSaveImage, /* 1.2.13 */ + .domainSendKey = vboxDomainSendKey, /* 1.2.14 */
1.2.15 now Jan

On Tue, 2015-04-07 at 12:27 +0200, Ján Tomko wrote:
On Thu, Mar 19, 2015 at 05:43:57PM -0400, Dawid Zamirski wrote:
Since the holdtime is not supported by VBOX SDK, it's being simulated by sleeping before sending the key-up codes. The key-up codes are auto-generated based on XT codeset rules (adding of 0x80 to key- down) which results in the same behavior as for QEMU implementation. --- src/vbox/vbox_common.c | 107 +++++++++++++++++++++++++++++++++++++++++++++++++ src/vbox/vbox_common.h | 1 + 2 files changed, 108 insertions(+)
Looks good to me, a few nits inline:
Jan
Hi Jan, Thanks for the review, I've posted v2 here: https://www.redhat.com/archives/libvir-list/2015-April/msg00227.html Regards, Dawid
participants (2)
-
Dawid Zamirski
-
Ján Tomko