Blender V4.5
GHOST_Wintab.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2021-2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#define _USE_MATH_DEFINES
10
11#include "GHOST_Wintab.hh"
12
13GHOST_Wintab *GHOST_Wintab::loadWintab(HWND hwnd)
14{
15 /* Load Wintab library if available. */
16 auto handle = unique_hmodule(::LoadLibrary("Wintab32.dll"), &::FreeLibrary);
17 if (!handle) {
18 return nullptr;
19 }
20
21 /* Get Wintab functions. */
22
23 auto info = (GHOST_WIN32_WTInfo)::GetProcAddress(handle.get(), "WTInfoA");
24 if (!info) {
25 return nullptr;
26 }
27
28 auto open = (GHOST_WIN32_WTOpen)::GetProcAddress(handle.get(), "WTOpenA");
29 if (!open) {
30 return nullptr;
31 }
32
33 auto get = (GHOST_WIN32_WTGet)::GetProcAddress(handle.get(), "WTGetA");
34 if (!get) {
35 return nullptr;
36 }
37
38 auto set = (GHOST_WIN32_WTSet)::GetProcAddress(handle.get(), "WTSetA");
39 if (!set) {
40 return nullptr;
41 }
42
43 auto close = (GHOST_WIN32_WTClose)::GetProcAddress(handle.get(), "WTClose");
44 if (!close) {
45 return nullptr;
46 }
47
48 auto packetsGet = (GHOST_WIN32_WTPacketsGet)::GetProcAddress(handle.get(), "WTPacketsGet");
49 if (!packetsGet) {
50 return nullptr;
51 }
52
53 auto queueSizeGet = (GHOST_WIN32_WTQueueSizeGet)::GetProcAddress(handle.get(), "WTQueueSizeGet");
54 if (!queueSizeGet) {
55 return nullptr;
56 }
57
58 auto queueSizeSet = (GHOST_WIN32_WTQueueSizeSet)::GetProcAddress(handle.get(), "WTQueueSizeSet");
59 if (!queueSizeSet) {
60 return nullptr;
61 }
62
63 auto enable = (GHOST_WIN32_WTEnable)::GetProcAddress(handle.get(), "WTEnable");
64 if (!enable) {
65 return nullptr;
66 }
67
68 auto overlap = (GHOST_WIN32_WTOverlap)::GetProcAddress(handle.get(), "WTOverlap");
69 if (!overlap) {
70 return nullptr;
71 }
72
73 /* Build Wintab context. */
74
75 LOGCONTEXT lc = {0};
76 if (!info(WTI_DEFSYSCTX, 0, &lc)) {
77 return nullptr;
78 }
79
80 Coord tablet, system;
81 extractCoordinates(lc, tablet, system);
82 modifyContext(lc);
83
84 /* The Wintab spec says we must open the context disabled if we are using cursor masks. */
85 auto hctx = unique_hctx(open(hwnd, &lc, FALSE), close);
86 if (!hctx) {
87 return nullptr;
88 }
89
90 /* Wintab provides no way to determine the maximum queue size aside from checking if attempts
91 * to change the queue size are successful. */
92 const int maxQueue = 500;
93 /* < 0 should realistically never happen, but given we cast to size_t later on better safe than
94 * sorry. */
95 int queueSize = max(0, queueSizeGet(hctx.get()));
96
97 while (queueSize < maxQueue) {
98 int testSize = min(queueSize + 16, maxQueue);
99 if (queueSizeSet(hctx.get(), testSize)) {
100 queueSize = testSize;
101 }
102 else {
103 /* From Windows Wintab Documentation for WTQueueSizeSet:
104 * "If the return value is zero, the context has no queue because the function deletes the
105 * original queue before attempting to create a new one. The application must continue
106 * calling the function with a smaller queue size until the function returns a non - zero
107 * value."
108 *
109 * In our case we start with a known valid queue size and in the event of failure roll
110 * back to the last valid queue size. The Wintab spec dates back to 16 bit Windows, thus
111 * assumes memory recently deallocated may not be available, which is no longer a practical
112 * concern. */
113 if (!queueSizeSet(hctx.get(), queueSize)) {
114 /* If a previously valid queue size is no longer valid, there is likely something wrong in
115 * the Wintab implementation and we should not use it. */
116 return nullptr;
117 }
118 break;
119 }
120 }
121
122 int sanityQueueSize = queueSizeGet(hctx.get());
123 WINTAB_PRINTF("HCTX %p %s queueSize: %d, queueSizeGet: %d\n",
124 hctx.get(),
125 __func__,
126 queueSize,
127 sanityQueueSize);
128
129 WINTAB_PRINTF("Loaded Wintab context %p\n", hctx.get());
130
131 return new GHOST_Wintab(std::move(handle),
132 info,
133 get,
134 set,
135 packetsGet,
136 enable,
137 overlap,
138 std::move(hctx),
139 tablet,
140 system,
141 size_t(queueSize));
142}
143
144void GHOST_Wintab::modifyContext(LOGCONTEXT &lc)
145{
146 lc.lcPktData = PACKETDATA;
147 lc.lcPktMode = PACKETMODE;
148 lc.lcMoveMask = PACKETDATA;
149 lc.lcOptions |= CXO_CSRMESSAGES | CXO_MESSAGES;
150
151 /* Tablet scaling is handled manually because some drivers don't handle HIDPI or multi-display
152 * correctly; reset tablet scale factors to un-scaled tablet coordinates. */
153 lc.lcOutOrgX = lc.lcInOrgX;
154 lc.lcOutOrgY = lc.lcInOrgY;
155 lc.lcOutExtX = lc.lcInExtX;
156 lc.lcOutExtY = lc.lcInExtY;
157}
158
159void GHOST_Wintab::extractCoordinates(LOGCONTEXT &lc, Coord &tablet, Coord &system)
160{
161 tablet.x.org = lc.lcInOrgX;
162 tablet.x.ext = lc.lcInExtX;
163 tablet.y.org = lc.lcInOrgY;
164 tablet.y.ext = lc.lcInExtY;
165
166 system.x.org = lc.lcSysOrgX;
167 system.x.ext = lc.lcSysExtX;
168 system.y.org = lc.lcSysOrgY;
169 /* Wintab maps y origin to the tablet's bottom; invert y to match Windows y origin mapping to the
170 * screen top. */
171 system.y.ext = -lc.lcSysExtY;
172}
173
174GHOST_Wintab::GHOST_Wintab(unique_hmodule handle,
178 GHOST_WIN32_WTPacketsGet packetsGet,
180 GHOST_WIN32_WTOverlap overlap,
181 unique_hctx hctx,
182 Coord tablet,
183 Coord system,
184 size_t queueSize)
185 : m_handle{std::move(handle)},
186 m_fpInfo{info},
187 m_fpGet{get},
188 m_fpSet{set},
189 m_fpPacketsGet{packetsGet},
190 m_fpEnable{enable},
191 m_fpOverlap{overlap},
192 m_context{std::move(hctx)},
193 m_tabletCoord{tablet},
194 m_systemCoord{system},
195 m_pkts{queueSize}
196{
197 m_fpInfo(WTI_INTERFACE, IFC_NDEVICES, &m_numDevices);
198 WINTAB_PRINTF("Wintab Devices: %d\n", m_numDevices);
199
201
202 /* Debug info. */
203 printContextDebugInfo();
204}
205
207{
208 WINTAB_PRINTF("Closing Wintab context %p\n", m_context.get());
209}
210
212{
213 m_fpEnable(m_context.get(), true);
214 m_enabled = true;
215}
216
218{
219 if (m_focused) {
220 loseFocus();
221 }
222 m_fpEnable(m_context.get(), false);
223 m_enabled = false;
224}
225
227{
228 m_fpOverlap(m_context.get(), true);
229 m_focused = true;
230}
231
233{
234 if (m_lastTabletData.Active != GHOST_kTabletModeNone) {
235 leaveRange();
236 }
237
238 /* Mouse mode of tablet or display layout may change when Wintab or Window is inactive. Don't
239 * trust for mouse movement until re-verified. */
240 m_coordTrusted = false;
241
242 m_fpOverlap(m_context.get(), false);
243 m_focused = false;
244}
245
247{
248 /* Button state can't be tracked while out of range, reset it. */
249 m_buttons = 0;
250 /* Set to none to indicate tablet is inactive. */
251 m_lastTabletData = GHOST_TABLET_DATA_NONE;
252 /* Clear the packet queue. */
253 m_fpPacketsGet(m_context.get(), m_pkts.size(), m_pkts.data());
254}
255
257{
258 LOGCONTEXT lc = {0};
259
260 if (m_fpInfo(WTI_DEFSYSCTX, 0, &lc)) {
261 extractCoordinates(lc, m_tabletCoord, m_systemCoord);
262 modifyContext(lc);
263
264 m_fpSet(m_context.get(), &lc);
265 }
266}
267
269{
270 AXIS Pressure, Orientation[3];
271
272 BOOL pressureSupport = m_fpInfo(WTI_DEVICES, DVC_NPRESSURE, &Pressure);
273 m_maxPressure = pressureSupport ? Pressure.axMax : 0;
274 WINTAB_PRINTF("HCTX %p %s maxPressure: %d\n", m_context.get(), __func__, m_maxPressure);
275
276 BOOL tiltSupport = m_fpInfo(WTI_DEVICES, DVC_ORIENTATION, &Orientation);
277 /* Check if tablet supports azimuth [0] and altitude [1], encoded in axResolution. */
278 if (tiltSupport && Orientation[0].axResolution && Orientation[1].axResolution) {
279 m_maxAzimuth = Orientation[0].axMax;
280 m_maxAltitude = Orientation[1].axMax;
281 }
282 else {
283 m_maxAzimuth = m_maxAltitude = 0;
284 }
285 WINTAB_PRINTF("HCTX %p %s maxAzimuth: %d, maxAltitude: %d\n",
286 m_context.get(),
287 __func__,
288 m_maxAzimuth,
289 m_maxAltitude);
290}
291
293{
294 /* Update number of connected Wintab digitizers. */
295 if (LOWORD(lParam) == WTI_INTERFACE && HIWORD(lParam) == IFC_NDEVICES) {
296 m_fpInfo(WTI_INTERFACE, IFC_NDEVICES, &m_numDevices);
297 WINTAB_PRINTF("HCTX %p %s numDevices: %d\n", m_context.get(), __func__, m_numDevices);
298 }
299}
300
302{
303 return m_numDevices > 0;
304}
305
307{
308 return m_lastTabletData;
309}
310
311void GHOST_Wintab::getInput(std::vector<GHOST_WintabInfoWin32> &outWintabInfo)
312{
313 const int numPackets = m_fpPacketsGet(m_context.get(), m_pkts.size(), m_pkts.data());
314 outWintabInfo.reserve(numPackets);
315
316 for (int i = 0; i < numPackets; i++) {
317 const PACKET pkt = m_pkts[i];
319
320 /* % 3 for multiple devices ("DualTrack"). */
321 switch (pkt.pkCursor % 3) {
322 case 0:
323 /* Puck - processed as mouse. */
324 out.tabletData.Active = GHOST_kTabletModeNone;
325 break;
326 case 1:
327 out.tabletData.Active = GHOST_kTabletModeStylus;
328 break;
329 case 2:
330 out.tabletData.Active = GHOST_kTabletModeEraser;
331 break;
332 }
333
334 out.x = pkt.pkX;
335 out.y = pkt.pkY;
336
337 if (m_maxPressure > 0) {
338 out.tabletData.Pressure = float(pkt.pkNormalPressure) / float(m_maxPressure);
339 }
340
341 if ((m_maxAzimuth > 0) && (m_maxAltitude > 0)) {
342 /* From the wintab spec:
343 * orAzimuth: Specifies the clockwise rotation of the cursor about the z axis through a
344 * full circular range.
345 * orAltitude: Specifies the angle with the x-y plane through a signed, semicircular range.
346 * Positive values specify an angle upward toward the positive z axis; negative values
347 * specify an angle downward toward the negative z axis.
348 *
349 * wintab.h defines orAltitude as a `uint` but documents orAltitude as positive for upward
350 * angles and negative for downward angles. WACOM uses negative altitude values to show that
351 * the pen is inverted; therefore we cast orAltitude as an `int` and then use the absolute
352 * value.
353 */
354
355 ORIENTATION ort = pkt.pkOrientation;
356
357 /* Convert raw fixed point data to radians. */
358 float altRad = float((fabs(float(ort.orAltitude)) / float(m_maxAltitude)) * M_PI_2);
359 float azmRad = float((float(ort.orAzimuth) / float(m_maxAzimuth)) * M_PI * 2.0);
360
361 /* Find length of the stylus' projected vector on the XY plane. */
362 float vecLen = cos(altRad);
363
364 /* From there calculate X and Y components based on azimuth. */
365
366 /* Blender expects: -1.0f (left) to +1.0f (right). */
367 out.tabletData.Xtilt = sin(azmRad) * vecLen;
368
369 /* Blender expects: -1.0f (away from user) to +1.0f (toward user). */
370 out.tabletData.Ytilt = -float(sin(M_PI_2 - azmRad) * vecLen);
371 }
372
373 out.time = pkt.pkTime;
374
375 /* Some Wintab libraries don't handle relative button input, so we track button presses
376 * manually. */
377 DWORD buttonsChanged = m_buttons ^ pkt.pkButtons;
378 /* We only needed the prior button state to compare to current, so we can overwrite it now. */
379 m_buttons = pkt.pkButtons;
380
381 /* Iterate over button flag indices until all flags are clear. */
382 for (WORD buttonIndex = 0; buttonsChanged; buttonIndex++, buttonsChanged >>= 1) {
383 if (buttonsChanged & 1) {
384 GHOST_TButton button = mapWintabToGhostButton(pkt.pkCursor, buttonIndex);
385
386 if (button != GHOST_kButtonMaskNone) {
387 /* If this is not the first button found, push info for the prior Wintab button. */
388 if (out.button != GHOST_kButtonMaskNone) {
389 outWintabInfo.push_back(out);
390 }
391
392 out.button = button;
393
394 DWORD buttonFlag = 1 << buttonIndex;
395 out.type = pkt.pkButtons & buttonFlag ? GHOST_kEventButtonDown : GHOST_kEventButtonUp;
396 }
397 }
398 }
399
400 outWintabInfo.push_back(out);
401 }
402
403 if (!outWintabInfo.empty()) {
404 m_lastTabletData = outWintabInfo.back().tabletData;
405 }
406}
407
408GHOST_TButton GHOST_Wintab::mapWintabToGhostButton(uint cursor, WORD physicalButton)
409{
410 const WORD numButtons = 32;
411 BYTE logicalButtons[numButtons] = {0};
412 BYTE systemButtons[numButtons] = {0};
413
414 if (!m_fpInfo(WTI_CURSORS + cursor, CSR_BUTTONMAP, &logicalButtons) ||
415 !m_fpInfo(WTI_CURSORS + cursor, CSR_SYSBTNMAP, &systemButtons))
416 {
418 }
419
420 if (physicalButton >= numButtons) {
422 }
423
424 BYTE lb = logicalButtons[physicalButton];
425
426 if (lb >= numButtons) {
428 }
429
430 switch (systemButtons[lb]) {
431 case SBN_LCLICK:
433 case SBN_RCLICK:
435 case SBN_MCLICK:
437 default:
439 }
440}
441
442void GHOST_Wintab::mapWintabToSysCoordinates(int x_in, int y_in, int &x_out, int &y_out)
443{
444 /* Maps from range [in.org, in.org + abs(in.ext)] to [out.org, out.org + abs(out.ext)], in
445 * reverse if in.ext and out.ext have differing sign. */
446 auto remap = [](int inPoint, Range in, Range out) -> int {
447 int absInExt = abs(in.ext);
448 int absOutExt = abs(out.ext);
449
450 /* Translate input from range [in.org, in.org + absInExt] to [0, absInExt] */
451 int inMagnitude = inPoint - in.org;
452
453 /* If signs of extents differ, reverse input over range. */
454 if ((in.ext < 0) != (out.ext < 0)) {
455 inMagnitude = absInExt - inMagnitude;
456 }
457
458 /* Scale from [0, absInExt] to [0, absOutExt]. */
459 int outMagnitude = inMagnitude * absOutExt / absInExt;
460
461 /* Translate from range [0, absOutExt] to [out.org, out.org + absOutExt]. */
462 int outPoint = outMagnitude + out.org;
463
464 return outPoint;
465 };
466
467 x_out = remap(x_in, m_tabletCoord.x, m_systemCoord.x);
468 y_out = remap(y_in, m_tabletCoord.y, m_systemCoord.y);
469}
470
472{
473 return m_coordTrusted;
474}
475
476bool GHOST_Wintab::testCoordinates(int sysX, int sysY, int wtX, int wtY)
477{
478 mapWintabToSysCoordinates(wtX, wtY, wtX, wtY);
479
480 /* Allow off by one pixel tolerance in case of rounding error. */
481 if (abs(sysX - wtX) <= 1 && abs(sysY - wtY) <= 1) {
482 m_coordTrusted = true;
483 return true;
484 }
485 else {
486 m_coordTrusted = false;
487 return false;
488 }
489}
490
491bool GHOST_Wintab::m_debug = false;
492
494{
495 m_debug = debug;
496}
497
499{
500 return m_debug;
501}
502
503void GHOST_Wintab::printContextDebugInfo()
504{
505 if (!m_debug) {
506 return;
507 }
508
509 /* Print button maps. */
510 BYTE logicalButtons[32] = {0};
511 BYTE systemButtons[32] = {0};
512 for (int i = 0; i < 3; i++) {
513 printf("initializeWintab cursor %d buttons\n", i);
514 uint lbut = m_fpInfo(WTI_CURSORS + i, CSR_BUTTONMAP, &logicalButtons);
515 if (lbut) {
516 printf("%d", logicalButtons[0]);
517 for (int j = 1; j < lbut; j++) {
518 printf(", %d", logicalButtons[j]);
519 }
520 printf("\n");
521 }
522 else {
523 printf("logical button error\n");
524 }
525 uint sbut = m_fpInfo(WTI_CURSORS + i, CSR_SYSBTNMAP, &systemButtons);
526 if (sbut) {
527 printf("%d", systemButtons[0]);
528 for (int j = 1; j < sbut; j++) {
529 printf(", %d", systemButtons[j]);
530 }
531 printf("\n");
532 }
533 else {
534 printf("system button error\n");
535 }
536 }
537
538 /* Print context information. */
539
540 /* Print open context constraints. */
541 uint maxcontexts, opencontexts;
542 m_fpInfo(WTI_INTERFACE, IFC_NCONTEXTS, &maxcontexts);
543 m_fpInfo(WTI_STATUS, STA_CONTEXTS, &opencontexts);
544 printf("%u max contexts, %u open contexts\n", maxcontexts, opencontexts);
545
546 /* Print system information. */
547 printf("left: %d, top: %d, width: %d, height: %d\n",
548 ::GetSystemMetrics(SM_XVIRTUALSCREEN),
549 ::GetSystemMetrics(SM_YVIRTUALSCREEN),
550 ::GetSystemMetrics(SM_CXVIRTUALSCREEN),
551 ::GetSystemMetrics(SM_CYVIRTUALSCREEN));
552
553 auto printContextRanges = [](LOGCONTEXT &lc) {
554 printf("lcInOrgX: %d, lcInOrgY: %d, lcInExtX: %d, lcInExtY: %d\n",
555 lc.lcInOrgX,
556 lc.lcInOrgY,
557 lc.lcInExtX,
558 lc.lcInExtY);
559 printf("lcOutOrgX: %d, lcOutOrgY: %d, lcOutExtX: %d, lcOutExtY: %d\n",
560 lc.lcOutOrgX,
561 lc.lcOutOrgY,
562 lc.lcOutExtX,
563 lc.lcOutExtY);
564 printf("lcSysOrgX: %d, lcSysOrgY: %d, lcSysExtX: %d, lcSysExtY: %d\n",
565 lc.lcSysOrgX,
566 lc.lcSysOrgY,
567 lc.lcSysExtX,
568 lc.lcSysExtY);
569 };
570
571 LOGCONTEXT lc;
572
573 /* Print system context. */
574 m_fpInfo(WTI_DEFSYSCTX, 0, &lc);
575 printf("WTI_DEFSYSCTX\n");
576 printContextRanges(lc);
577
578 /* Print system context, manually populated. */
579 m_fpInfo(WTI_DEFSYSCTX, CTX_INORGX, &lc.lcInOrgX);
580 m_fpInfo(WTI_DEFSYSCTX, CTX_INORGY, &lc.lcInOrgY);
581 m_fpInfo(WTI_DEFSYSCTX, CTX_INEXTX, &lc.lcInExtX);
582 m_fpInfo(WTI_DEFSYSCTX, CTX_INEXTY, &lc.lcInExtY);
583 m_fpInfo(WTI_DEFSYSCTX, CTX_OUTORGX, &lc.lcOutOrgX);
584 m_fpInfo(WTI_DEFSYSCTX, CTX_OUTORGY, &lc.lcOutOrgY);
585 m_fpInfo(WTI_DEFSYSCTX, CTX_OUTEXTX, &lc.lcOutExtX);
586 m_fpInfo(WTI_DEFSYSCTX, CTX_OUTEXTY, &lc.lcOutExtY);
587 m_fpInfo(WTI_DEFSYSCTX, CTX_SYSORGX, &lc.lcSysOrgX);
588 m_fpInfo(WTI_DEFSYSCTX, CTX_SYSORGY, &lc.lcSysOrgY);
589 m_fpInfo(WTI_DEFSYSCTX, CTX_SYSEXTX, &lc.lcSysExtX);
590 m_fpInfo(WTI_DEFSYSCTX, CTX_SYSEXTY, &lc.lcSysExtY);
591 printf("WTI_DEFSYSCTX CTX_*\n");
592 printContextRanges(lc);
593
594 for (uint i = 0; i < m_numDevices; i++) {
595 /* Print individual device system context. */
596 m_fpInfo(WTI_DSCTXS + i, 0, &lc);
597 printf("WTI_DSCTXS %u\n", i);
598 printContextRanges(lc);
599
600 /* Print individual device system context, manually populated. */
601 m_fpInfo(WTI_DSCTXS + i, CTX_INORGX, &lc.lcInOrgX);
602 m_fpInfo(WTI_DSCTXS + i, CTX_INORGY, &lc.lcInOrgY);
603 m_fpInfo(WTI_DSCTXS + i, CTX_INEXTX, &lc.lcInExtX);
604 m_fpInfo(WTI_DSCTXS + i, CTX_INEXTY, &lc.lcInExtY);
605 m_fpInfo(WTI_DSCTXS + i, CTX_OUTORGX, &lc.lcOutOrgX);
606 m_fpInfo(WTI_DSCTXS + i, CTX_OUTORGY, &lc.lcOutOrgY);
607 m_fpInfo(WTI_DSCTXS + i, CTX_OUTEXTX, &lc.lcOutExtX);
608 m_fpInfo(WTI_DSCTXS + i, CTX_OUTEXTY, &lc.lcOutExtY);
609 m_fpInfo(WTI_DSCTXS + i, CTX_SYSORGX, &lc.lcSysOrgX);
610 m_fpInfo(WTI_DSCTXS + i, CTX_SYSORGY, &lc.lcSysOrgY);
611 m_fpInfo(WTI_DSCTXS + i, CTX_SYSEXTX, &lc.lcSysExtX);
612 m_fpInfo(WTI_DSCTXS + i, CTX_SYSEXTY, &lc.lcSysExtY);
613 printf("WTI_DSCTX %u CTX_*\n", i);
614 printContextRanges(lc);
615
616 /* Print device axis. */
617 AXIS axis_x, axis_y;
618 m_fpInfo(WTI_DEVICES + i, DVC_X, &axis_x);
619 m_fpInfo(WTI_DEVICES + i, DVC_Y, &axis_y);
620 printf("WTI_DEVICES %u axis_x org: %d, axis_y org: %d axis_x ext: %d, axis_y ext: %d\n",
621 i,
622 axis_x.axMin,
623 axis_y.axMin,
624 axis_x.axMax - axis_x.axMin + 1,
625 axis_y.axMax - axis_y.axMin + 1);
626 }
627
628 /* Other stuff while we have a log-context. */
629 printf("sysmode %d\n", lc.lcSysMode);
630}
#define M_PI_2
#define M_PI
unsigned int uint
#define FALSE
@ GHOST_kEventButtonUp
@ GHOST_kEventButtonDown
static const GHOST_TabletData GHOST_TABLET_DATA_NONE
@ GHOST_kTabletModeEraser
@ GHOST_kTabletModeStylus
@ GHOST_kTabletModeNone
GHOST_TButton
@ GHOST_kButtonMaskRight
@ GHOST_kButtonMaskNone
@ GHOST_kButtonMaskLeft
@ GHOST_kButtonMaskMiddle
BOOL(API * GHOST_WIN32_WTOverlap)(HCTX, BOOL)
BOOL(API * GHOST_WIN32_WTEnable)(HCTX, BOOL)
#define PACKETMODE
std::unique_ptr< std::remove_pointer_t< HCTX >, GHOST_WIN32_WTClose > unique_hctx
HCTX(API * GHOST_WIN32_WTOpen)(HWND, LPLOGCONTEXTA, BOOL)
BOOL(API * GHOST_WIN32_WTClose)(HCTX)
BOOL(API * GHOST_WIN32_WTGet)(HCTX, LPLOGCONTEXTA)
#define WINTAB_PRINTF(x,...)
#define PACKETDATA
std::unique_ptr< std::remove_pointer_t< HMODULE >, decltype(&::FreeLibrary)> unique_hmodule
BOOL(API * GHOST_WIN32_WTQueueSizeSet)(HCTX, int)
BOOL(API * GHOST_WIN32_WTSet)(HCTX, LPLOGCONTEXTA)
UINT(API * GHOST_WIN32_WTInfo)(UINT, UINT, LPVOID)
int(API * GHOST_WIN32_WTPacketsGet)(HCTX, int, LPVOID)
int(API * GHOST_WIN32_WTQueueSizeGet)(HCTX)
void mapWintabToSysCoordinates(int x_in, int y_in, int &x_out, int &y_out)
GHOST_TabletData getLastTabletData()
bool devicesPresent()
void getInput(std::vector< GHOST_WintabInfoWin32 > &outWintabInfo)
void processInfoChange(LPARAM lParam)
void updateCursorInfo()
void remapCoordinates()
static GHOST_Wintab * loadWintab(HWND hwnd)
bool trustCoordinates()
static bool getDebug()
bool testCoordinates(int sysX, int sysY, int wtX, int wtY)
static void setDebug(bool debug)
#define sin
#define cos
#define in
#define abs
#define out
#define printf(...)
ccl_device_inline float2 fabs(const float2 a)
std::shared_ptr< const T > get(const GenericKey &key, FunctionRef< std::unique_ptr< T >()> compute_fn)
#define min(a, b)
Definition sort.cc:36
i
Definition text_draw.cc:230
max
Definition text_draw.cc:251