[Spice-devel] [PATCH win-vdagent v4 2/2] Implementing multiple monitors and arbitrary resolution

Sameeh Jubran sameeh at daynix.com
Sun Aug 7 15:07:51 UTC 2016


The Direct3D 9 API operates on either the Windows XP display driver
model (XPDM) or the Windows Vista display driver model (WDDM), depending
on the operating system installed.

This patch implements the WDDM interface while using the CCD API to do
so. Moreover it introduces multiple monitors support and arbitrary resolution
for Windows 10 while preserving backward compatiblity with previous versions
of Windows.

Based on a patch by Sandy Stutsman <sstutsma at redhat.com>

Signed-off-by: Dmitry Fleytman <dfleytma at redhat.com>
Signed-off-by: Sameeh Jubran <sameeh at daynix.com>
---
 vdagent/desktop_layout.cpp        | 148 +++--------
 vdagent/desktop_layout.h          |   9 +-
 vdagent/display_configuration.cpp | 526 ++++++++++++++++++++++++++++++++++++++
 vdagent/display_configuration.h   |  83 ++++++
 4 files changed, 644 insertions(+), 122 deletions(-)

diff --git a/vdagent/desktop_layout.cpp b/vdagent/desktop_layout.cpp
index a7666ca..9c3e873 100644
--- a/vdagent/desktop_layout.cpp
+++ b/vdagent/desktop_layout.cpp
@@ -18,6 +18,7 @@
 #include <spice/qxl_windows.h>
 #include <spice/qxl_dev.h>
 #include "desktop_layout.h"
+#include "display_configuration.h"
 #include "vdlog.h"
 
 #ifdef __MINGW32__
@@ -35,15 +36,17 @@ void DisplayMode::set_res(DWORD width, DWORD height, DWORD depth)
 DesktopLayout::DesktopLayout()
     : _total_width (0)
     , _total_height (0)
-    , _send_monitors_position(false)
+    , _display_config (NULL)
 {
     MUTEX_INIT(_mutex);
+    _display_config = DisplayConfig::create_config();
     get_displays();
 }
 
 DesktopLayout::~DesktopLayout()
 {
     clean_displays();
+    delete _display_config;
 }
 
 void DesktopLayout::get_displays()
@@ -59,6 +62,7 @@ void DesktopLayout::get_displays()
         unlock();
         return;
     }
+    _display_config->update_config_path();
     clean_displays();
     ZeroMemory(&dev_info, sizeof(dev_info));
     dev_info.cb = sizeof(dev_info);
@@ -82,12 +86,13 @@ void DesktopLayout::get_displays()
                 _displays[i] = NULL;
             }
         }
-        attached = !!(dev_info.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP);
+        attached = _display_config->is_attached(&dev_info);
+
         EnumDisplaySettings(dev_info.DeviceName, ENUM_CURRENT_SETTINGS, &mode);
         _displays[display_id] = new DisplayMode(mode.dmPosition.x, mode.dmPosition.y,
                                                 mode.dmPelsWidth, mode.dmPelsHeight,
                                                 mode.dmBitsPerPel, attached);
-        update_monitor_config(dev_info.DeviceName, _displays[display_id]);
+        _display_config->update_monitor_config(dev_info.DeviceName, _displays[display_id], &mode);
     }
     normalize_displays_pos();
     unlock();
@@ -121,6 +126,7 @@ void DesktopLayout::set_displays()
         unlock();
         return;
     }
+    _display_config->update_config_path();
     ZeroMemory(&dev_info, sizeof(dev_info));
     dev_info.cb = sizeof(dev_info);
     ZeroMemory(&dev_mode, sizeof(dev_mode));
@@ -146,28 +152,32 @@ void DesktopLayout::set_displays()
             break;
         }
         DisplayMode * mode(_displays.at(display_id));
-        if (!init_dev_mode(dev_info.DeviceName, &dev_mode, mode, normal_x, normal_y, true)) {
+        if (!init_dev_mode(dev_info.DeviceName, &dev_mode, mode)) {
             vd_printf("No suitable mode found for display %S", dev_info.DeviceName);
             break;
         }
         vd_printf("Set display mode %lux%lu", dev_mode.dmPelsWidth, dev_mode.dmPelsHeight);
-        LONG ret = ChangeDisplaySettingsEx(dev_info.DeviceName, &dev_mode, NULL,
-                                           CDS_UPDATEREGISTRY | CDS_NORESET, NULL);
-        if (ret == DISP_CHANGE_SUCCESSFUL) {
+        if (_display_config->update_dev_mode_position(dev_info.DeviceName, &dev_mode,
+                                                     mode->_pos_x - normal_x,
+                                                     mode->_pos_y - normal_y)) {
             dev_sets++;
-            update_monitor_config(dev_info.DeviceName, mode);
+            _display_config->update_monitor_config(dev_info.DeviceName, mode, &dev_mode);
         }
         if (!is_qxl) {
             display_id++;
         }
     }
     if (dev_sets) {
-        ChangeDisplaySettingsEx(NULL, NULL, NULL, 0, NULL);
+        _display_config->update_display_settings();
         normalize_displays_pos();
     }
     unlock();
 }
 
+void DesktopLayout::set_position_configurable(bool flag) {
+    _display_config->set_monitors_config(flag);
+}
+
 // Normalize all display positions to non-negative coordinates and update total width and height of
 // the virtual desktop. Caller is responsible to lock() & unlock().
 void DesktopLayout::normalize_displays_pos()
@@ -265,125 +275,29 @@ bool DesktopLayout::get_qxl_device_id(WCHAR* device_key, DWORD* device_id)
     return key_found;
 }
 
-bool DesktopLayout::init_dev_mode(LPCTSTR dev_name, DEVMODE* dev_mode, DisplayMode* mode,
-                                  LONG normal_x, LONG normal_y, bool set_pos)
+bool DesktopLayout::init_dev_mode(LPCTSTR dev_name, DEVMODE* dev_mode, DisplayMode* mode)
 {
-    DWORD closest_diff = -1;
-    DWORD best = -1;
-    QXLEscapeSetCustomDisplay custom;
-    HDC hdc = NULL;
-    LONG ret;
-
     ZeroMemory(dev_mode, sizeof(DEVMODE));
     dev_mode->dmSize = sizeof(DEVMODE);
-    if (!mode || !mode->_attached) {
-        //Detach monitor
-        dev_mode->dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_POSITION;
-        return true;
-    }
-
-    hdc = CreateDC(dev_name, NULL, NULL, NULL);
-    if (!hdc) {
-        // for some reason, windows want those 3 flags to enable monitor
-        dev_mode->dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_POSITION;
-        dev_mode->dmPelsWidth = mode->_width;
-        dev_mode->dmPelsHeight = mode->_height;
-        ret = ChangeDisplaySettingsEx(dev_name, dev_mode, NULL, CDS_UPDATEREGISTRY, NULL);
-        if (ret == DISP_CHANGE_BADMODE) {
-            // custom resolution might not be set yet, use known resolution
-            // FIXME: this causes client temporary resize... a
-            // solution would involve passing custom resolution before
-            // driver initialization, perhaps through registry
-            dev_mode->dmPelsWidth = 640;
-            dev_mode->dmPelsHeight = 480;
-            ret = ChangeDisplaySettingsEx(dev_name, dev_mode, NULL, CDS_UPDATEREGISTRY, NULL);
-        }
 
-        vd_printf("attach %ld", ret);
-        hdc = CreateDC(dev_name, NULL, NULL, NULL);
+    //Update monitor state
+    MONITOR_STATE monitor_state = (!mode || !mode->_attached)? MONITOR_DETACHED : MONITOR_ATTACHED;
+    _display_config->set_monitor_state(dev_name, dev_mode, monitor_state);
+    if (monitor_state == MONITOR_DETACHED) {
+        return true;
     }
 
-    if (!hdc) {
-        vd_printf("failed to create DC");
+    // Update custom resolution
+    dev_mode->dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_POSITION;
+    dev_mode->dmPelsWidth = mode->_width;
+    dev_mode->dmPelsHeight = mode->_height;
+    dev_mode->dmBitsPerPel = mode->_depth;
+    if (!_display_config->custom_display_escape(dev_name, dev_mode))
         return false;
-    } else {
-        // Update custom resolution
-        custom.xres = mode->_width;
-        custom.yres = mode->_height;
-        custom.bpp = mode->_depth;
-
-        int err = ExtEscape(hdc, QXL_ESCAPE_SET_CUSTOM_DISPLAY,
-                            sizeof(QXLEscapeSetCustomDisplay), (LPCSTR)&custom, 0, NULL);
-        if (err <= 0) {
-            vd_printf("can't set custom display, perhaps an old driver");
-        }
-        DeleteDC(hdc);
-    }
-
-    // force refresh mode table
-    DEVMODE tempDevMode;
-    ZeroMemory(&tempDevMode, sizeof (tempDevMode));
-    tempDevMode.dmSize = sizeof(DEVMODE);
-    EnumDisplaySettings(dev_name, 0xffffff, &tempDevMode);
-
-    //Find the closest size which will fit within the monitor
-    for (DWORD i = 0; EnumDisplaySettings(dev_name, i, dev_mode); i++) {
-        if (dev_mode->dmPelsWidth > mode->_width ||
-            dev_mode->dmPelsHeight > mode->_height ||
-            dev_mode->dmBitsPerPel != mode->_depth) {
-            continue;
-        }
-        DWORD wdiff = mode->_width - dev_mode->dmPelsWidth;
-        DWORD hdiff = mode->_height - dev_mode->dmPelsHeight;
-        DWORD diff = wdiff * wdiff + hdiff * hdiff;
-        if (diff < closest_diff) {
-            closest_diff = diff;
-            best = i;
-        }
-    }
-    if (best == (DWORD)-1 || !EnumDisplaySettings(dev_name, best, dev_mode)) {
-        return false;
-    }
-    dev_mode->dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
-    if (set_pos) {
-        //Convert the position so that the primary is always at (0,0)
-        dev_mode->dmPosition.x = mode->_pos_x - normal_x;
-        dev_mode->dmPosition.y = mode->_pos_y - normal_y;
-        dev_mode->dmFields |= DM_POSITION;
-    }
 
     // update current DisplayMode (so mouse scaling works properly)
     mode->_width = dev_mode->dmPelsWidth;
     mode->_height = dev_mode->dmPelsHeight;
-
     return true;
-}
-
-bool DesktopLayout::update_monitor_config(LPCTSTR dev_name, DisplayMode* mode)
-{
-    QXLHead monitor_config;
-
-    if (!mode || !mode->get_attached())
-        return false;
-
-    //Don't configure monitors unless the client supports it
-    if(!_send_monitors_position) return FALSE;
-
-    HDC hdc = CreateDC(dev_name, NULL, NULL, NULL);
-
-    memset(&monitor_config, 0, sizeof(monitor_config));
-    monitor_config.x = mode->_pos_x;
-    monitor_config.y = mode->_pos_y;
-    monitor_config.width = mode->_width;
-    monitor_config.height = mode->_height;
-
-    int err = ExtEscape(hdc, QXL_ESCAPE_MONITOR_CONFIG,
-        sizeof(QXLHead), (LPCSTR) &monitor_config, 0, NULL);
-
-    if (err < 0){
-        vd_printf("can't update monitor config, may have an older driver");
-    }
 
-    DeleteDC(hdc);
-    return (err >= 0);
 }
diff --git a/vdagent/desktop_layout.h b/vdagent/desktop_layout.h
index ece3946..fd6af76 100644
--- a/vdagent/desktop_layout.h
+++ b/vdagent/desktop_layout.h
@@ -60,6 +60,7 @@ private:
 };
 
 typedef std::vector<DisplayMode*> Displays;
+class DisplayConfig;
 
 class DesktopLayout {
 public:
@@ -73,23 +74,21 @@ public:
     size_t get_display_count() { return _displays.size();}
     DWORD get_total_width() { return _total_width;}
     DWORD get_total_height() { return _total_height;}
-    void set_position_configurable(bool flag) { _send_monitors_position = flag; }
+    void set_position_configurable(bool flag);
 private:
     void clean_displays();
     void normalize_displays_pos();
     DisplayMode * get_primary_display();
-    bool update_monitor_config(LPCTSTR dev_name, DisplayMode* mode);
+    bool init_dev_mode(LPCTSTR dev_name, DEVMODE* dev_mode, DisplayMode* mode);
     static bool consistent_displays();
     static bool is_attached(LPCTSTR dev_name);
     static bool get_qxl_device_id(WCHAR* device_key, DWORD* device_id);
-    static bool init_dev_mode(LPCTSTR dev_name, DEVMODE* dev_mode, DisplayMode* mode,
-                              LONG normal_x, LONG normal_y, bool set_pos);
 private:
     mutex_t _mutex;
     Displays _displays;
     DWORD _total_width;
     DWORD _total_height;
-    bool _send_monitors_position;
+    DisplayConfig* _display_config;
 };
 
 #endif
diff --git a/vdagent/display_configuration.cpp b/vdagent/display_configuration.cpp
index a7aa0d7..837ae86 100755
--- a/vdagent/display_configuration.cpp
+++ b/vdagent/display_configuration.cpp
@@ -152,6 +152,532 @@ struct DISPLAYCONFIG_PATH_INFO {
     UINT32 flags;
 };
 
+
+enum D3DKMT_ESCAPETYPE {
+    D3DKMT_ESCAPE_DRIVERPRIVATE = 0
+};
+
+struct D3DDDI_ESCAPEFLAGS {
+    union {
+        struct {
+            UINT    Reserved : 31;
+        };
+        UINT        Value;
+    };
+};
+
+struct D3DKMT_ESCAPE {
+    D3D_HANDLE hAdapter;
+    D3D_HANDLE hDevice;
+    D3DKMT_ESCAPETYPE Type;
+    D3DDDI_ESCAPEFLAGS Flags;
+    VOID* pPrivateDriverData;
+    UINT PrivateDriverDataSize;
+    D3D_HANDLE hContext;
+};
+
+struct D3DKMT_OPENADAPTERFROMHDC {
+    HDC hDc;
+    D3D_HANDLE hAdapter;
+    LUID AdapterLuid;
+    UINT VidPnSourceId;
+};
+
+struct D3DKMT_CLOSEADAPTER {
+    D3D_HANDLE hAdapter;
+};
+
+struct D3DKMT_OPENADAPTERFROMDEVICENAME {
+    const WCHAR *pDeviceName;
+    D3D_HANDLE hAdapter;
+    LUID AdapterLuid;
+};
+
+struct D3DKMT_OPENADAPTERFROMGDIDISPLAYNAME {
+    WCHAR DeviceName[32];
+    D3D_HANDLE hAdapter;
+    LUID AdapterLuid;
+    UINT VidPnSourceId;
+};
+
+struct QXLMonitorEscape {
+    QXLMonitorEscape(DEVMODE* dev_mode)
+    {
+        ZeroMemory(&_head, sizeof(_head));
+        _head.x = dev_mode->dmPosition.x;
+        _head.y = dev_mode->dmPosition.y;
+        _head.width = dev_mode->dmPelsWidth;
+        _head.height = dev_mode->dmPelsHeight;
+    }
+    QXLHead _head;
+};
+
+struct QxlCustomEscapeObj : public QXLEscapeSetCustomDisplay {
+    QxlCustomEscapeObj(uint32_t bitsPerPel, uint32_t width, uint32_t height)
+    {
+        xres = width;
+        yres = height;
+        bpp = bitsPerPel;
+    }
+    QxlCustomEscapeObj() {};
+};
+
+struct WDDMCustomDisplayEscape {
+    WDDMCustomDisplayEscape(DEVMODE* dev_mode)
+    {
+        _ioctl = QXL_ESCAPE_SET_CUSTOM_DISPLAY;
+        _custom.bpp  = dev_mode->dmBitsPerPel;
+        _custom.xres = dev_mode->dmPelsWidth;
+        _custom.yres = dev_mode->dmPelsHeight;
+    }
+    int                         _ioctl;
+    QXLEscapeSetCustomDisplay   _custom;
+};
+
+struct WDDMMonitorConfigEscape {
+    WDDMMonitorConfigEscape(DisplayMode* mode)
+    {
+        _ioctl = QXL_ESCAPE_MONITOR_CONFIG;
+        _head.id = _head.surface_id = 0;
+        _head.x = mode->get_pos_x();
+        _head.y = mode->get_pos_y();
+        _head.width = mode->get_width();
+        _head.height = mode->get_height();
+    }
+    int         _ioctl;
+    QXLHead     _head;
+};
+
+DisplayConfig* DisplayConfig::create_config()
+{
+    DisplayConfig* new_interface;
+    /* Try to open a WDDM adapter.
+    If that failed, assume we have an XPDM driver */
+    try {
+        new_interface = new  WDDMInterface();
+    } catch (std::exception& e) {
+        new_interface = new XPDMInterface();
+    }
+    return new_interface;
+}
+
+DisplayConfig::DisplayConfig()
+    : _send_monitors_config(false)
+{}
+
+bool XPDMInterface::is_attached(DISPLAY_DEVICE* dev_info)
+{
+    return !!(dev_info->StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP);
+}
+
+bool XPDMInterface::set_monitor_state(LPCTSTR device_name, DEVMODE* dev_mode, MONITOR_STATE state)
+{
+    dev_mode->dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_POSITION;
+    if (state == MONITOR_ATTACHED) {
+        return true;
+    }
+
+    LONG status = ChangeDisplaySettingsEx(device_name, dev_mode, NULL, CDS_UPDATEREGISTRY, NULL);
+    return (status == DISP_CHANGE_SUCCESSFUL);
+}
+
+LONG XPDMInterface::update_display_settings()
+{
+    return ChangeDisplaySettingsEx(NULL, NULL, NULL, 0, NULL);
+}
+
+bool XPDMInterface::update_dev_mode_position(LPCTSTR device_name,
+                                             DEVMODE* dev_mode, LONG x, LONG y)
+{
+    //Convert the position so that the primary is always at (0,0)
+    dev_mode->dmPosition.x = x;
+    dev_mode->dmPosition.y = y;
+    dev_mode->dmFields |= DM_POSITION;
+    vd_printf("%s: setting %S at (%lu, %lu)", __FUNCTION__, device_name, dev_mode->dmPosition.x,
+        dev_mode->dmPosition.y);
+
+    LONG status = ChangeDisplaySettingsEx(device_name, dev_mode, NULL,
+                                          CDS_UPDATEREGISTRY | CDS_NORESET, NULL);
+    return (status == DISP_CHANGE_SUCCESSFUL);
+}
+
+bool XPDMInterface::custom_display_escape(LPCTSTR device_name, DEVMODE* dev_mode)
+{
+    LONG        ret;
+    NTSTATUS    Status (ERROR_SUCCESS);
+    HDC         hdc = CreateDC(device_name, NULL, NULL, NULL);
+
+    if (!hdc) {
+        ret = ChangeDisplaySettingsEx(device_name, dev_mode, NULL, CDS_UPDATEREGISTRY, NULL);
+        if (ret == DISP_CHANGE_BADMODE) {
+            // custom resolution might not be set yet, use known resolution
+            // FIXME: this causes client temporary resize... a
+            // solution would involve passing custom resolution before
+            // driver initialization, perhaps through registry
+            dev_mode->dmPelsWidth = 640;
+            dev_mode->dmPelsHeight = 480;
+            ret = ChangeDisplaySettingsEx(device_name, dev_mode, NULL, CDS_UPDATEREGISTRY, NULL);
+        }
+
+        vd_printf("attach %ld", ret);
+        if (!(hdc = CreateDC(device_name, NULL, NULL, NULL))) {
+            vd_printf("%s: failed to create DC", __FUNCTION__);
+            return false;
+        }
+    }
+
+    QxlCustomEscapeObj custom_escape(dev_mode->dmBitsPerPel,
+                                            dev_mode->dmPelsWidth, dev_mode->dmPelsHeight);
+
+    int err = ExtEscape(hdc, QXL_ESCAPE_SET_CUSTOM_DISPLAY,
+              sizeof(QXLEscapeSetCustomDisplay), (LPCSTR) &custom_escape, 0, NULL);
+    if (err <= 0) {
+        vd_printf("%s: Can't set custom display, perhaps running with an older driver?",
+            __FUNCTION__);
+    }
+
+    if (!find_best_mode(device_name, dev_mode)) {
+        Status = E_FAIL;
+    }
+
+    DeleteDC(hdc);
+    return NT_SUCCESS(Status);
+}
+
+bool XPDMInterface::update_monitor_config(LPCTSTR device_name, DisplayMode* mode,
+                                           DEVMODE* dev_mode)
+{
+    if (!mode || !mode->get_attached()) {
+        return false;
+    }
+
+    QXLMonitorEscape monitor_config(dev_mode);
+    HDC hdc(CreateDC(device_name, NULL, NULL, NULL));
+    int err(0);
+
+    if (!hdc || !_send_monitors_config) {
+        return false;
+    }
+
+    err = ExtEscape(hdc, QXL_ESCAPE_MONITOR_CONFIG, sizeof(QXLHead),
+                    (LPCSTR) &monitor_config, 0, NULL);
+    if (err < 0) {
+        vd_printf("%s: %S can't update monitor config, may have old, old driver",
+                 __FUNCTION__, device_name);
+    }
+    DeleteDC(hdc);
+    return (err >= 0);
+}
+
+bool XPDMInterface::find_best_mode(LPCTSTR Device, DEVMODE* dev_mode)
+{
+    DWORD closest_diff = -1;
+    DWORD best = -1;
+
+    // force refresh mode table
+    DEVMODE test_dev_mode;
+    ZeroMemory(&test_dev_mode, sizeof(test_dev_mode));
+    test_dev_mode.dmSize = sizeof(DEVMODE);
+    EnumDisplaySettings(Device, 0xffffff, &test_dev_mode);
+
+    //Find the closest size which will fit within the monitor
+    for (DWORD i = 0; EnumDisplaySettings(Device, i, &test_dev_mode); i++) {
+        if (dev_mode->dmPelsWidth > test_dev_mode.dmPelsWidth ||
+            dev_mode->dmPelsHeight > test_dev_mode.dmPelsHeight ||
+            dev_mode->dmBitsPerPel != test_dev_mode.dmBitsPerPel) {
+            continue;
+        }
+        DWORD wdiff = dev_mode->dmPelsWidth - test_dev_mode.dmPelsWidth;
+        DWORD hdiff = dev_mode->dmPelsHeight - test_dev_mode.dmPelsHeight;
+        DWORD diff = wdiff * wdiff + hdiff * hdiff;
+        if (diff < closest_diff) {
+            closest_diff = diff;
+            best = i;
+        }
+    }
+    vd_printf("%s: closest_diff at %lu best %lu", __FUNCTION__, closest_diff, best);
+    if (best == (DWORD) -1 || !EnumDisplaySettings(Device, best, dev_mode)) {
+        return false;
+    }
+
+    //Change to the best fit
+    LONG status = ChangeDisplaySettingsEx(Device, dev_mode, NULL,
+                                          CDS_UPDATEREGISTRY | CDS_NORESET, NULL);
+    return NT_SUCCESS(status);
+}
+
+WDDMInterface::WDDMInterface()
+    : _pfnOpen_adapter_hdc(NULL)
+    , _pfnClose_adapter(NULL)
+    , _pfnEscape(NULL)
+    , _pfnOpen_adapter_device_name(NULL)
+    , _pfnOpen_adapter_gdi_name(NULL)
+{
+    LONG error(0);
+    //Can we find the D3D calls we need?
+    if (!init_d3d_api()) {
+        throw std::exception();
+    }
+
+    //Initialize  CCD path stuff
+    if (!_ccd.query_display_config()) {
+        throw std::exception();
+    }
+
+    if (!_ccd.set_display_config(error)) {
+        throw std::exception();
+    }
+}
+
+bool WDDMInterface::is_attached(DISPLAY_DEVICE* dev_info)
+{
+    return _ccd.is_attached(dev_info->DeviceName);
+}
+
+bool WDDMInterface::set_monitor_state(LPCTSTR device_name, DEVMODE* dev_mode, MONITOR_STATE state)
+{
+   return  _ccd.set_path_state(device_name, state);
+}
+
+bool WDDMInterface::custom_display_escape(LPCTSTR device_name, DEVMODE* dev_mode)
+{
+    DISPLAYCONFIG_MODE_INFO* mode = _ccd.get_active_mode(device_name, false);
+    if (!mode) {
+        return false;
+    }
+
+    //Don't bother if we are already set to the new resolution
+    if (mode->sourceMode.width == dev_mode->dmPelsWidth &&
+        mode->sourceMode.height == dev_mode->dmPelsHeight) {
+        return true;
+    }
+
+    vd_printf("%s: updating %S resolution\n", __FUNCTION__, device_name);
+
+    WDDMCustomDisplayEscape wddm_escape(dev_mode);
+    if (escape(device_name, &wddm_escape, sizeof(wddm_escape))) {
+        return _ccd.update_mode_size(device_name, dev_mode);
+    }
+
+    vd_printf("%s: (%dx%d)", __FUNCTION__, mode->sourceMode.width, mode->sourceMode.height);
+    return false;
+}
+
+bool WDDMInterface::update_monitor_config(LPCTSTR device_name, DisplayMode* display_mode,
+                                           DEVMODE* dev_mode)
+{
+    if (!display_mode || !display_mode->get_attached()) {
+        return false;
+    }
+    DISPLAYCONFIG_MODE_INFO* mode = _ccd.get_active_mode(device_name, false);
+    if (!mode || !_send_monitors_config)
+        return false;
+
+    WDDMMonitorConfigEscape wddm_escape(display_mode);
+    if (escape(device_name, &wddm_escape, sizeof(wddm_escape))) {
+        //Update the path position
+        return _ccd.update_mode_position(device_name, dev_mode);
+    }
+
+    vd_printf("%s: %S failed", __FUNCTION__, device_name);
+    return false;
+
+}
+
+LONG WDDMInterface::update_display_settings()
+{
+    LONG error(0);
+    //If we removed the primary monitor since the last call, we need to
+    //reorder the other monitors, making the leftmost one the primary
+    _ccd.verify_primary_position();
+    _ccd.set_display_config(error);
+    return error;
+}
+
+void WDDMInterface::update_config_path()
+{
+    _ccd.query_display_config();
+}
+
+bool WDDMInterface::update_dev_mode_position(LPCTSTR device_name, DEVMODE* dev_mode,
+                                             LONG x, LONG y)
+{
+    //Convert the position so that the primary is always at (0,0)
+    dev_mode->dmPosition.x = x;
+    dev_mode->dmPosition.y = y;
+    return _ccd.update_mode_position(device_name, dev_mode);
+}
+
+bool WDDMInterface::init_d3d_api()
+{
+    HMODULE hModule = LoadLibrary(L"gdi32.dll");
+
+    //Look for the gdi32 functions we need to perform driver escapes
+    if (!hModule) {
+        vd_printf("%s something wildly wrong as we can't open gdi32.dll", __FUNCTION__);
+        return false;
+    }
+
+    do {
+        _pfnClose_adapter = (PFND3DKMT_CLOSEADAPTER)
+            GetProcAddress(hModule, "D3DKMTCloseAdapter");
+        if (!_pfnClose_adapter) {
+            break;
+        }
+
+        _pfnEscape = (PFND3DKMT_ESCAPE) GetProcAddress(hModule, "D3DKMTEscape");
+        if (!_pfnEscape) {
+            break;
+        }
+
+        _pfnOpen_adapter_hdc = (PFND3DKMT_OPENADAPTERFROMHDC)
+            GetProcAddress(hModule, "D3DKMTOpenAdapterFromHdc");
+        if (!_pfnOpen_adapter_hdc) {
+            break;
+        }
+
+        _pfnOpen_adapter_device_name = (PFND3DKMT_OPENADAPTERFROMDEVICENAME)
+            GetProcAddress(hModule, "D3DKMTOpenAdapterFromDeviceName");
+        if (!_pfnOpen_adapter_device_name) {
+            break;
+        }
+
+        _pfnOpen_adapter_gdi_name = (PFND3DKMT_OPENADAPTERFROMGDIDISPLAYNAME)
+            GetProcAddress(hModule, "D3DKMTOpenAdapterFromGdiDisplayName");
+        if (!_pfnOpen_adapter_gdi_name) {
+            break;
+        }
+
+    }
+    while(0);
+
+    FreeLibrary(hModule);
+
+    //Did we get them ?
+    if (!_pfnClose_adapter || !_pfnOpen_adapter_hdc || !_pfnEscape)  {
+        return false;
+    }
+    return true;
+}
+
+D3D_HANDLE WDDMInterface::adapter_handle(LPCTSTR device_name)
+{
+    D3D_HANDLE hAdapter(0);
+
+    //For some reason, unknown to me, this call will occasionally fail.
+    if ((hAdapter = handle_from_DC(device_name))) {
+        return hAdapter;
+    }
+        //So try other available methods.
+    if (_pfnOpen_adapter_device_name && (hAdapter = handle_from_device_name(device_name))) {
+        return hAdapter;
+    }
+    //One last chance to open this guy
+    if (_pfnOpen_adapter_gdi_name) {
+        hAdapter = handle_from_GDI_name(device_name);
+    }
+
+    if (!hAdapter) {
+        vd_printf("%s: failed to open adapter %S", __FUNCTION__, device_name);
+    }
+
+    return hAdapter;
+}
+
+D3D_HANDLE WDDMInterface::handle_from_DC(LPCTSTR adapter_name)
+{
+    NTSTATUS status;
+    D3DKMT_OPENADAPTERFROMHDC open_data;
+    HDC hDc(CreateDC(adapter_name, NULL, NULL, NULL));
+
+    if (!hDc) {
+        vd_printf("%s: %S CreateDC failed with %lu", __FUNCTION__, adapter_name, GetLastError());
+        return 0;
+    }
+
+    ZeroMemory(&open_data, sizeof(D3DKMT_OPENADAPTERFROMHDC));
+    open_data.hDc = hDc;
+
+    if (!NT_SUCCESS(status = _pfnOpen_adapter_hdc(&open_data))) {
+        vd_printf("%s: %S open adapter from hdc failed with %lu", __FUNCTION__, adapter_name,
+            status);
+        open_data.hAdapter = 0;
+    }
+
+    DeleteDC(hDc);
+    return open_data.hAdapter;
+}
+
+D3D_HANDLE WDDMInterface::handle_from_device_name(LPCTSTR adapter_name)
+{
+    D3DKMT_OPENADAPTERFROMDEVICENAME display_name_data;
+    NTSTATUS  status;
+
+    ZeroMemory(&display_name_data, sizeof(display_name_data));
+    display_name_data.pDeviceName = adapter_name;
+
+    if (NT_SUCCESS(status = _pfnOpen_adapter_device_name(&display_name_data))) {
+        return display_name_data.hAdapter;
+    }
+
+    vd_printf("%s %S failed with 0x%lx", __FUNCTION__, adapter_name, status);
+    return 0;
+}
+
+D3D_HANDLE WDDMInterface::handle_from_GDI_name(LPCTSTR adapter_name)
+{
+    D3DKMT_OPENADAPTERFROMGDIDISPLAYNAME gdi_display_name;
+    NTSTATUS status;
+
+    ZeroMemory(&gdi_display_name, sizeof(gdi_display_name));
+    memcpy((void *) gdi_display_name.DeviceName, adapter_name, sizeof(TCHAR)* CCHDEVICENAME);
+
+    if (NT_SUCCESS(status = _pfnOpen_adapter_gdi_name(&gdi_display_name))) {
+        return  gdi_display_name.hAdapter;
+    }
+
+    vd_printf("%s: %S aurrrgghh nothing works..error  is 0x%lx", __FUNCTION__, adapter_name,
+            status);
+    return 0;
+}
+
+void WDDMInterface::close_adapter(D3D_HANDLE handle)
+{
+    D3DKMT_CLOSEADAPTER closeData;
+    if (handle) {
+        closeData.hAdapter = handle;
+        _pfnClose_adapter(&closeData);
+    }
+}
+
+bool WDDMInterface::escape(LPCTSTR device_name, void* data, UINT size_data)
+{
+    D3DKMT_ESCAPE   escapeData;
+    NTSTATUS        status;
+    D3D_HANDLE   hAdapter(0);
+
+    if (!(hAdapter = adapter_handle(device_name)))
+        return false;
+
+    escapeData.hAdapter = hAdapter;
+    escapeData.hDevice = 0;
+    escapeData.hContext = 0;
+    escapeData.Type = D3DKMT_ESCAPE_DRIVERPRIVATE;
+    escapeData.Flags.Value = 0;
+    escapeData.pPrivateDriverData = data;
+    escapeData.PrivateDriverDataSize = size_data;
+
+    status = _pfnEscape(&escapeData);
+
+    if (!NT_SUCCESS(status)) {
+        vd_printf("%s: this should never happen. Status is 0x%lx", __FUNCTION__, status);
+    }
+
+    //Close the handle to this device
+    close_adapter(hAdapter);
+    return NT_SUCCESS(status);
+}
+
 CCD::CCD()
     :_numPathElements(0)
     ,_numModeElements(0)
diff --git a/vdagent/display_configuration.h b/vdagent/display_configuration.h
index 94e52e4..e5ee90d 100755
--- a/vdagent/display_configuration.h
+++ b/vdagent/display_configuration.h
@@ -89,4 +89,87 @@ private:
     PATH_STATE _path_state;
 };
 
+class DisplayMode;
+
+//Class provides interface to get/set display configurations
+class DisplayConfig {
+public:
+    static DisplayConfig* create_config();
+    DisplayConfig();
+    virtual ~DisplayConfig() {};
+    virtual bool is_attached(DISPLAY_DEVICE* dev_info) = 0;
+    virtual bool custom_display_escape(LPCTSTR device, DEVMODE* mode) = 0;
+    virtual bool update_monitor_config(LPCTSTR device, DisplayMode* mode, DEVMODE* dev_mode) = 0;
+    virtual bool set_monitor_state(LPCTSTR device_name, DEVMODE* dev_mode, MONITOR_STATE state) = 0;
+    virtual LONG update_display_settings() = 0;
+    virtual bool update_dev_mode_position(LPCTSTR dev_name, DEVMODE* dev_mode, LONG x, LONG y) = 0;
+    void set_monitors_config(bool flag) { _send_monitors_config = flag; }
+    virtual void update_config_path() {};
+
+protected:
+    bool _send_monitors_config;
+};
+
+//DisplayConfig implementation for guest with XPDM graphics drivers
+class XPDMInterface : public DisplayConfig {
+public:
+    XPDMInterface() :DisplayConfig() {};
+    bool is_attached(DISPLAY_DEVICE* dev_info);
+    bool custom_display_escape(LPCTSTR device_name, DEVMODE* dev_mode);
+    bool update_monitor_config(LPCTSTR device_name, DisplayMode* mode, DEVMODE* dev_mode);
+    bool set_monitor_state(LPCTSTR device_name, DEVMODE* dev_mode, MONITOR_STATE state);
+    LONG update_display_settings();
+    bool update_dev_mode_position(LPCTSTR device_name, DEVMODE * dev_mode, LONG x, LONG y);
+
+private:
+    bool find_best_mode(LPCTSTR Device, DEVMODE* dev_mode);
+};
+
+//DisplayConfig implementation for guest with WDDM graphics drivers
+typedef UINT D3D_HANDLE;
+
+struct D3DKMT_ESCAPE;
+struct D3DKMT_OPENADAPTERFROMGDIDISPLAYNAME;
+struct D3DKMT_OPENADAPTERFROMDEVICENAME;
+struct D3DKMT_CLOSEADAPTER;
+struct D3DKMT_OPENADAPTERFROMHDC;
+
+typedef NTSTATUS(APIENTRY* PFND3DKMT_ESCAPE)(CONST D3DKMT_ESCAPE*);
+typedef NTSTATUS(APIENTRY* PFND3DKMT_OPENADAPTERFROMGDIDISPLAYNAME)(D3DKMT_OPENADAPTERFROMGDIDISPLAYNAME*);
+typedef NTSTATUS(APIENTRY* PFND3DKMT_OPENADAPTERFROMDEVICENAME)(D3DKMT_OPENADAPTERFROMDEVICENAME*);
+typedef NTSTATUS(APIENTRY* PFND3DKMT_CLOSEADAPTER)(D3DKMT_CLOSEADAPTER*);
+typedef NTSTATUS(APIENTRY* PFND3DKMT_OPENADAPTERFROMHDC)(D3DKMT_OPENADAPTERFROMHDC*);
+
+class WDDMInterface : public DisplayConfig {
+public:
+    WDDMInterface();
+    bool is_attached(DISPLAY_DEVICE* dev_info);
+    bool set_monitor_state(LPCTSTR device_name, DEVMODE* dev_mode, MONITOR_STATE state);
+    LONG update_display_settings();
+    bool custom_display_escape(LPCTSTR device_name, DEVMODE* dev_mode);
+    bool update_monitor_config(LPCTSTR device_name, DisplayMode* mode, DEVMODE* dev_mode);
+    bool update_dev_mode_position(LPCTSTR device_name, DEVMODE * dev_mode, LONG x, LONG y);
+    void update_config_path();
+
+private:
+    bool init_d3d_api();
+    D3D_HANDLE adapter_handle(LPCTSTR device_name);
+    D3D_HANDLE handle_from_DC(LPCTSTR adapter_name);
+    D3D_HANDLE handle_from_device_name(LPCTSTR adapter_name);
+    D3D_HANDLE handle_from_GDI_name(LPCTSTR adapter_name);
+
+    void close_adapter(D3D_HANDLE handle);
+    bool escape(LPCTSTR device_name, void* data, UINT sizeData);
+
+    //GDI Function pointers
+    PFND3DKMT_OPENADAPTERFROMHDC _pfnOpen_adapter_hdc;
+    PFND3DKMT_CLOSEADAPTER  _pfnClose_adapter;
+    PFND3DKMT_ESCAPE _pfnEscape;
+    PFND3DKMT_OPENADAPTERFROMDEVICENAME _pfnOpen_adapter_device_name;
+    PFND3DKMT_OPENADAPTERFROMGDIDISPLAYNAME _pfnOpen_adapter_gdi_name;
+
+    //object handles the CCD API
+    CCD _ccd;
+};
+
 #endif
\ No newline at end of file
-- 
2.5.5



More information about the Spice-devel mailing list