[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