Blender V5.0
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
8
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 : h_wnd_(hWnd),
24 scroll_direction_reg_key_(nullptr),
25 scroll_direction_change_event_(nullptr),
26 direct_manipulation_manager_(directManipulationManager),
27 direct_manipulation_update_manager_(directManipulationUpdateManager),
28 direct_manipulation_viewport_(directManipulationViewport),
29 direct_manipulation_event_handler_(directManipulationEventHandler),
30 direct_manipulation_viewport_handler_cookie_(directManipulationViewportHandlerCookie),
31 is_scroll_direction_inverted_(isScrollDirectionInverted)
32{
33}
34
35GHOST_DirectManipulationHelper *GHOST_DirectManipulationHelper::create(HWND hWnd, uint16_t dpi)
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 (!scroll_direction_reg_key_) {
141 HRESULT hr = HRESULT_FROM_WIN32(
142 RegOpenKeyExW(HKEY_CURRENT_USER,
143 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\PrecisionTouchPad\\",
144 0,
145 KEY_NOTIFY,
146 &scroll_direction_reg_key_));
147 if (!SUCCEEDED(hr)) {
148 GHOST_PRINT("Failed to open scroll direction registry key\n");
149 return;
150 }
151 }
152
153 if (!scroll_direction_change_event_) {
154 scroll_direction_change_event_ = CreateEventW(nullptr, true, false, nullptr);
155 }
156 else {
157 ResetEvent(scroll_direction_change_event_);
158 }
159 HRESULT hr = HRESULT_FROM_WIN32(RegNotifyChangeKeyValue(scroll_direction_reg_key_,
160 true,
161 REG_NOTIFY_CHANGE_LAST_SET,
162 scroll_direction_change_event_,
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 = direct_manipulation_viewport_->SetContact(pointerId);
173 GHOST_ASSERT(SUCCEEDED(hr), "Viewport set contact failed\n");
174
175 if (WaitForSingleObject(scroll_direction_change_event_, 0) == WAIT_OBJECT_0) {
176 is_scroll_direction_inverted_ = getScrollDirectionFromReg();
177 registerScrollDirectionChangeListener();
178 }
179}
180
182{
183 if (direct_manipulation_event_handler_->dm_status == DIRECTMANIPULATION_RUNNING ||
184 direct_manipulation_event_handler_->dm_status == DIRECTMANIPULATION_INERTIA)
185 {
186 [[maybe_unused]] HRESULT hr = direct_manipulation_update_manager_->Update(nullptr);
187 GHOST_ASSERT(SUCCEEDED(hr), "DirectManipulationUpdateManager update failed\n");
188 }
189}
190
192{
193 direct_manipulation_event_handler_->dpi = dpi;
194}
195
197{
198 GHOST_TTrackpadInfo result = direct_manipulation_event_handler_->accumulated_values;
199 result.isScrollDirectionInverted = is_scroll_direction_inverted_;
200
201 direct_manipulation_event_handler_->accumulated_values = {0, 0, 0};
202 return result;
203}
204
206{
207 HRESULT hr;
208 hr = direct_manipulation_viewport_->Stop();
209 GHOST_ASSERT(SUCCEEDED(hr), "Viewport stop failed\n");
210
211 hr = direct_manipulation_viewport_->RemoveEventHandler(
212 direct_manipulation_viewport_handler_cookie_);
213 GHOST_ASSERT(SUCCEEDED(hr), "Viewport remove event handler failed\n");
214
215 hr = direct_manipulation_viewport_->Abandon();
216 GHOST_ASSERT(SUCCEEDED(hr), "Viewport abandon failed\n");
217
218 hr = direct_manipulation_manager_->Deactivate(h_wnd_);
219 GHOST_ASSERT(SUCCEEDED(hr), "DirectManipulationManager deactivate failed\n");
220
221 if (scroll_direction_change_event_) {
222 CloseHandle(scroll_direction_change_event_);
223 scroll_direction_change_event_ = nullptr;
224 }
225 if (scroll_direction_reg_key_) {
226 RegCloseKey(scroll_direction_reg_key_);
227 scroll_direction_reg_key_ = nullptr;
228 }
229}
230
232 uint16_t dpi)
233 : accumulated_values({0, 0, 0}), dpi(dpi), dm_status(DIRECTMANIPULATION_BUILDING)
234{
235}
236
238 IDirectManipulationViewport *viewport)
239{
240 if (gesture_state != GESTURE_NONE) {
241 [[maybe_unused]] HRESULT hr = viewport->ZoomToRect(0.0f, 0.0f, 10000.0f, 10000.0f, FALSE);
242 GHOST_ASSERT(SUCCEEDED(hr), "Viewport reset failed\n");
243 }
244
245 gesture_state = GESTURE_NONE;
246
247 last_scale = PINCH_SCALE_FACTOR;
248 last_x = 0.0f;
249 last_y = 0.0f;
250}
251
253 IDirectManipulationViewport *viewport,
254 DIRECTMANIPULATION_STATUS current,
255 DIRECTMANIPULATION_STATUS previous)
256{
257 dm_status = current;
258
259 if (current == previous) {
260 return S_OK;
261 }
262
263 if (previous == DIRECTMANIPULATION_ENABLED || current == DIRECTMANIPULATION_READY ||
264 (previous == DIRECTMANIPULATION_INERTIA && current != DIRECTMANIPULATION_INERTIA))
265 {
266 resetViewport(viewport);
267 }
268
269 return S_OK;
270}
271
273 IDirectManipulationViewport * /*viewport*/)
274{
275 /* Nothing to do here. */
276 return S_OK;
277}
278
280 IDirectManipulationViewport * /*viewport*/, IDirectManipulationContent *content)
281{
282 float transform[6];
283 HRESULT hr = content->GetContentTransform(transform, ARRAYSIZE(transform));
284 GHOST_ASSERT(SUCCEEDED(hr), "DirectManipulationContent get transform failed\n");
285
286 const float device_scale_factor = dpi / 96.0f;
287
288 const float scale = transform[0] * PINCH_SCALE_FACTOR;
289 const float x = transform[4] / device_scale_factor;
290 const float y = transform[5] / device_scale_factor;
291
292 const float EPS = 3e-5;
293
294 /* Ignore repeating or incorrect input. */
295 if ((fabs(scale - last_scale) <= EPS && fabs(x - last_x) <= EPS && fabs(y - last_y) <= EPS) ||
296 scale == 0.0f)
297 {
298 GHOST_PRINT("Ignoring touchpad input\n");
299 return hr;
300 }
301
302 /* Assume that every gesture is a pan in the beginning.
303 * If it's a pinch, the gesture will be changed below. */
304 if (gesture_state == GESTURE_NONE) {
305 gesture_state = GESTURE_PAN;
306 }
307
308 /* DM doesn't always immediately recognize pinch gestures,
309 * so allow transition from pan to pinch. */
310 if (gesture_state == GESTURE_PAN) {
311 if (fabs(scale - PINCH_SCALE_FACTOR) > EPS) {
312 gesture_state = GESTURE_PINCH;
313 }
314 }
315
316 /* This state machine is used here because:
317 * 1. Pinch and pan gestures must be differentiated and cannot be processed at the same time
318 * because XY transform values become nonsensical during pinch gesture.
319 * 2. GHOST requires delta values for events while DM provides transformation matrix of the
320 * current gesture.
321 * 3. GHOST events accept integer values while DM values are non-integer.
322 * Truncated fractional parts are accumulated and accounted for in following updates.
323 */
324 switch (gesture_state) {
325 case GESTURE_PINCH: {
326 int32_t dscale = roundf(scale - last_scale);
327
328 last_scale += dscale;
329
330 accumulated_values.scale += dscale;
331 break;
332 }
333 case GESTURE_PAN: {
334 int32_t dx = roundf(x - last_x);
335 int32_t dy = roundf(y - last_y);
336
337 last_x += dx;
338 last_y += dy;
339
340 accumulated_values.x += dx;
341 accumulated_values.y += dy;
342 break;
343 }
344 case GESTURE_NONE:
345 break;
346 }
347
348 return hr;
349}
#define FALSE
#define GHOST_ASSERT(x, info)
#define GHOST_PRINT(x)
#define DM_CHECK_RESULT_AND_EXIT_EARLY(hr, failMessage)
#define PINCH_SCALE_FACTOR
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
SIMD_FORCE_INLINE btVector3 transform(const btVector3 &point) const
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
#define roundf(x)
ccl_device_inline float2 fabs(const float2 a)
#define L
#define EPS
Definition unit.cc:42