[Spice-devel] [RFC spice-vdagent_win] Add initial seamless mode support

Jakub Janků janku.jakub.jj at gmail.com
Fri Jul 7 09:57:40 UTC 2017


---
Demo: https://youtu.be/IX49z8VbD-c

VDAgent: https://github.com/jjanku/win32-vd_agent/tree/seamless-mode
Protocol: https://gitlab.com/xerus/spice-protocol/tree/seamless-mode
Gtk: https://github.com/jjanku/spice-gtk/tree/seamless-mode

This patch adds very basic implementation of seamless mode for Windows, that was partialy implemented for linux by Ondrej Holy and Lukas Venhoda earlier.

It's just a proof of concept as it's very buggy at the moment:
-occasional screen tearing (can be seen in the demo with MineSweeper, Gtk issue maybe?)
-tested just on Win7 & Win10
-EnumWindows doesn't work for Win10 ModernUI apps
 (possible fix: https://wj32.org/wp/2012/12/12/enumwindows-no-longer-finds-metromodern-ui-windows-a-workaround-2/)
-narrow black bar on top in Win10
-weird behaviour with multiple screens
-other...
---
 spice-protocol      |   2 +-
 vdagent/vdagent.cpp | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 149 insertions(+), 1 deletion(-)

diff --git a/spice-protocol b/spice-protocol
index 666b5c5..d52016e 160000
--- a/spice-protocol
+++ b/spice-protocol
@@ -1 +1 @@
-Subproject commit 666b5c5780acf3176a9cff61ad549d30bb1b9824
+Subproject commit d52016e727e48f6eb214becafa9103e6d4fb7f64
diff --git a/vdagent/vdagent.cpp b/vdagent/vdagent.cpp
index cd49755..4d445c0 100644
--- a/vdagent/vdagent.cpp
+++ b/vdagent/vdagent.cpp
@@ -28,6 +28,8 @@
 #include <queue>
 #include <set>
 #include <vector>
+#include <dwmapi.h>
+#include <versionhelpers.h>
 
 #define VD_AGENT_LOG_PATH       TEXT("%svdagent.log")
 #define VD_AGENT_WINCLASS_NAME  TEXT("VDAGENT")
@@ -64,6 +66,7 @@ typedef struct ALIGN_VC VDIChunk {
 #define VD_READ_BUF_SIZE       (sizeof(VDIChunk) + VD_AGENT_MAX_DATA_SIZE)
 
 typedef BOOL (WINAPI *PCLIPBOARD_OP)(HWND);
+typedef HRESULT (WINAPI *DWM_GET_PROC)(HWND hwnd, DWORD, PVOID, DWORD);
 
 class VDAgent {
 public:
@@ -92,6 +95,12 @@ private:
     DWORD get_buttons_change(DWORD last_buttons_state, DWORD new_buttons_state,
                              DWORD mask, DWORD down_flag, DWORD up_flag);
     static HGLOBAL utf8_alloc(LPCSTR data, int size);
+    void send_seamless_mode_list();
+    void set_seamless_mode(uint8_t enabled);
+    static void CALLBACK wnd_event_proc(HWINEVENTHOOK hWinEventHook, DWORD event,HWND hwnd,
+                                        LONG idObject, LONG idChild, DWORD dwEventThread,
+                                        DWORD dwmsEventTime);
+    static BOOL CALLBACK enum_wnd_proc(HWND hwnd, LPARAM lparam);
     static LRESULT CALLBACK wnd_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);
     static DWORD WINAPI event_thread_proc(LPVOID param);
     static VOID CALLBACK read_completion(DWORD err, DWORD bytes, LPOVERLAPPED overlapped);
@@ -168,6 +177,10 @@ private:
 
     std::set<uint32_t> _grab_types;
 
+    HWINEVENTHOOK _seamless_window_change_hooks[2];
+    HMODULE _dwm_lib;
+    DWM_GET_PROC _dwm_get_wnd_attr;
+
     VDLog* _log;
 };
 
@@ -339,6 +352,7 @@ bool VDAgent::run()
     }
     vd_printf("Agent stopped");
     CloseHandle(event_thread);
+    set_seamless_mode(FALSE);
     cleanup();
     return true;
 }
@@ -833,6 +847,7 @@ bool VDAgent::send_announce_capabilities(bool request)
     VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_SPARSE_MONITORS_CONFIG);
     VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_GUEST_LINEEND_CRLF);
     VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MAX_CLIPBOARD);
+    VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_SEAMLESS_MODE);
     vd_printf("Sending capabilities:");
     for (uint32_t i = 0 ; i < caps_size; ++i) {
         vd_printf("%X", caps->caps[i]);
@@ -1292,6 +1307,11 @@ void VDAgent::dispatch_message(VDAgentMessage* msg, uint32_t port)
     case VD_AGENT_MAX_CLIPBOARD:
         res = handle_max_clipboard((VDAgentMaxClipboard*)msg->data, msg->size);
         break;
+    case VD_AGENT_SEAMLESS_MODE: {
+        VDAgentSeamlessMode *seamless_msg = (VDAgentSeamlessMode*)msg->data;
+        set_seamless_mode(seamless_msg->enabled);
+        break;
+    }
     default:
         vd_printf("Unsupported message type %u size %u", msg->type, msg->size);
     }
@@ -1301,6 +1321,134 @@ void VDAgent::dispatch_message(VDAgentMessage* msg, uint32_t port)
     }
 }
 
+void VDAgent::set_seamless_mode(uint8_t enabled)
+{
+    if (enabled) {
+        if (IsWindows8OrGreater()) {
+            _dwm_lib = LoadLibrary(L"Dwmapi.dll");
+            if (_dwm_lib) {
+                _dwm_get_wnd_attr = (DWM_GET_PROC)GetProcAddress(_dwm_lib, "DwmGetWindowAttribute");
+                if (!_dwm_get_wnd_attr) {
+                    vd_printf("GetProcAddress for DwmGetWindowAttribute failed with error %lu", GetLastError());
+                }
+            } else {
+                vd_printf("Loading Dwmapi.dll failed with error %lu", GetLastError());
+            }
+        }
+
+        //TODO maybe the range of events is too large?
+        //TODO check that we don't create new hooks when the old ones haven't been removed yet
+        _seamless_window_change_hooks[0] = SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE,
+                                                           EVENT_OBJECT_LOCATIONCHANGE,
+                                                           NULL,
+                                                           wnd_event_proc,
+                                                           0, 0,
+                                                           WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
+        _seamless_window_change_hooks[1] = SetWinEventHook(EVENT_OBJECT_CREATE,
+                                                           EVENT_OBJECT_HIDE,
+                                                           NULL,
+                                                           wnd_event_proc,
+                                                           0, 0,
+                                                           WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
+    } else {
+        UnhookWinEvent(_seamless_window_change_hooks[0]);
+        UnhookWinEvent(_seamless_window_change_hooks[1]);
+        if (_dwm_lib) {
+            FreeLibrary(_dwm_lib);
+            _dwm_lib = NULL;
+            _dwm_get_wnd_attr = NULL;
+        }
+    }
+}
+
+void CALLBACK VDAgent::wnd_event_proc(HWINEVENTHOOK hWinEventHook, DWORD event, HWND hwnd,
+                                             LONG idObject, LONG idChild, DWORD dwEventThread,
+                                             DWORD dwmsEventTime)
+{
+    LONG_PTR style;
+
+    if (idObject != OBJID_WINDOW || hwnd == NULL)
+        return;
+
+    style = GetWindowLongPtr(hwnd, GWL_STYLE);
+    if (style & WS_CHILD)
+        return;
+
+    switch (event) {
+        case EVENT_OBJECT_LOCATIONCHANGE:
+        case EVENT_OBJECT_CREATE:
+        case EVENT_OBJECT_DESTROY:
+        case EVENT_OBJECT_HIDE:
+        case EVENT_OBJECT_SHOW:
+            _singleton->send_seamless_mode_list();
+            break;
+    }
+}
+
+void VDAgent::send_seamless_mode_list()
+{
+    std::queue<HWND> windows;
+    RECT rect;
+    VDAgentSeamlessModeList *list;
+    uint32_t size;
+
+    EnumWindows(enum_wnd_proc, reinterpret_cast<LPARAM>(&windows));
+
+    size = sizeof(VDAgentSeamlessModeList) +
+           windows.size() * sizeof(VDAgentSeamlessModeWindow);
+    list = (VDAgentSeamlessModeList*) malloc(size);
+    list->num_of_windows = 0;
+
+    while (!windows.empty()) {
+        if (_dwm_get_wnd_attr)
+            _dwm_get_wnd_attr(windows.front(), DWMWA_EXTENDED_FRAME_BOUNDS,
+                              &rect, sizeof(RECT));
+        else
+            GetWindowRect(windows.front(), &rect);
+
+        windows.pop();
+
+        list->windows[list->num_of_windows].w = rect.right - rect.left;
+        list->windows[list->num_of_windows].h = rect.bottom - rect.top;
+        if (list->windows[list->num_of_windows].w == 0 ||
+            list->windows[list->num_of_windows].h == 0)
+            continue;
+        list->windows[list->num_of_windows].x = rect.left;
+        list->windows[list->num_of_windows].y = rect.top;
+
+        list->num_of_windows++;
+    }
+
+    write_message(VD_AGENT_SEAMLESS_MODE_LIST, size, list);
+    free(list);
+}
+
+BOOL CALLBACK VDAgent::enum_wnd_proc(HWND hwnd, LPARAM lparam)
+{
+    std::queue<HWND>* windows = reinterpret_cast<std::queue<HWND>*>(lparam);
+    char window_text[256];
+    char window_class[256];
+    //TITLEBARINFO titlebar;
+
+    if (!IsWindowVisible(hwnd))
+        return TRUE;
+
+    GetWindowTextA(hwnd, window_text, sizeof(window_text));
+    GetClassNameA(hwnd, window_class, sizeof(window_class));
+    if (!strcmp(window_text, "Program Manager") || !strcmp(window_class, "ApplicationFrameWindow"))
+        return TRUE;
+
+    vd_printf("seamless::: text: %s, class: %s", window_text, window_class);
+
+    //titlebar.cbSize = sizeof(TITLEBARINFO);
+    //GetTitleBarInfo(hwnd, &titlebar);
+    //if((titlebar.rgstate[0] & STATE_SYSTEM_INVISIBLE))
+    //    return TRUE;
+
+    windows->push(hwnd);
+    return TRUE;
+}
+
 VOID VDAgent::read_completion(DWORD err, DWORD bytes, LPOVERLAPPED overlapped)
 {
     VDAgent* a = _singleton;
-- 
2.13.2



More information about the Spice-devel mailing list