[libvirt] [PATCHv2 0/2] vbox: Add support for virDomainSendKey

Those patches implement support for virDomainSendKey in the VBOX driver. Since VBOX SDK does not support "holdtime" natively, it's being simulated by using usleep to wait for that time before "key-up" scancodes are sent. The "key-up" scancodes are automatically generated by add 0x80 to "key-down" scancodes. This is done to make the implementation match the behavior of how QEMU driver handles this, and therefore is different from what native VBoxManage command does - e.g. one has to type in "key-up" scancodes explicitely and no hold time support at all. --- v1 (for reference): https://www.redhat.com/archives/libvir-list/2015-March/msg01028.html v2: - add virReportError in all potentially failing code paths - coding style adjustment - mark for 1.2.15 Dawid Zamirski (2): vbox: Register IKeyboard with the unified API. vbox: Implement virDomainSendKey src/vbox/vbox_common.c | 120 ++++++++++++++++++++++++++++++++++++++++++ src/vbox/vbox_common.h | 1 + src/vbox/vbox_tmpl.c | 27 ++++++++++ src/vbox/vbox_uniformed_api.h | 8 +++ 4 files changed, 156 insertions(+) -- 2.3.4

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.4

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 | 120 +++++++++++++++++++++++++++++++++++++++++++++++++ src/vbox/vbox_common.h | 1 + 2 files changed, 121 insertions(+) diff --git a/src/vbox/vbox_common.c b/src/vbox/vbox_common.c index bb4de15..78b5906 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" @@ -7672,6 +7673,124 @@ 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)) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("Unable to open VirtualBox session with domain %s"), + dom->name); + goto cleanup; + } + + rc = gVBoxAPI.UISession.GetConsole(data->vboxSession, &console); + + if (NS_FAILED(rc) || !console) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("Unable to get Console object for domain %s"), + dom->name); + goto cleanup; + } + + rc = gVBoxAPI.UIConsole.GetKeyboard(console, &keyboard); + + if (NS_FAILED(rc)) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("Unable to get Keyboard object for domain %s"), + dom->name); + goto cleanup; + } + + rc = gVBoxAPI.UIKeyboard.PutScancodes(keyboard, nkeycodes, keyDownCodes, + &codesStored); + + if (NS_FAILED(rc)) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("Unable to send keyboard scancodes for domain %s"), + dom->name); + 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, + _("Unable to send keyboard scan codes to domain %s"), + dom->name); + 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 @@ -7746,6 +7865,7 @@ virHypervisorDriver vboxCommonDriver = { .nodeGetFreePages = vboxNodeGetFreePages, /* 1.2.6 */ .nodeAllocPages = vboxNodeAllocPages, /* 1.2.9 */ .domainHasManagedSaveImage = vboxDomainHasManagedSaveImage, /* 1.2.13 */ + .domainSendKey = vboxDomainSendKey, /* 1.2.15 */ }; static void updateDriver(void) diff --git a/src/vbox/vbox_common.h b/src/vbox/vbox_common.h index f9ce612..eb7fd76 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.4

On Tue, Apr 07, 2015 at 11:37:34AM -0400, Dawid Zamirski wrote:
Those patches implement support for virDomainSendKey in the VBOX driver. Since VBOX SDK does not support "holdtime" natively, it's being simulated by using usleep to wait for that time before "key-up" scancodes are sent. The "key-up" scancodes are automatically generated by add 0x80 to "key-down" scancodes. This is done to make the implementation match the behavior of how QEMU driver handles this, and therefore is different from what native VBoxManage command does - e.g. one has to type in "key-up" scancodes explicitely and no hold time support at all.
--- v1 (for reference): https://www.redhat.com/archives/libvir-list/2015-March/msg01028.html
v2: - add virReportError in all potentially failing code paths - coding style adjustment - mark for 1.2.15
Dawid Zamirski (2): vbox: Register IKeyboard with the unified API. vbox: Implement virDomainSendKey
src/vbox/vbox_common.c | 120 ++++++++++++++++++++++++++++++++++++++++++ src/vbox/vbox_common.h | 1 + src/vbox/vbox_tmpl.c | 27 ++++++++++ src/vbox/vbox_uniformed_api.h | 8 +++ 4 files changed, 156 insertions(+)
ACK and pushed. Jan
participants (2)
-
Dawid Zamirski
-
Ján Tomko