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