[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