/* 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 .
*
* 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
#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
#include
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:
{
GdkWin32Surface *surface_win32;
GdkModifierType state;
uint32_t time;
int scale;
float pan_x;
float pan_y;
GdkEvent *event;
pan_x = transform[4];
pan_y = transform[5];
surface_win32 = GDK_WIN32_SURFACE (self->surface);
scale = surface_win32->surface_scale;
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) / scale,
(self->pan_y - pan_y) / scale,
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);
IUnknown_Release (content);
}
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;
}
/* }}} */