[Spice-devel] [RFC spice-vdagent_win] Add initial seamless mode support
Pavel Grunt
pgrunt at redhat.com
Tue Jul 25 13:35:11 UTC 2017
Hi Jakub,
On Fri, 2017-07-07 at 11:57 +0200, Jakub Janků wrote:
> ---
> Demo: https://youtu.be/IX49z8VbD-c
Cool!
>
> 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/)
that is a pity
> -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_LOCAT
> IONCHANGE,
> + NULL,
> + wnd_event_proc,
> + 0, 0,
> + WINEVENT_OUTOFCONT
> EXT | WINEVENT_SKIPOWNPROCESS);
> + _seamless_window_change_hooks[1] =
> SetWinEventHook(EVENT_OBJECT_CREATE,
> + EVENT_OBJECT_HIDE,
> + NULL,
> + wnd_event_proc,
> + 0, 0,
> + WINEVENT_OUTOFCONT
> EXT | 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)
indentation
> +{
> + 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;
It works nicely. It should disable the seamless mode as soon as the client
disconnects (otherwise a new client may recieve the messages even if it does not
have the capability).
Pavel
More information about the Spice-devel
mailing list