GdkWin32: Add support for DirectManipulation

Adds support for Precision TouchPads (PTPs) gestures
and scroll events.
This commit is contained in:
Luca Bacci 2022-05-04 14:56:48 +02:00
parent db2516af0a
commit 8f19099f1a
8 changed files with 735 additions and 2 deletions

View File

@ -58,6 +58,7 @@
#include "gdkdeviceprivate.h"
#include "gdkdevice-virtual.h"
#include "gdkdevice-wintab.h"
#include "gdkinput-dmanipulation.h"
#include "gdkinput-winpointer.h"
#include "gdkwin32dnd.h"
#include "gdkwin32dnd-private.h"
@ -2656,6 +2657,13 @@ gdk_event_translate (MSG *msg,
return_val = TRUE;
break;
case DM_POINTERHITTEST:
gdk_dmanipulation_maybe_add_contact (window, msg);
*ret_valp = 0;
return_val = TRUE;
break;
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
{
@ -3156,6 +3164,8 @@ gdk_event_translate (MSG *msg,
if (win32_display->tablet_input_api == GDK_WIN32_TABLET_INPUT_API_WINPOINTER)
gdk_winpointer_finalize_surface (window);
gdk_dmanipulation_finalize_surface (window);
return_val = FALSE;
break;

View File

@ -0,0 +1,586 @@
/* gdkinput-dmanipulation.c
*
* Copyright © 2022 the GTK team
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
/* {{{ */
#ifdef WINVER
#undef WINVER
#endif
#ifdef _WIN32_WINNT
#undef _WIN32_WINNT
#endif
#define WINVER 0x0603
#define _WIN32_WINNT 0x0603
#define COBJMACROS
#include "config.h"
#include <gdk/gdk.h>
#include "gdkwin32.h"
#include "gdkprivate-win32.h"
#include "gdkdevicemanager-win32.h"
#include "gdkdevice-virtual.h"
#include "gdkdeviceprivate.h"
#include "gdkdisplayprivate.h"
#include "gdkeventsprivate.h"
#include "gdkseatdefaultprivate.h"
#include "gdkinput-dmanipulation.h"
#include "winpointer.h"
#include <windows.h>
#include <directmanipulation.h>
typedef BOOL
(WINAPI *getPointerType_t)(UINT32 pointerId, POINTER_INPUT_TYPE *pointerType);
static getPointerType_t getPointerType;
static IDirectManipulationManager *dmanipulation_manager;
typedef struct
{
IDirectManipulationViewportEventHandlerVtbl *vtable;
LONG reference_count;
enum {
GESTURE_PAN,
GESTURE_ZOOM,
} gesture;
GdkTouchpadGesturePhase phase;
gpointer sequence;
float scale;
float pan_x;
float pan_y;
GdkSurface *surface;
GdkDevice *device;
}
DManipEventHandler;
static void dmanip_event_handler_running_state_clear (DManipEventHandler *handler);
static void dmanip_event_handler_free (DManipEventHandler *handler);
static void reset_viewport (IDirectManipulationViewport *viewport);
static gpointer util_get_next_sequence (void);
static GdkModifierType util_get_modifier_state (void);
static gboolean util_handler_free (gpointer);
/* }}} */
/* {{{ ViewportEventHandler */
static STDMETHODIMP_ (ULONG)
DManipEventHandler_AddRef (IDirectManipulationViewportEventHandler *self_)
{
DManipEventHandler *self = (DManipEventHandler*) self_;
return (ULONG) InterlockedIncrement (&self->reference_count);
}
static STDMETHODIMP_ (ULONG)
DManipEventHandler_Release (IDirectManipulationViewportEventHandler *self_)
{
DManipEventHandler *self = (DManipEventHandler*) self_;
/* NOTE: This may run from a worker thread */
LONG new_reference_count = InterlockedDecrement (&self->reference_count);
if (new_reference_count <= 0)
{
/* For safety, schedule the cleanup to be executed
* on the main thread */
g_idle_add (util_handler_free, self);
return 0;
}
return (ULONG) new_reference_count;
}
static STDMETHODIMP
DManipEventHandler_QueryInterface (IDirectManipulationViewportEventHandler *self_,
REFIID riid,
void **ppvObject)
{
DManipEventHandler *self = (DManipEventHandler*) self_;
if G_UNLIKELY (!self || !ppvObject)
return E_POINTER;
*ppvObject = NULL;
if (IsEqualGUID (riid, &IID_IUnknown))
*ppvObject = self;
else if (IsEqualGUID (riid, &IID_IDirectManipulationViewportEventHandler))
*ppvObject = self;
if (*ppvObject == NULL)
return E_NOINTERFACE;
DManipEventHandler_AddRef (self_);
return S_OK;
}
/* NOTE:
*
* All DManipEventHandler callbacks are fired from the main thread */
static STDMETHODIMP
DManipEventHandler_OnViewportUpdated (IDirectManipulationViewportEventHandler *self_,
IDirectManipulationViewport *viewport)
{
return S_OK;
}
static STDMETHODIMP
DManipEventHandler_OnContentUpdated (IDirectManipulationViewportEventHandler *self_,
IDirectManipulationViewport *viewport,
IDirectManipulationContent *content)
{
DManipEventHandler *self = (DManipEventHandler*) self_;
float transform[6] = {1., 0., 0., 1., 0., 0.};
HRESULT hr;
hr = IDirectManipulationContent_GetContentTransform (content, transform,
G_N_ELEMENTS (transform));
HR_CHECK_RETURN_VAL (hr, E_FAIL);
switch (self->gesture)
{
case GESTURE_PAN:
{
GdkModifierType state;
uint32_t time;
float pan_x;
float pan_y;
GdkEvent *event;
pan_x = transform[4];
pan_y = transform[5];
state = util_get_modifier_state ();
time = (uint32_t) GetMessageTime ();
event = gdk_scroll_event_new (self->surface,
self->device,
NULL, time, state,
self->pan_x - pan_x,
self->pan_y - pan_y,
FALSE,
GDK_SCROLL_UNIT_SURFACE);
_gdk_win32_append_event (event);
self->pan_x = pan_x;
self->pan_y = pan_y;
}
break;
case GESTURE_ZOOM:
{
GdkModifierType state;
uint32_t time;
POINT cursor = {0, 0};
float scale;
GdkEvent *event;
scale = transform[0];
state = util_get_modifier_state ();
time = (uint32_t) GetMessageTime ();
_gdk_win32_get_cursor_pos (&cursor);
ScreenToClient (GDK_SURFACE_HWND (self->surface), &cursor);
if (!self->sequence)
self->sequence = util_get_next_sequence ();
event = gdk_touchpad_event_new_pinch (self->surface, self->sequence, self->device,
time, state, self->phase, cursor.x, cursor.y,
2, 0.0, 0.0, scale, 0.0);
_gdk_win32_append_event (event);
self->scale = scale;
self->phase = GDK_TOUCHPAD_GESTURE_PHASE_UPDATE;
}
break;
default:
g_assert_not_reached ();
break;
}
return S_OK;
}
static STDMETHODIMP
DManipEventHandler_OnViewportStatusChanged (IDirectManipulationViewportEventHandler *self_,
IDirectManipulationViewport *viewport,
DIRECTMANIPULATION_STATUS current,
DIRECTMANIPULATION_STATUS previous)
{
DManipEventHandler *self = (DManipEventHandler*) self_;
if (previous == DIRECTMANIPULATION_RUNNING)
{
switch (self->gesture)
{
case GESTURE_PAN:
{
GdkModifierType state;
uint32_t time;
GdkEvent *event;
state = util_get_modifier_state ();
time = (uint32_t) GetMessageTime ();
event = gdk_scroll_event_new (self->surface, self->device,
NULL, time, state,
0.0, 0.0, TRUE,
GDK_SCROLL_UNIT_SURFACE);
_gdk_win32_append_event (event);
}
break;
case GESTURE_ZOOM:
{
GdkModifierType state;
uint32_t time;
POINT cursor = {0, 0};
GdkEvent *event;
if (self->phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN)
break;
state = util_get_modifier_state ();
time = (uint32_t) GetMessageTime ();
_gdk_win32_get_cursor_pos (&cursor);
ScreenToClient (GDK_SURFACE_HWND (self->surface), &cursor);
event = gdk_touchpad_event_new_pinch (self->surface, self->sequence, self->device,
time, state, GDK_TOUCHPAD_GESTURE_PHASE_END,
cursor.x, cursor.y, 2, 0., 0., self->scale,
0.);
_gdk_win32_append_event (event);
}
break;
default:
g_assert_not_reached ();
break;
}
dmanip_event_handler_running_state_clear (self);
reset_viewport (viewport);
}
return S_OK;
}
static void
dmanip_event_handler_running_state_clear (DManipEventHandler *handler)
{
handler->scale = 1.0;
handler->pan_x = 0.0;
handler->pan_y = 0.0;
handler->phase = GDK_TOUCHPAD_GESTURE_PHASE_BEGIN;
handler->sequence = NULL;
}
static DManipEventHandler*
dmanip_event_handler_new (GdkSurface *surface,
int gesture)
{
static IDirectManipulationViewportEventHandlerVtbl vtable = {
DManipEventHandler_QueryInterface,
DManipEventHandler_AddRef,
DManipEventHandler_Release,
DManipEventHandler_OnViewportStatusChanged,
DManipEventHandler_OnViewportUpdated,
DManipEventHandler_OnContentUpdated,
};
DManipEventHandler *handler;
handler = g_new0 (DManipEventHandler, 1);
handler->vtable = &vtable;
handler->reference_count = 1;
handler->gesture = gesture;
handler->surface = surface;
handler->device = _gdk_device_manager->core_pointer;
dmanip_event_handler_running_state_clear (handler);
return handler;
}
static void
dmanip_event_handler_free (DManipEventHandler *handler)
{
g_free (handler);
}
/* }}} */
/* {{{ Viewport utils */
static void
reset_viewport (IDirectManipulationViewport *viewport)
{
IDirectManipulationContent *content = NULL;
REFIID iid = &IID_IDirectManipulationContent;
float identity[6] = {1., 0., 0., 1., 0., 0.};
HRESULT hr;
hr = IDirectManipulationViewport_GetPrimaryContent (viewport, iid, (void**)&content);
HR_CHECK (hr);
hr = IDirectManipulationContent_SyncContentTransform (content, identity,
G_N_ELEMENTS (identity));
HR_CHECK (hr);
}
static void
close_viewport (IDirectManipulationViewport **p_viewport)
{
IDirectManipulationViewport *viewport = *p_viewport;
if (viewport)
{
IDirectManipulationViewport_Abandon (viewport);
IUnknown_Release (viewport);
*p_viewport = NULL;
}
}
static void
create_viewport (GdkSurface *surface,
int gesture,
IDirectManipulationViewport **pViewport)
{
DIRECTMANIPULATION_CONFIGURATION configuration = 0;
HWND hwnd = GDK_SURFACE_HWND (surface);
IDirectManipulationViewportEventHandler *handler;
DWORD cookie = 0;
HRESULT hr;
hr = IDirectManipulationManager_CreateViewport (dmanipulation_manager, NULL, hwnd,
&IID_IDirectManipulationViewport,
(void**) pViewport);
HR_CHECK_GOTO (hr, failed);
switch (gesture)
{
case GESTURE_PAN:
configuration = DIRECTMANIPULATION_CONFIGURATION_INTERACTION |
DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X |
DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y;
break;
case GESTURE_ZOOM:
configuration = DIRECTMANIPULATION_CONFIGURATION_INTERACTION |
DIRECTMANIPULATION_CONFIGURATION_SCALING;
break;
default:
g_assert_not_reached ();
break;
}
handler = (IDirectManipulationViewportEventHandler*)
dmanip_event_handler_new (surface, gesture);
hr = IDirectManipulationViewport_AddEventHandler (*pViewport, hwnd, handler, &cookie);
HR_CHECK_GOTO (hr, failed);
hr = IDirectManipulationViewport_ActivateConfiguration (*pViewport, configuration);
HR_CHECK_GOTO (hr, failed);
hr = IDirectManipulationViewport_SetViewportOptions (*pViewport,
DIRECTMANIPULATION_VIEWPORT_OPTIONS_DISABLEPIXELSNAPPING);
hr = IDirectManipulationViewport_Enable (*pViewport);
HR_CHECK_GOTO (hr, failed);
// drop our initial reference
IUnknown_Release (handler);
return;
failed:
if (handler)
IUnknown_Release (handler);
close_viewport (pViewport);
}
/* }}} */
/* {{{ Public */
void gdk_dmanipulation_initialize (void)
{
if (!getPointerType)
{
HMODULE user32_mod;
user32_mod = LoadLibraryW (L"user32.dll");
if (!user32_mod)
{
WIN32_API_FAILED ("LoadLibraryW");
return;
}
getPointerType = (getPointerType_t)
GetProcAddress (user32_mod, "GetPointerType");
if (!getPointerType)
return;
}
if (!gdk_win32_ensure_com ())
return;
if (dmanipulation_manager == NULL)
{
HRESULT hr;
hr = CoCreateInstance (&CLSID_DirectManipulationManager,
NULL,
CLSCTX_INPROC_SERVER,
&IID_IDirectManipulationManager,
(LPVOID*)&dmanipulation_manager);
if (FAILED (hr))
{
if (hr == REGDB_E_CLASSNOTREG || hr == E_NOINTERFACE);
/* Not an error,
* DirectManipulation is not available */
else HR_LOG (hr);
}
}
}
void gdk_dmanipulation_initialize_surface (GdkSurface *surface)
{
GdkWin32Surface *surface_win32;
HRESULT hr;
if (!dmanipulation_manager)
return;
surface_win32 = GDK_WIN32_SURFACE (surface);
create_viewport (surface, GESTURE_PAN,
&surface_win32->dmanipulation_viewport_pan);
create_viewport (surface, GESTURE_ZOOM,
&surface_win32->dmanipulation_viewport_zoom);
hr = IDirectManipulationManager_Activate (dmanipulation_manager,
GDK_SURFACE_HWND (surface));
HR_CHECK (hr);
}
void gdk_dmanipulation_finalize_surface (GdkSurface *surface)
{
GdkWin32Surface *surface_win32 = GDK_WIN32_SURFACE (surface);
close_viewport (&surface_win32->dmanipulation_viewport_zoom);
close_viewport (&surface_win32->dmanipulation_viewport_pan);
}
void gdk_dmanipulation_maybe_add_contact (GdkSurface *surface,
MSG *msg)
{
POINTER_INPUT_TYPE type = PT_POINTER;
UINT32 pointer_id = GET_POINTERID_WPARAM (msg->wParam);
if (!dmanipulation_manager)
return;
if (!getPointerType)
return;
if G_UNLIKELY (!getPointerType (pointer_id, &type))
{
WIN32_API_FAILED_LOG_ONCE ("GetPointerType");
return;
}
if (type == PT_TOUCHPAD)
{
GdkWin32Surface *surface_win32 = GDK_WIN32_SURFACE (surface);
HRESULT hr;
hr = IDirectManipulationViewport_SetContact (surface_win32->dmanipulation_viewport_pan,
pointer_id);
HR_CHECK (hr);
hr = IDirectManipulationViewport_SetContact (surface_win32->dmanipulation_viewport_zoom,
pointer_id);
HR_CHECK (hr);
}
}
/* }}} */
/* {{{ Utils */
static gpointer
util_get_next_sequence (void)
{
//TODO: sequence of other input types?
static unsigned char *sequence_counter = 0;
if (++sequence_counter == 0)
sequence_counter++;
return sequence_counter;
}
static GdkModifierType
util_get_modifier_state (void)
{
GdkModifierType mask = 0;
BYTE kbd[256];
GetKeyboardState (kbd);
if (kbd[VK_SHIFT] & 0x80)
mask |= GDK_SHIFT_MASK;
if (kbd[VK_CAPITAL] & 0x80)
mask |= GDK_LOCK_MASK;
if (kbd[VK_CONTROL] & 0x80)
mask |= GDK_CONTROL_MASK;
if (kbd[VK_MENU] & 0x80)
mask |= GDK_ALT_MASK;
return mask;
}
static gboolean
util_handler_free (gpointer handler)
{
dmanip_event_handler_free ((DManipEventHandler*)handler);
return G_SOURCE_REMOVE;
}
/* }}} */

View File

@ -0,0 +1,32 @@
/* gdkinput-dmanipulation.h
*
* Copyright © 2022 the GTK team
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __GDK_INPUT_DMANIPULATION_H__
#define __GDK_INPUT_DMANIPULATION_H__
void gdk_dmanipulation_initialize (void);
void gdk_dmanipulation_initialize_surface (GdkSurface *surface);
void gdk_dmanipulation_finalize_surface (GdkSurface *surface);
void gdk_dmanipulation_maybe_add_contact (GdkSurface *surface,
MSG *msg);
#endif /* __GDK_INPUT_DMANIPULATION_H__ */

View File

@ -36,6 +36,7 @@
#include "gdkkeysyms.h"
#include "gdkintl.h"
#include "gdkprivate-win32.h"
#include "gdkinput-dmanipulation.h"
#include "gdkwin32.h"
#include <objbase.h>
@ -49,6 +50,12 @@
static gboolean gdk_synchronize = FALSE;
/* Whether GDK initialized COM */
static gboolean co_initialized = FALSE;
/* Whether GDK initialized OLE */
static gboolean ole_initialized = FALSE;
void
_gdk_win32_surfaceing_init (void)
{
@ -67,6 +74,82 @@ _gdk_win32_surfaceing_init (void)
GDK_NOTE (EVENTS, g_print ("input_locale: %p\n", _gdk_input_locale));
_gdk_win32_clipdrop_init ();
gdk_dmanipulation_initialize ();
}
gboolean
gdk_win32_ensure_com (void)
{
if (!co_initialized)
{
/* UI thread should only use STA model. See
* -> https://devblogs.microsoft.com/oldnewthing/20080424-00/?p=22603
* -> https://devblogs.microsoft.com/oldnewthing/20071018-00/?p=24743
*/
const DWORD flags = COINIT_APARTMENTTHREADED |
COINIT_DISABLE_OLE1DDE;
HRESULT hr;
hr = CoInitializeEx (NULL, flags);
if (SUCCEEDED (hr))
co_initialized = TRUE;
else switch (hr)
{
case RPC_E_CHANGED_MODE:
g_warning ("COM runtime already initialized on the main "
"thread with an incompatible apartment model");
break;
default:
HR_LOG (hr);
break;
}
}
return co_initialized;
}
static void
gdk_win32_finalize_com (void)
{
if (co_initialized)
{
CoUninitialize ();
co_initialized = FALSE;
}
}
gboolean
gdk_win32_ensure_ole (void)
{
if (!ole_initialized)
{
HRESULT hr = OleInitialize (NULL);
if (SUCCEEDED (hr))
ole_initialized = TRUE;
else switch (hr)
{
case RPC_E_CHANGED_MODE:
g_warning ("Failed to initialize the OLE2 runtime because "
"the thread has an incompatible apartment model");
break;
default:
HR_LOG (hr);
break;
}
}
return ole_initialized;
}
static void
gdk_win32_finalize_ole (void)
{
if (ole_initialized)
{
OleUninitialize ();
ole_initialized = FALSE;
}
}
void

View File

@ -196,6 +196,9 @@ void _gdk_remove_modal_window (GdkSurface *window);
GdkSurface *_gdk_modal_current (void);
gboolean _gdk_modal_blocked (GdkSurface *window);
gboolean gdk_win32_ensure_com (void);
gboolean gdk_win32_ensure_ole (void);
#ifdef G_ENABLE_DEBUG
void _gdk_win32_print_paletteentries (const PALETTEENTRY *pep,
const int nentries);
@ -252,6 +255,13 @@ void _gdk_other_api_failed (const char *where,
#define GDI_CALL(api, arglist) (api arglist ? 1 : (WIN32_GDI_FAILED (#api), 0))
#define API_CALL(api, arglist) (api arglist ? 1 : (WIN32_API_FAILED (#api), 0))
#define HR_LOG(hr)
#define HR_CHECK_RETURN(hr) { if G_UNLIKELY (FAILED (hr)) return; }
#define HR_CHECK_RETURN_VAL(hr, val) { if G_UNLIKELY (FAILED (hr)) return val; }
#define HR_CHECK(hr)
#define HR_CHECK_GOTO(hr, label) { if G_UNLIKELY (FAILED (hr)) goto label; }
extern LRESULT CALLBACK _gdk_win32_surface_procedure (HWND, UINT, WPARAM, LPARAM);
extern GdkDisplay *_gdk_display;

View File

@ -44,6 +44,7 @@
#include "gdktoplevelprivate.h"
#include "gdkwin32surface.h"
#include "gdkwin32cursor.h"
#include "gdkinput-dmanipulation.h"
#include "gdkinput-winpointer.h"
#include "gdkglcontext-win32.h"
#include "gdkdisplay-win32.h"
@ -639,9 +640,15 @@ _gdk_win32_display_create_surface (GdkDisplay *display,
}
gdk_surface_set_egl_native_window (surface, (void *) impl->handle);
if (surface_type != GDK_SURFACE_TEMP)
{
if (display_win32->tablet_input_api == GDK_WIN32_TABLET_INPUT_API_WINPOINTER)
gdk_winpointer_initialize_surface (surface);
gdk_dmanipulation_initialize_surface (surface);
}
_gdk_win32_surface_enable_transparency (surface);
_gdk_win32_surface_register_dnd (surface);
_gdk_win32_surface_update_style_bits (surface);

View File

@ -32,6 +32,7 @@
#include "gdk/gdkcursor.h"
#include <windows.h>
#include <directmanipulation.h>
#ifdef HAVE_EGL
# include <epoxy/egl.h>
@ -339,6 +340,9 @@ struct _GdkWin32Surface
} next_layout;
gboolean force_recompute_size;
IDirectManipulationViewport *dmanipulation_viewport_pan;
IDirectManipulationViewport *dmanipulation_viewport_zoom;
#ifdef HAVE_EGL
guint egl_force_redraw_all : 1;
#endif

View File

@ -17,6 +17,7 @@ gdk_win32_sources = files([
'gdkglcontext-win32-wgl.c',
'gdkglobals-win32.c',
'gdkhdataoutputstream-win32.c',
'gdkinput-dmanipulation.c',
'gdkinput-winpointer.c',
'gdkkeys-win32.c',
'gdkkeys-win32-impl.c',