--- 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 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel