Blender V4.3
GHOST_TrackpadWin32.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include <cmath>
10
11#include "GHOST_Debug.hh"
13
14GHOST_DirectManipulationHelper::GHOST_DirectManipulationHelper(
15 HWND hWnd,
16 Microsoft::WRL::ComPtr<IDirectManipulationManager> directManipulationManager,
17 Microsoft::WRL::ComPtr<IDirectManipulationUpdateManager> directManipulationUpdateManager,
18 Microsoft::WRL::ComPtr<IDirectManipulationViewport> directManipulationViewport,
19 Microsoft::WRL::ComPtr<GHOST_DirectManipulationViewportEventHandler>
20 directManipulationEventHandler,
21 DWORD directManipulationViewportHandlerCookie,
22 bool isScrollDirectionInverted)
23 : m_hWnd(hWnd),
24 m_scrollDirectionRegKey(nullptr),
25 m_scrollDirectionChangeEvent(nullptr),
26 m_directManipulationManager(directManipulationManager),
27 m_directManipulationUpdateManager(directManipulationUpdateManager),
28 m_directManipulationViewport(directManipulationViewport),
29 m_directManipulationEventHandler(directManipulationEventHandler),
30 m_directManipulationViewportHandlerCookie(directManipulationViewportHandlerCookie),
31 m_isScrollDirectionInverted(isScrollDirectionInverted)
32{
33}
34
36{
37#define DM_CHECK_RESULT_AND_EXIT_EARLY(hr, failMessage) \
38 { \
39 if (!SUCCEEDED(hr)) { \
40 GHOST_PRINT(failMessage); \
41 return nullptr; \
42 } \
43 }
44
45 Microsoft::WRL::ComPtr<IDirectManipulationManager> directManipulationManager;
46 HRESULT hr = ::CoCreateInstance(CLSID_DirectManipulationManager,
47 nullptr,
48 CLSCTX_INPROC_SERVER,
49 IID_PPV_ARGS(&directManipulationManager));
50 DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "DirectManipulationManager create failed\n");
51
52 /* Since we want to use fake viewport, we need to send fake updates to UpdateManager. */
53 Microsoft::WRL::ComPtr<IDirectManipulationUpdateManager> directManipulationUpdateManager;
54 hr = directManipulationManager->GetUpdateManager(IID_PPV_ARGS(&directManipulationUpdateManager));
55 DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "Get UpdateManager failed\n");
56
57 Microsoft::WRL::ComPtr<IDirectManipulationViewport> directManipulationViewport;
58 hr = directManipulationManager->CreateViewport(
59 nullptr, hWnd, IID_PPV_ARGS(&directManipulationViewport));
60 DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "Viewport create failed\n");
61
62 DIRECTMANIPULATION_CONFIGURATION configuration =
63 DIRECTMANIPULATION_CONFIGURATION_INTERACTION |
64 DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X |
65 DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y |
66 DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA |
67 DIRECTMANIPULATION_CONFIGURATION_SCALING;
68
69 hr = directManipulationViewport->ActivateConfiguration(configuration);
70 DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "Viewport set ActivateConfiguration failed\n");
71
72 /* Since we are using fake viewport and only want to use Direct Manipulation for touchpad, we
73 * need to use MANUALUPDATE option. */
74 hr = directManipulationViewport->SetViewportOptions(
75 DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE);
76 DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "Viewport set ViewportOptions failed\n");
77
78 /* We receive Direct Manipulation transform updates in IDirectManipulationViewportEventHandler
79 * callbacks. */
80 Microsoft::WRL::ComPtr<GHOST_DirectManipulationViewportEventHandler>
81 directManipulationEventHandler =
82 Microsoft::WRL::Make<GHOST_DirectManipulationViewportEventHandler>(dpi);
83 DWORD directManipulationViewportHandlerCookie;
84 directManipulationViewport->AddEventHandler(
85 hWnd, directManipulationEventHandler.Get(), &directManipulationViewportHandlerCookie);
86 DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "Viewport add EventHandler failed\n");
87
88 /* Set default rect for viewport before activating. */
89 RECT rect = {0, 0, 10000, 10000};
90 hr = directManipulationViewport->SetViewportRect(&rect);
91 DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "Viewport set rect failed\n");
92
93 hr = directManipulationManager->Activate(hWnd);
94 DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "DirectManipulationManager activate failed\n");
95
96 hr = directManipulationViewport->Enable();
97 DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "Viewport enable failed\n");
98
99 directManipulationEventHandler->resetViewport(directManipulationViewport.Get());
100
101 bool isScrollDirectionInverted = getScrollDirectionFromReg();
102
103 auto instance = new GHOST_DirectManipulationHelper(hWnd,
104 directManipulationManager,
105 directManipulationUpdateManager,
106 directManipulationViewport,
107 directManipulationEventHandler,
108 directManipulationViewportHandlerCookie,
109 isScrollDirectionInverted);
110
111 instance->registerScrollDirectionChangeListener();
112
113 return instance;
114
115#undef DM_CHECK_RESULT_AND_EXIT_EARLY
116}
117
118bool GHOST_DirectManipulationHelper::getScrollDirectionFromReg()
119{
120 DWORD scrollDirectionRegValue, pcbData;
121 HRESULT hr = HRESULT_FROM_WIN32(
122 RegGetValueW(HKEY_CURRENT_USER,
123 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\PrecisionTouchPad\\",
124 L"ScrollDirection",
125 RRF_RT_REG_DWORD,
126 nullptr,
127 &scrollDirectionRegValue,
128 &pcbData));
129 if (!SUCCEEDED(hr)) {
130 GHOST_PRINT("Failed to get scroll direction from registry\n");
131 return false;
132 }
133
134 return scrollDirectionRegValue == 0;
135}
136
137void GHOST_DirectManipulationHelper::registerScrollDirectionChangeListener()
138{
139
140 if (!m_scrollDirectionRegKey) {
141 HRESULT hr = HRESULT_FROM_WIN32(
142 RegOpenKeyExW(HKEY_CURRENT_USER,
143 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\PrecisionTouchPad\\",
144 0,
145 KEY_NOTIFY,
146 &m_scrollDirectionRegKey));
147 if (!SUCCEEDED(hr)) {
148 GHOST_PRINT("Failed to open scroll direction registry key\n");
149 return;
150 }
151 }
152
153 if (!m_scrollDirectionChangeEvent) {
154 m_scrollDirectionChangeEvent = CreateEventW(nullptr, true, false, nullptr);
155 }
156 else {
157 ResetEvent(m_scrollDirectionChangeEvent);
158 }
159 HRESULT hr = HRESULT_FROM_WIN32(RegNotifyChangeKeyValue(m_scrollDirectionRegKey,
160 true,
161 REG_NOTIFY_CHANGE_LAST_SET,
162 m_scrollDirectionChangeEvent,
163 true));
164 if (!SUCCEEDED(hr)) {
165 GHOST_PRINT("Failed to register scroll direction change listener\n");
166 return;
167 }
168}
169
171{
172 [[maybe_unused]] HRESULT hr = m_directManipulationViewport->SetContact(pointerId);
173 GHOST_ASSERT(SUCCEEDED(hr), "Viewport set contact failed\n");
174
175 if (WaitForSingleObject(m_scrollDirectionChangeEvent, 0) == WAIT_OBJECT_0) {
176 m_isScrollDirectionInverted = getScrollDirectionFromReg();
177 registerScrollDirectionChangeListener();
178 }
179}
180
182{
183 if (m_directManipulationEventHandler->dm_status == DIRECTMANIPULATION_RUNNING ||
184 m_directManipulationEventHandler->dm_status == DIRECTMANIPULATION_INERTIA)
185 {
186 [[maybe_unused]] HRESULT hr = m_directManipulationUpdateManager->Update(nullptr);
187 GHOST_ASSERT(SUCCEEDED(hr), "DirectManipulationUpdateManager update failed\n");
188 }
189}
190
192{
193 m_directManipulationEventHandler->dpi = dpi;
194}
195
197{
198 GHOST_TTrackpadInfo result = m_directManipulationEventHandler->accumulated_values;
199 result.isScrollDirectionInverted = m_isScrollDirectionInverted;
200
201 m_directManipulationEventHandler->accumulated_values = {0, 0, 0};
202 return result;
203}
204
206{
207 HRESULT hr;
208 hr = m_directManipulationViewport->Stop();
209 GHOST_ASSERT(SUCCEEDED(hr), "Viewport stop failed\n");
210
211 hr = m_directManipulationViewport->RemoveEventHandler(m_directManipulationViewportHandlerCookie);
212 GHOST_ASSERT(SUCCEEDED(hr), "Viewport remove event handler failed\n");
213
214 hr = m_directManipulationViewport->Abandon();
215 GHOST_ASSERT(SUCCEEDED(hr), "Viewport abandon failed\n");
216
217 hr = m_directManipulationManager->Deactivate(m_hWnd);
218 GHOST_ASSERT(SUCCEEDED(hr), "DirectManipulationManager deactivate failed\n");
219
220 if (m_scrollDirectionChangeEvent) {
221 CloseHandle(m_scrollDirectionChangeEvent);
222 m_scrollDirectionChangeEvent = nullptr;
223 }
224 if (m_scrollDirectionRegKey) {
225 RegCloseKey(m_scrollDirectionRegKey);
226 m_scrollDirectionRegKey = nullptr;
227 }
228}
229
231 uint16_t dpi)
232 : accumulated_values({0, 0, 0}), dpi(dpi), dm_status(DIRECTMANIPULATION_BUILDING)
233{
234}
235
237 IDirectManipulationViewport *viewport)
238{
239 if (gesture_state != GESTURE_NONE) {
240 [[maybe_unused]] HRESULT hr = viewport->ZoomToRect(0.0f, 0.0f, 10000.0f, 10000.0f, FALSE);
241 GHOST_ASSERT(SUCCEEDED(hr), "Viewport reset failed\n");
242 }
243
244 gesture_state = GESTURE_NONE;
245
246 last_scale = PINCH_SCALE_FACTOR;
247 last_x = 0.0f;
248 last_y = 0.0f;
249}
250
252 IDirectManipulationViewport *viewport,
253 DIRECTMANIPULATION_STATUS current,
254 DIRECTMANIPULATION_STATUS previous)
255{
256 dm_status = current;
257
258 if (current == previous) {
259 return S_OK;
260 }
261
262 if (previous == DIRECTMANIPULATION_ENABLED || current == DIRECTMANIPULATION_READY ||
263 (previous == DIRECTMANIPULATION_INERTIA && current != DIRECTMANIPULATION_INERTIA))
264 {
265 resetViewport(viewport);
266 }
267
268 return S_OK;
269}
270
272 IDirectManipulationViewport * /*viewport*/)
273{
274 /* Nothing to do here. */
275 return S_OK;
276}
277
279 IDirectManipulationViewport * /*viewport*/, IDirectManipulationContent *content)
280{
281 float transform[6];
282 HRESULT hr = content->GetContentTransform(transform, ARRAYSIZE(transform));
283 GHOST_ASSERT(SUCCEEDED(hr), "DirectManipulationContent get transform failed\n");
284
285 const float device_scale_factor = dpi / 96.0f;
286
287 const float scale = transform[0] * PINCH_SCALE_FACTOR;
288 const float x = transform[4] / device_scale_factor;
289 const float y = transform[5] / device_scale_factor;
290
291 const float EPS = 3e-5;
292
293 /* Ignore repeating or incorrect input. */
294 if ((fabs(scale - last_scale) <= EPS && fabs(x - last_x) <= EPS && fabs(y - last_y) <= EPS) ||
295 scale == 0.0f)
296 {
297 GHOST_PRINT("Ignoring touchpad input\n");
298 return hr;
299 }
300
301 /* Assume that every gesture is a pan in the beginning.
302 * If it's a pinch, the gesture will be changed below. */
303 if (gesture_state == GESTURE_NONE) {
304 gesture_state = GESTURE_PAN;
305 }
306
307 /* DM doesn't always immediately recognize pinch gestures,
308 * so allow transition from pan to pinch. */
309 if (gesture_state == GESTURE_PAN) {
310 if (fabs(scale - PINCH_SCALE_FACTOR) > EPS) {
311 gesture_state = GESTURE_PINCH;
312 }
313 }
314
315 /* This state machine is used here because:
316 * 1. Pinch and pan gestures must be differentiated and cannot be processed at the same time
317 * because XY transform values become nonsensical during pinch gesture.
318 * 2. GHOST requires delta values for events while DM provides transformation matrix of the
319 * current gesture.
320 * 3. GHOST events accept integer values while DM values are non-integer.
321 * Truncated fractional parts are accumulated and accounted for in following updates.
322 */
323 switch (gesture_state) {
324 case GESTURE_PINCH: {
325 int32_t dscale = roundf(scale - last_scale);
326
327 last_scale += dscale;
328
329 accumulated_values.scale += dscale;
330 break;
331 }
332 case GESTURE_PAN: {
333 int32_t dx = roundf(x - last_x);
334 int32_t dy = roundf(y - last_y);
335
336 last_x += dx;
337 last_y += dy;
338
339 accumulated_values.x += dx;
340 accumulated_values.y += dy;
341 break;
342 }
343 case GESTURE_NONE:
344 break;
345 }
346
347 return hr;
348}
#define FALSE
#define GHOST_ASSERT(x, info)
#define GHOST_PRINT(x)
#define DM_CHECK_RESULT_AND_EXIT_EARLY(hr, failMessage)
#define PINCH_SCALE_FACTOR
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object instance
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
void onPointerHitTest(UINT32 pointerId)
static GHOST_DirectManipulationHelper * create(HWND hWnd, uint16_t dpi)
void resetViewport(IDirectManipulationViewport *viewport)
HRESULT STDMETHODCALLTYPE OnContentUpdated(IDirectManipulationViewport *viewport, IDirectManipulationContent *content) override
HRESULT STDMETHODCALLTYPE OnViewportUpdated(IDirectManipulationViewport *viewport) override
HRESULT STDMETHODCALLTYPE OnViewportStatusChanged(IDirectManipulationViewport *viewport, DIRECTMANIPULATION_STATUS current, DIRECTMANIPULATION_STATUS previous) override
ccl_device_inline float2 fabs(const float2 a)
#define L
unsigned short uint16_t
Definition stdint.h:79
signed int int32_t
Definition stdint.h:77
#define EPS
Definition unit.cc:42