Signed-off-by: Matt Coleman <matt(a)datto.com>
---
src/hyperv/hyperv_driver.c | 207 ++++++++++++++++++++++++++
src/hyperv/hyperv_wmi_classes.h | 21 +++
src/hyperv/hyperv_wmi_generator.input | 163 ++++++++++++++++++++
3 files changed, 391 insertions(+)
diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c
index c99d2a4e79..419ca96828 100644
--- a/src/hyperv/hyperv_driver.c
+++ b/src/hyperv/hyperv_driver.c
@@ -22,6 +22,8 @@
#include <config.h>
+#include <fcntl.h>
+
#include "internal.h"
#include "datatypes.h"
#include "virdomainobjlist.h"
@@ -38,6 +40,8 @@
#include "virstring.h"
#include "virkeycode.h"
#include "domain_conf.h"
+#include "virfdstream.h"
+#include "virfile.h"
#define VIR_FROM_THIS VIR_FROM_HYPERV
@@ -294,6 +298,73 @@ hypervCapsInit(hypervPrivate *priv)
return NULL;
}
+
+static int
+hypervGetVideoResolution(hypervPrivate *priv,
+ char *vm_uuid,
+ int *xRes,
+ int *yRes,
+ bool fallback)
+{
+ g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER;
+ g_autoptr(Msvm_S3DisplayController) s3Display = NULL;
+ g_autoptr(Msvm_SyntheticDisplayController) synthetic = NULL;
+ g_autoptr(Msvm_VideoHead) heads = NULL;
+ const char *wmiClass = NULL;
+ char *deviceId = NULL;
+ g_autofree char *enabledStateString = NULL;
+
+ if (fallback) {
+ wmiClass = "Msvm_S3DisplayController";
+
+ virBufferEscapeSQL(&query,
+ MSVM_S3DISPLAYCONTROLLER_WQL_SELECT "WHERE SystemName =
'%s'",
+ vm_uuid);
+
+ if (hypervGetWmiClass(Msvm_S3DisplayController, &s3Display) < 0 ||
!s3Display)
+ return -1;
+
+ deviceId = s3Display->data->DeviceID;
+ } else {
+ wmiClass = "Msvm_SyntheticDisplayController";
+
+ virBufferEscapeSQL(&query,
+ MSVM_SYNTHETICDISPLAYCONTROLLER_WQL_SELECT "WHERE
SystemName = '%s'",
+ vm_uuid);
+
+ if (hypervGetWmiClass(Msvm_SyntheticDisplayController, &synthetic) < 0 ||
!synthetic)
+ return -1;
+
+ deviceId = synthetic->data->DeviceID;
+ }
+
+ virBufferFreeAndReset(&query);
+
+ virBufferAsprintf(&query,
+ "ASSOCIATORS OF {%s."
+ "CreationClassName='%s',"
+ "DeviceID='%s',"
+ "SystemCreationClassName='Msvm_ComputerSystem',"
+ "SystemName='%s'"
+ "} WHERE AssocClass = Msvm_VideoHeadOnController "
+ "ResultClass = Msvm_VideoHead",
+ wmiClass, wmiClass, deviceId, vm_uuid);
+
+ if (hypervGetWmiClass(Msvm_VideoHead, &heads) < 0)
+ return -1;
+
+ enabledStateString = g_strdup_printf("%d",
CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_ENABLED);
+ if (heads && STREQ(heads->data->EnabledState, enabledStateString)) {
+ *xRes = heads->data->CurrentHorizontalResolution;
+ *yRes = heads->data->CurrentVerticalResolution;
+
+ return 0;
+ }
+
+ return -1;
+}
+
+
/*
* Virtual device functions
*/
@@ -2313,6 +2384,141 @@ hypervDomainGetState(virDomainPtr domain, int *state, int
*reason,
}
+static char *
+hypervDomainScreenshot(virDomainPtr domain,
+ virStreamPtr stream,
+ unsigned int screen G_GNUC_UNUSED,
+ unsigned int flags)
+{
+ char uuid_string[VIR_UUID_STRING_BUFLEN];
+ hypervPrivate *priv = domain->conn->privateData;
+ g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER;
+ g_autoptr(hypervInvokeParamsList) params = NULL;
+ g_auto(WsXmlDocH) ret_doc = NULL;
+ int xRes = 640;
+ int yRes = 480;
+ g_autofree char *width = NULL;
+ g_autofree char *height = NULL;
+ g_autofree char *imageDataText = NULL;
+ g_autofree unsigned char *imageDataBuffer = NULL;
+ size_t imageDataBufferSize;
+ const char *temporaryDirectory = NULL;
+ g_autofree char *temporaryFile = NULL;
+ g_autofree uint8_t *ppmBuffer = NULL;
+ char *result = NULL;
+ const char *xpath =
"/s:Envelope/s:Body/p:GetVirtualSystemThumbnailImage_OUTPUT/p:ImageData";
+ int pixelCount;
+ int pixelByteCount;
+ size_t i = 0;
+ int fd = -1;
+ bool unlinkTemporaryFile = false;
+
+ virCheckFlags(0, NULL);
+
+ virUUIDFormat(domain->uuid, uuid_string);
+
+ /* Hyper-V Generation 1 VMs use two video heads:
+ * - S3DisplayController is used for early boot screens.
+ * - SyntheticDisplayController takes over when the guest OS initializes its video
driver.
+ *
+ * This attempts to get the resolution from the SyntheticDisplayController first.
+ * If that fails, it falls back to S3DisplayController. */
+ if (hypervGetVideoResolution(priv, uuid_string, &xRes, &yRes, false) < 0)
{
+ if (hypervGetVideoResolution(priv, uuid_string, &xRes, &yRes, true) <
0)
+ goto cleanup;
+ }
+
+ /* prepare params */
+ params = hypervCreateInvokeParamsList("GetVirtualSystemThumbnailImage",
+ MSVM_VIRTUALSYSTEMMANAGEMENTSERVICE_SELECTOR,
+ Msvm_VirtualSystemManagementService_WmiInfo);
+ if (!params)
+ goto cleanup;
+
+ width = g_strdup_printf("%d", xRes);
+ hypervAddSimpleParam(params, "WidthPixels", width);
+
+ height = g_strdup_printf("%d", yRes);
+ hypervAddSimpleParam(params, "HeightPixels", height);
+
+ virBufferAsprintf(&query,
+ "ASSOCIATORS OF "
+
"{Msvm_ComputerSystem.CreationClassName='Msvm_ComputerSystem',Name='%s'}
"
+ "WHERE ResultClass = Msvm_VirtualSystemSettingData",
+ uuid_string);
+ hypervAddEprParam(params, "TargetSystem", &query,
Msvm_VirtualSystemSettingData_WmiInfo);
+
+ /* capture and parse the screenshot */
+ if (hypervInvokeMethod(priv, ¶ms, &ret_doc) < 0)
+ goto cleanup;
+
+ if (!ws_xml_get_soap_envelope(ret_doc)) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not retrieve
screenshot"));
+ goto cleanup;
+ }
+
+ imageDataText = ws_xml_get_xpath_value(ret_doc, (char *)xpath);
+
+ if (!imageDataText) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Failed to retrieve
image data"));
+ goto cleanup;
+ }
+
+ imageDataBuffer = g_base64_decode(imageDataText, &imageDataBufferSize);
+
+ pixelCount = imageDataBufferSize / 2;
+ pixelByteCount = pixelCount * 3;
+
+ ppmBuffer = g_new0(uint8_t, pixelByteCount);
+
+ /* convert rgb565 to rgb888 */
+ for (i = 0; i < pixelCount; i++) {
+ const uint16_t pixel = imageDataBuffer[i * 2] + (imageDataBuffer[i * 2 + 1]
<< 8);
+ const uint16_t redMask = 0xF800;
+ const uint16_t greenMask = 0x7E0;
+ const uint16_t blueMask = 0x1F;
+ const uint8_t redFive = (pixel & redMask) >> 11;
+ const uint8_t greenSix = (pixel & greenMask) >> 5;
+ const uint8_t blueFive = pixel & blueMask;
+ ppmBuffer[i * 3] = (redFive * 527 + 23) >> 6;
+ ppmBuffer[i * 3 + 1] = (greenSix * 259 + 33) >> 6;
+ ppmBuffer[i * 3 + 2] = (blueFive * 527 + 23) >> 6;
+ }
+
+ temporaryDirectory = getenv("TMPDIR");
+ if (!temporaryDirectory)
+ temporaryDirectory = "/tmp";
+ temporaryFile = g_strdup_printf("%s/libvirt.hyperv.screendump.XXXXXX",
temporaryDirectory);
+ if ((fd = g_mkstemp_full(temporaryFile, O_RDWR | O_CLOEXEC, S_IRUSR | S_IWUSR)) ==
-1) {
+ virReportSystemError(errno, _("g_mkstemp(\"%s\") failed"),
temporaryFile);
+ goto cleanup;
+ }
+ unlinkTemporaryFile = true;
+
+ /* write image data */
+ dprintf(fd, "P6\n%d %d\n255\n", xRes, yRes);
+ if (safewrite(fd, ppmBuffer, pixelByteCount) != pixelByteCount) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Failed to write
pixel data"));
+ goto cleanup;
+ }
+
+ if (VIR_CLOSE(fd) < 0)
+ virReportSystemError(errno, "%s", _("failed to close screenshot
file"));
+
+ if (virFDStreamOpenFile(stream, temporaryFile, 0, 0, O_RDONLY) < 0)
+ goto cleanup;
+
+ result = g_strdup("image/x-portable-pixmap");
+
+ cleanup:
+ VIR_FORCE_CLOSE(fd);
+ if (unlinkTemporaryFile)
+ unlink(temporaryFile);
+
+ return result;
+}
+
+
static int
hypervDomainSetVcpusFlags(virDomainPtr domain,
unsigned int nvcpus,
@@ -3474,6 +3680,7 @@ static virHypervisorDriver hypervHypervisorDriver = {
.domainSetMemoryFlags = hypervDomainSetMemoryFlags, /* 3.6.0 */
.domainGetInfo = hypervDomainGetInfo, /* 0.9.5 */
.domainGetState = hypervDomainGetState, /* 0.9.5 */
+ .domainScreenshot = hypervDomainScreenshot, /* 7.1.0 */
.domainSetVcpus = hypervDomainSetVcpus, /* 6.10.0 */
.domainSetVcpusFlags = hypervDomainSetVcpusFlags, /* 6.10.0 */
.domainGetVcpusFlags = hypervDomainGetVcpusFlags, /* 6.10.0 */
diff --git a/src/hyperv/hyperv_wmi_classes.h b/src/hyperv/hyperv_wmi_classes.h
index 86a7124799..faf98077eb 100644
--- a/src/hyperv/hyperv_wmi_classes.h
+++ b/src/hyperv/hyperv_wmi_classes.h
@@ -133,6 +133,27 @@ enum _Msvm_EthernetPortAllocationSettingData_EnabledState {
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * CIM_EnabledLogicalElement
+ */
+
+/*
https://docs.microsoft.com/en-us/windows/win32/hyperv_v2/cim-enabledlogic...
*/
+enum _CIM_EnabledLogicalElement_EnabledState {
+ CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_UNKNOWN = 0,
+ CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_OTHER = 1,
+ CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_ENABLED = 2,
+ CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_DISABLED = 3,
+ CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_SHUTTING_DOWN = 4,
+ CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_NOT_APPLICABLE = 5,
+ CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_ENABLED_BUT_OFFLINE = 6,
+ CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_IN_TEST = 7,
+ CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_DEFERRED = 8,
+ CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_QUIESCE = 9,
+ CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_STARTING = 10,
+};
+
+
+
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* WMI
*/
diff --git a/src/hyperv/hyperv_wmi_generator.input
b/src/hyperv/hyperv_wmi_generator.input
index f9d486bd4c..0b342cbfa6 100644
--- a/src/hyperv/hyperv_wmi_generator.input
+++ b/src/hyperv/hyperv_wmi_generator.input
@@ -968,3 +968,166 @@ class Msvm_VirtualEthernetSwitchManagementService
string StartMode
boolean Started
end
+
+
+class Msvm_SyntheticDisplayController
+ string InstanceID
+ string Caption
+ string Description
+ string ElementName
+ datetime InstallDate
+ string Name
+ uint16 OperationalStatus[]
+ string StatusDescriptions[]
+ string Status
+ uint16 HealthState
+ uint16 CommunicationStatus
+ uint16 DetailedStatus
+ uint16 OperatingStatus
+ uint16 PrimaryStatus
+ string EnabledState
+ string OtherEnabledState
+ uint16 RequestedState
+ uint16 EnabledDefault
+ datetime TimeOfLastStateChange
+ uint16 AvailableRequestedStates[]
+ uint16 TransitioningToState
+ string SystemCreationClassName
+ string SystemName
+ string CreationClassName
+ string DeviceID
+ boolean PowerManagementSupported
+ uint16 PowerManagementCapabilities[]
+ uint16 Availability
+ uint16 StatusInfo
+ uint32 LastErrorCode
+ string ErrorDescription
+ boolean ErrorCleared
+ uint64 PowerOnHours
+ uint64 TotalPowerOnHours
+ string OtherIdentifyingInfo[]
+ string IdentifyingDescriptions[]
+ uint16 AdditionalAvailability[]
+ uint64 MaxQuiesceTime
+ datetime TimeOfLastReset
+ uint16 ProtocolSupported
+ uint32 MaxNumberControlled
+ string ProtocolDescription
+ string VideoProcessor
+ uint16 VideoMemoryType
+ string OtherVideoMemoryType
+ uint32 NumberOfVideoPages
+ uint32 MaxMemorySupported
+ uint16 AcceleratorCapabilities[]
+ string CapabilityDescriptions[]
+ string OtherVideoArchitecture
+ uint16 VideoArchitecture
+end
+
+
+class Msvm_S3DisplayController
+ string InstanceID
+ string Caption
+ string Description
+ string ElementName
+ datetime InstallDate
+ string Name
+ uint16 OperationalStatus[]
+ string StatusDescriptions[]
+ string Status
+ uint16 HealthState
+ uint16 CommunicationStatus
+ uint16 DetailedStatus
+ uint16 OperatingStatus
+ uint16 PrimaryStatus
+ string EnabledState
+ string OtherEnabledState
+ uint16 RequestedState
+ uint16 EnabledDefault
+ datetime TimeOfLastStateChange
+ uint16 AvailableRequestedStates[]
+ uint16 TransitioningToState
+ string SystemCreationClassName
+ string SystemName
+ string CreationClassName
+ string DeviceID
+ boolean PowerManagementSupported
+ uint16 PowerManagementCapabilities[]
+ uint16 Availability
+ uint16 StatusInfo
+ uint32 LastErrorCode
+ string ErrorDescription
+ boolean ErrorCleared
+ string OtherIdentifyingInfo[]
+ uint64 PowerOnHours
+ uint64 TotalPowerOnHours
+ string IdentifyingDescriptions[]
+ uint16 AdditionalAvailability[]
+ uint64 MaxQuiesceTime
+ datetime TimeOfLastReset
+ uint16 ProtocolSupported
+ uint32 MaxNumberControlled
+ string ProtocolDescription
+ string VideoProcessor
+ uint16 VideoMemoryType
+ string OtherVideoMemoryType
+ uint32 NumberOfVideoPages
+ uint32 MaxMemorySupported
+ uint16 AcceleratorCapabilities[]
+ string CapabilityDescriptions[]
+ string OtherVideoArchitecture
+ uint16 VideoArchitecture
+end
+
+
+class Msvm_VideoHead
+ string InstanceID
+ string Caption
+ string Description
+ string ElementName
+ datetime InstallDate
+ string Name
+ uint16 OperationalStatus[]
+ string StatusDescriptions[]
+ string Status
+ uint16 HealthState
+ uint16 CommunicationStatus
+ uint16 DetailedStatus
+ uint16 OperatingStatus
+ uint16 PrimaryStatus
+ string EnabledState
+ string OtherEnabledState
+ uint16 RequestedState
+ uint16 EnabledDefault
+ datetime TimeOfLastStateChange
+ uint16 AvailableRequestedStates[]
+ uint16 TransitioningToState
+ string SystemCreationClassName
+ string SystemName
+ string CreationClassName
+ string DeviceID
+ boolean PowerManagementSupported
+ uint16 PowerManagementCapabilities[]
+ uint16 Availability
+ uint16 StatusInfo
+ uint32 LastErrorCode
+ string ErrorDescription
+ boolean ErrorCleared
+ string OtherIdentifyingInfo[]
+ uint64 PowerOnHours
+ uint64 TotalPowerOnHours
+ string IdentifyingDescriptions[]
+ uint16 AdditionalAvailability[]
+ uint64 MaxQuiesceTime
+ uint32 CurrentBitsPerPixel
+ uint32 CurrentHorizontalResolution
+ uint32 CurrentVerticalResolution
+ uint32 MaxRefreshRate
+ uint32 MinRefreshRate
+ uint32 CurrentRefreshRate
+ uint16 CurrentScanMode
+ string OtherCurrentScanMode
+ uint32 CurrentNumberOfRows
+ uint32 CurrentNumberOfColumns
+ uint64 CurrentNumberOfColors
+end
--
2.30.0