1 module derelict.wintab.tablet;
2 
3 import core.sys.windows.windows;
4 import derelict.wintab.wintab;
5 import dlangui.core.logger;
6 import dlangui.core.signals;
7 
8 interface TabletPositionHandler {
9     void onPositionChange(double x, double y, double pressure, uint buttons);
10 }
11 
12 interface TabletProximityHandler {
13     void onProximity(bool enter);
14 }
15 
16 class Tablet {
17     Signal!TabletPositionHandler onPosition;
18     Signal!TabletProximityHandler onProximity;
19 
20     private HCTX _hCtx;
21     private LOGCONTEXT glogContext;
22     private HWND _hWnd;
23     /// returns true if initialized
24     @property bool isInitialized() {
25         if (_hCtx is null)
26             return false;
27         uint numDevices;
28         if (!WTInfo(WTI_INTERFACE, IFC_NDEVICES, cast(void*)&numDevices))
29             return false;
30         if (!numDevices)
31             return false;
32         return _proximity;
33     }
34     /// initialize tablet API for window
35     bool init(HWND hWnd) {
36 
37         try {
38             DerelictWintab.load();
39         } catch (Exception e) {
40             Log.e("Cannot load wintab32.dll");
41             return false;
42         }
43 
44         uint res;
45 
46         _hWnd = hWnd;
47 
48         try {
49             if (!WTInfo(0, 0, null)) {
50                 Log.e("WinTab services not available");
51                 //return 1;
52                 return false;
53             }
54         } catch (Exception e) {
55             Log.e("Exception in WTInfo", e);
56             return false;
57         }
58 
59         ushort thisVersion;
60         res = WTInfo(WTI_INTERFACE, IFC_SPECVERSION, &thisVersion);
61 
62         glogContext.lcOptions |= CXO_SYSTEM;
63         uint wWTInfoRetVal = WTInfo(WTI_DEFSYSCTX, 0, &glogContext);
64         assert(glogContext.lcOptions & CXO_SYSTEM);
65         assert(wWTInfoRetVal == glogContext.sizeof);
66 
67         wchar[50] wname;
68         if (!WTInfo(WTI_DEVICES, DVC_NAME, cast(void*)wname.ptr)) {
69             Log.e("WinTab cannot get device name");
70             return false;
71         }
72         if (wname[0 .. 5] != "WACOM") {
73             Log.e("Not a wacom device");
74             return false;
75         }
76 
77         AXIS[3] tpOri;
78         uint tilt_support = WTInfo(WTI_DEVICES, DVC_ORIENTATION, cast(void*)&tpOri);
79         // load theme from file "theme_default.xml"
80         //Platform.instance.uiTheme = "theme_default";
81 
82         immutable uint PACKETDATA = PK_ALL; // (PK_X | PK_Y | PK_BUTTONS | PK_NORMAL_PRESSURE);
83         immutable uint PACKETMODE = 0; //PK_BUTTONS; // | PK_X | PK_Y | PK_Z;
84 
85         // What data items we want to be included in the tablet packets
86         glogContext.lcPktData = PACKETDATA;
87 
88         // Which packet items should show change in value since the last
89         // packet (referred to as 'relative' data) and which items
90         // should be 'absolute'.
91         glogContext.lcPktMode = PACKETMODE;
92 
93         // This bitfield determines whether or not this context will receive
94         // a packet when a value for each packet field changes.  This is not
95         // supported by the Intuos Wintab.  Your context will always receive
96         // packets, even if there has been no change in the data.
97         glogContext.lcMoveMask = PACKETDATA;
98 
99         glogContext.lcOptions = CXO_MESSAGES; // | CXO_PEN;
100 
101         // Which buttons events will be handled by this context.  lcBtnMask
102         // is a bitfield with one bit per button.
103         glogContext.lcBtnUpMask = glogContext.lcBtnDnMask;
104 
105         AXIS TabletX;
106         AXIS TabletY;
107         // Set the entire tablet as active
108         wWTInfoRetVal = WTInfo( WTI_DEVICES + 0, DVC_X, &TabletX );
109         assert(wWTInfoRetVal == AXIS.sizeof);
110         wWTInfoRetVal = WTInfo( WTI_DEVICES, DVC_Y, &TabletY );
111         assert(wWTInfoRetVal == AXIS.sizeof);
112 
113         glogContext.lcInOrgX = 0;
114         glogContext.lcInOrgY = 0;
115         glogContext.lcInExtX = TabletX.axMax;
116         glogContext.lcInExtY = TabletY.axMax;
117 
118         // Guarantee the output coordinate space to be in screen coordinates.  
119         glogContext.lcOutOrgX = 0; //GetSystemMetrics( SM_XVIRTUALSCREEN );
120         glogContext.lcOutOrgY = 0; //GetSystemMetrics( SM_YVIRTUALSCREEN );
121         glogContext.lcOutExtX = 100000; //GetSystemMetrics( SM_CXVIRTUALSCREEN ); //SM_CXSCREEN );
122 
123         // In Wintab, the tablet origin is lower left.  Move origin to upper left
124         // so that it coincides with screen origin.
125         glogContext.lcOutExtY = -100000; //-GetSystemMetrics( SM_CYVIRTUALSCREEN );	//SM_CYSCREEN );
126 
127         // Leave the system origin and extents as received:
128         // lcSysOrgX, lcSysOrgY, lcSysExtX, lcSysExtY
129 
130         // open the region
131         // The Wintab spec says we must open the context disabled if we are 
132         // using cursor masks.  
133         _hCtx = WTOpen(hWnd, &glogContext, FALSE );
134         return _hCtx !is null;
135     }
136     void uninit() {
137         if (_hCtx) {
138             WTClose(_hCtx);
139             _hCtx = null;
140         }
141     }
142 
143     void onActivate(bool activated) {
144         if (_hCtx) 
145         {
146             WTEnable(_hCtx, activated ? TRUE : FALSE);
147             if (_hCtx && activated)
148             {
149                 WTOverlap(_hCtx, TRUE);
150             }
151         }
152     }
153 
154     bool _proximity;
155     void handleProximity(bool enter) {
156         _proximity = enter;
157         if (onProximity.assigned)
158             onProximity(enter);
159     }
160 
161     private double _lastx = -1;
162     private double _lasty = -1;
163     private double _lastpressure = -1;
164     private uint _lastbuttons = 0;
165     void onPacket(int x, int y, int press, uint buttons) {
166         double xx = x / 100000.0;
167         double yy = y / 100000.0;
168         double pressure = press / 1000.0;
169         if (xx != _lastx || yy != _lasty || pressure != _lastpressure || buttons != _lastbuttons) {
170             if (onPosition.assigned)
171                 onPosition(xx, yy, pressure, buttons);
172             _lastx = xx;
173             _lasty = yy;
174             _lastpressure = pressure;
175             _lastbuttons = buttons;
176         }
177     }
178 
179     bool onUnknownWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, ref LRESULT result) {
180         import std..string : format;
181         PACKET!PK_ALL pkt;
182         switch(message) {
183             case WM_ACTIVATE:
184                 Log.d("WM_ACTIVATE ", wParam);
185                 onActivate(wParam != 0);
186                 break;
187             case WT_CTXOPEN:
188                 Log.d("WT_CTXOPEN");
189                 break;
190             case WT_CTXCLOSE:
191                 Log.d("WT_CTXCLOSE");
192                 break;
193             case WT_CTXUPDATE:
194                 Log.d("WT_CTXUPDATE");
195                 break;
196             case WT_CTXOVERLAP:
197                 Log.d("WT_CTXOVERLAP");
198                 break;
199             case WT_PROXIMITY:
200                 Log.d("WT_PROXIMITY ", lParam);
201                 handleProximity((lParam & 0xFFFF) != 0);
202                 break;
203             case WT_INFOCHANGE:
204                 Log.d("WT_INFOCHANGE");
205                 break;
206             case WT_CSRCHANGE:
207                 Log.d("WT_CSRCHANGE");
208                 break;
209             case WT_PACKETEXT:
210                 Log.d("WT_PACKETEXT");
211                 break;
212             case WT_PACKET:
213                 //Log.d("WT_PACKET");
214                 if (WTPacket(cast(HCTX)lParam, cast(uint)wParam, cast(void*)&pkt)) 
215                 {
216                     if (HIWORD(pkt.pkButtons)==TBN_DOWN) 
217                     {
218                         //MessageBeep(0);
219                     }
220                     //Log.d("WT_PACKET x=", pkt.pkX, " y=", pkt.pkY, " z=", pkt.pkZ, " np=", pkt.pkNormalPressure, " tp=", pkt.pkTangentPressure, " buttons=", "%08x".format(pkt.pkButtons));
221                     onPacket(pkt.pkX, pkt.pkY, pkt.pkNormalPressure, pkt.pkButtons);
222                     //ptOld = ptNew;
223                     //prsOld = prsNew;
224                     //
225                     //ptNew.x = pkt.pkX;
226                     //ptNew.y = pkt.pkY;
227                     //
228                     //prsNew = pkt.pkNormalPressure;
229                     //
230                     //if (ptNew.x != ptOld.x ||
231                     //    ptNew.y != ptOld.y ||
232                     //    prsNew != prsOld) 
233                     //{
234                     //    InvalidateRect(hWnd, NULL, TRUE);
235                     //}
236                 }
237                 break;
238             default:
239                 //Log.d("UNKNOWN: ", "%x".format(message));
240                 break;
241         }
242         return false; // to call DefWindowProc
243     }
244 }
245