Blender V4.3
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
9#define _USE_MATH_DEFINES
10
11#include "GHOST_Wintab.hh"
12
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. */
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 out.tabletData.Xtilt = sin(azmRad) * vecLen;
366 out.tabletData.Ytilt = float(sin(M_PI_2 - azmRad) * vecLen);
367 }
368
369 out.time = pkt.pkTime;
370
371 /* Some Wintab libraries don't handle relative button input, so we track button presses
372 * manually. */
373 DWORD buttonsChanged = m_buttons ^ pkt.pkButtons;
374 /* We only needed the prior button state to compare to current, so we can overwrite it now. */
375 m_buttons = pkt.pkButtons;
376
377 /* Iterate over button flag indices until all flags are clear. */
378 for (WORD buttonIndex = 0; buttonsChanged; buttonIndex++, buttonsChanged >>= 1) {
379 if (buttonsChanged & 1) {
380 GHOST_TButton button = mapWintabToGhostButton(pkt.pkCursor, buttonIndex);
381
382 if (button != GHOST_kButtonMaskNone) {
383 /* If this is not the first button found, push info for the prior Wintab button. */
384 if (out.button != GHOST_kButtonMaskNone) {
385 outWintabInfo.push_back(out);
386 }
387
388 out.button = button;
389
390 DWORD buttonFlag = 1 << buttonIndex;
391 out.type = pkt.pkButtons & buttonFlag ? GHOST_kEventButtonDown : GHOST_kEventButtonUp;
392 }
393 }
394 }
395
396 outWintabInfo.push_back(out);
397 }
398
399 if (!outWintabInfo.empty()) {
400 m_lastTabletData = outWintabInfo.back().tabletData;
401 }
402}
403
404GHOST_TButton GHOST_Wintab::mapWintabToGhostButton(uint cursor, WORD physicalButton)
405{
406 const WORD numButtons = 32;
407 BYTE logicalButtons[numButtons] = {0};
408 BYTE systemButtons[numButtons] = {0};
409
410 if (!m_fpInfo(WTI_CURSORS + cursor, CSR_BUTTONMAP, &logicalButtons) ||
411 !m_fpInfo(WTI_CURSORS + cursor, CSR_SYSBTNMAP, &systemButtons))
412 {
414 }
415
416 if (physicalButton >= numButtons) {
418 }
419
420 BYTE lb = logicalButtons[physicalButton];
421
422 if (lb >= numButtons) {
424 }
425
426 switch (systemButtons[lb]) {
427 case SBN_LCLICK:
429 case SBN_RCLICK:
431 case SBN_MCLICK:
433 default:
435 }
436}
437
438void GHOST_Wintab::mapWintabToSysCoordinates(int x_in, int y_in, int &x_out, int &y_out)
439{
440 /* Maps from range [in.org, in.org + abs(in.ext)] to [out.org, out.org + abs(out.ext)], in
441 * reverse if in.ext and out.ext have differing sign. */
442 auto remap = [](int inPoint, Range in, Range out) -> int {
443 int absInExt = abs(in.ext);
444 int absOutExt = abs(out.ext);
445
446 /* Translate input from range [in.org, in.org + absInExt] to [0, absInExt] */
447 int inMagnitude = inPoint - in.org;
448
449 /* If signs of extents differ, reverse input over range. */
450 if ((in.ext < 0) != (out.ext < 0)) {
451 inMagnitude = absInExt - inMagnitude;
452 }
453
454 /* Scale from [0, absInExt] to [0, absOutExt]. */
455 int outMagnitude = inMagnitude * absOutExt / absInExt;
456
457 /* Translate from range [0, absOutExt] to [out.org, out.org + absOutExt]. */
458 int outPoint = outMagnitude + out.org;
459
460 return outPoint;
461 };
462
463 x_out = remap(x_in, m_tabletCoord.x, m_systemCoord.x);
464 y_out = remap(y_in, m_tabletCoord.y, m_systemCoord.y);
465}
466
468{
469 return m_coordTrusted;
470}
471
472bool GHOST_Wintab::testCoordinates(int sysX, int sysY, int wtX, int wtY)
473{
474 mapWintabToSysCoordinates(wtX, wtY, wtX, wtY);
475
476 /* Allow off by one pixel tolerance in case of rounding error. */
477 if (abs(sysX - wtX) <= 1 && abs(sysY - wtY) <= 1) {
478 m_coordTrusted = true;
479 return true;
480 }
481 else {
482 m_coordTrusted = false;
483 return false;
484 }
485}
486
487bool GHOST_Wintab::m_debug = false;
488
490{
491 m_debug = debug;
492}
493
495{
496 return m_debug;
497}
498
499void GHOST_Wintab::printContextDebugInfo()
500{
501 if (!m_debug) {
502 return;
503 }
504
505 /* Print button maps. */
506 BYTE logicalButtons[32] = {0};
507 BYTE systemButtons[32] = {0};
508 for (int i = 0; i < 3; i++) {
509 printf("initializeWintab cursor %d buttons\n", i);
510 uint lbut = m_fpInfo(WTI_CURSORS + i, CSR_BUTTONMAP, &logicalButtons);
511 if (lbut) {
512 printf("%d", logicalButtons[0]);
513 for (int j = 1; j < lbut; j++) {
514 printf(", %d", logicalButtons[j]);
515 }
516 printf("\n");
517 }
518 else {
519 printf("logical button error\n");
520 }
521 uint sbut = m_fpInfo(WTI_CURSORS + i, CSR_SYSBTNMAP, &systemButtons);
522 if (sbut) {
523 printf("%d", systemButtons[0]);
524 for (int j = 1; j < sbut; j++) {
525 printf(", %d", systemButtons[j]);
526 }
527 printf("\n");
528 }
529 else {
530 printf("system button error\n");
531 }
532 }
533
534 /* Print context information. */
535
536 /* Print open context constraints. */
537 uint maxcontexts, opencontexts;
538 m_fpInfo(WTI_INTERFACE, IFC_NCONTEXTS, &maxcontexts);
539 m_fpInfo(WTI_STATUS, STA_CONTEXTS, &opencontexts);
540 printf("%u max contexts, %u open contexts\n", maxcontexts, opencontexts);
541
542 /* Print system information. */
543 printf("left: %d, top: %d, width: %d, height: %d\n",
544 ::GetSystemMetrics(SM_XVIRTUALSCREEN),
545 ::GetSystemMetrics(SM_YVIRTUALSCREEN),
546 ::GetSystemMetrics(SM_CXVIRTUALSCREEN),
547 ::GetSystemMetrics(SM_CYVIRTUALSCREEN));
548
549 auto printContextRanges = [](LOGCONTEXT &lc) {
550 printf("lcInOrgX: %d, lcInOrgY: %d, lcInExtX: %d, lcInExtY: %d\n",
551 lc.lcInOrgX,
552 lc.lcInOrgY,
553 lc.lcInExtX,
554 lc.lcInExtY);
555 printf("lcOutOrgX: %d, lcOutOrgY: %d, lcOutExtX: %d, lcOutExtY: %d\n",
556 lc.lcOutOrgX,
557 lc.lcOutOrgY,
558 lc.lcOutExtX,
559 lc.lcOutExtY);
560 printf("lcSysOrgX: %d, lcSysOrgY: %d, lcSysExtX: %d, lcSysExtY: %d\n",
561 lc.lcSysOrgX,
562 lc.lcSysOrgY,
563 lc.lcSysExtX,
564 lc.lcSysExtY);
565 };
566
567 LOGCONTEXT lc;
568
569 /* Print system context. */
570 m_fpInfo(WTI_DEFSYSCTX, 0, &lc);
571 printf("WTI_DEFSYSCTX\n");
572 printContextRanges(lc);
573
574 /* Print system context, manually populated. */
575 m_fpInfo(WTI_DEFSYSCTX, CTX_INORGX, &lc.lcInOrgX);
576 m_fpInfo(WTI_DEFSYSCTX, CTX_INORGY, &lc.lcInOrgY);
577 m_fpInfo(WTI_DEFSYSCTX, CTX_INEXTX, &lc.lcInExtX);
578 m_fpInfo(WTI_DEFSYSCTX, CTX_INEXTY, &lc.lcInExtY);
579 m_fpInfo(WTI_DEFSYSCTX, CTX_OUTORGX, &lc.lcOutOrgX);
580 m_fpInfo(WTI_DEFSYSCTX, CTX_OUTORGY, &lc.lcOutOrgY);
581 m_fpInfo(WTI_DEFSYSCTX, CTX_OUTEXTX, &lc.lcOutExtX);
582 m_fpInfo(WTI_DEFSYSCTX, CTX_OUTEXTY, &lc.lcOutExtY);
583 m_fpInfo(WTI_DEFSYSCTX, CTX_SYSORGX, &lc.lcSysOrgX);
584 m_fpInfo(WTI_DEFSYSCTX, CTX_SYSORGY, &lc.lcSysOrgY);
585 m_fpInfo(WTI_DEFSYSCTX, CTX_SYSEXTX, &lc.lcSysExtX);
586 m_fpInfo(WTI_DEFSYSCTX, CTX_SYSEXTY, &lc.lcSysExtY);
587 printf("WTI_DEFSYSCTX CTX_*\n");
588 printContextRanges(lc);
589
590 for (uint i = 0; i < m_numDevices; i++) {
591 /* Print individual device system context. */
592 m_fpInfo(WTI_DSCTXS + i, 0, &lc);
593 printf("WTI_DSCTXS %u\n", i);
594 printContextRanges(lc);
595
596 /* Print individual device system context, manually populated. */
597 m_fpInfo(WTI_DSCTXS + i, CTX_INORGX, &lc.lcInOrgX);
598 m_fpInfo(WTI_DSCTXS + i, CTX_INORGY, &lc.lcInOrgY);
599 m_fpInfo(WTI_DSCTXS + i, CTX_INEXTX, &lc.lcInExtX);
600 m_fpInfo(WTI_DSCTXS + i, CTX_INEXTY, &lc.lcInExtY);
601 m_fpInfo(WTI_DSCTXS + i, CTX_OUTORGX, &lc.lcOutOrgX);
602 m_fpInfo(WTI_DSCTXS + i, CTX_OUTORGY, &lc.lcOutOrgY);
603 m_fpInfo(WTI_DSCTXS + i, CTX_OUTEXTX, &lc.lcOutExtX);
604 m_fpInfo(WTI_DSCTXS + i, CTX_OUTEXTY, &lc.lcOutExtY);
605 m_fpInfo(WTI_DSCTXS + i, CTX_SYSORGX, &lc.lcSysOrgX);
606 m_fpInfo(WTI_DSCTXS + i, CTX_SYSORGY, &lc.lcSysOrgY);
607 m_fpInfo(WTI_DSCTXS + i, CTX_SYSEXTX, &lc.lcSysExtX);
608 m_fpInfo(WTI_DSCTXS + i, CTX_SYSEXTY, &lc.lcSysExtY);
609 printf("WTI_DSCTX %u CTX_*\n", i);
610 printContextRanges(lc);
611
612 /* Print device axis. */
613 AXIS axis_x, axis_y;
614 m_fpInfo(WTI_DEVICES + i, DVC_X, &axis_x);
615 m_fpInfo(WTI_DEVICES + i, DVC_Y, &axis_y);
616 printf("WTI_DEVICES %u axis_x org: %d, axis_y org: %d axis_x ext: %d, axis_y ext: %d\n",
617 i,
618 axis_x.axMin,
619 axis_y.axMin,
620 axis_x.axMax - axis_x.axMin + 1,
621 axis_y.axMax - axis_y.axMin + 1);
622 }
623
624 /* Other stuff while we have a log-context. */
625 printf("sysmode %d\n", lc.lcSysMode);
626}
#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 printf
draw_view in_light_buf[] float
ccl_device_inline float2 fabs(const float2 a)
ccl_device_inline float3 cos(float3 v)
std::shared_ptr< const T > get(const GenericKey &key, FunctionRef< std::unique_ptr< T >()> compute_fn)
#define min(a, b)
Definition sort.c:32
GHOST_TTabletMode Active
GHOST_TabletData tabletData
float max
ccl_device_inline int abs(int x)
Definition util/math.h:120