/* Copyright 2010 Fictive (Fictive's public key's fingerprint is "44:1a:41:70:b1:22:d4:93:3a:bb:84:62:60:0b:e4:a3") This file is part of Sane Java Tablet. Sane Java Tablet is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Sane Java Tablet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Sane Java Tablet. If not, see <http://www.gnu.org/licenses/>. */ package domain.libs.sjt.winimpl; import com.sun.jna.Memory; import com.sun.jna.Native; import com.sun.jna.Pointer; import domain.libs.sjt.misc.ConsoleLogger; import domain.libs.sjt.winimpl.wintab.defs.AXIS; import domain.libs.sjt.winimpl.wintab.defs.LOGCONTEXTW; import domain.libs.sjt.winimpl.wintab.defs.PACKET; import domain.libs.sjt.winimpl.wintab.defs.Wintab32; import domain.libs.sjt.winimpl.wintab.defs.Wintab32Direct; public class TabletWinImplWintabFuncs { private final ConsoleLogger log = ConsoleLogger.newInstanceForCallingClass(); private final TabletWinImpl impl; WintabDevice wtDevices[]; Memory pktBuffer; PACKET pkts[]; int maxPktsInBuf; public TabletWinImplWintabFuncs(TabletWinImpl tabletWinImpl) { this.impl = tabletWinImpl; } void ensureWintabIsPresent() throws TabletWinImplException { try { if (Wintab32.INSTANCE.WTInfoW(0, 0, Pointer.NULL) == 0) { throw new TabletWinImplException( "Wintab implementation isn't present."); } } catch (UnsatisfiedLinkError e) { throw new TabletWinImplException(e, "Failed to load Wintab dll."); } short version[] = new short[1]; if (Wintab32.INSTANCE.WTInfoW( (int) Wintab32.WTI_INTERFACE, (int) Wintab32.IFC_SPECVERSION, version) == 0 ) { throw new TabletWinImplException( "Failed to acquire Wintab version."); } int version1 = (version[0] & 0xFF00) >> 8; int version2 = version[0] & 0xFF; log.info("Found Wintab version %d.%d", version1, version2); if ((version1 < 1) && (version2 < 1)) { throw new TabletWinImplException( "Minimum supported Wintab version is 1.1"); } } // method void initTablets() throws TabletWinImplException { final int bufSize = 1024*20; // 100 KiB final int pktSize = new PACKET().size(); maxPktsInBuf = bufSize / pktSize; pktBuffer = new Memory(bufSize); pkts = new PACKET[maxPktsInBuf]; for (int i=0; i<maxPktsInBuf; i++) { pkts[i] = new PACKET(pktBuffer.share(pktSize*i)); } int devCount = getDeviceCount(); wtDevices = new WintabDevice[devCount]; for (int devIdx=0; devIdx<devCount; devIdx++) { wtDevices[devIdx] = new WintabDevice(); initTabletDevice(devIdx); } } int getDeviceCount() throws TabletWinImplException { int devCount[] = new int[1]; if (Wintab32.INSTANCE.WTInfoW((int) Wintab32.WTI_INTERFACE, (int) Wintab32.IFC_NDEVICES, devCount) == 0) { throw new TabletWinImplException( "Failed to acquire device count."); } if (devCount[0] == 0) { throw new TabletWinImplException( "No devices detected."); } Memory devName = new Memory(Native.WCHAR_SIZE*40); for (int devIdx=0; devIdx<devCount[0]; devIdx++) { if (Wintab32.INSTANCE.WTInfoW((int) Wintab32.WTI_DEVICES + devIdx, (int) Wintab32.DVC_NAME, devName) == 0) { throw new TabletWinImplException( "Failed to acquire device #%d name.", devIdx+1); } log.info("Device %d of %d detected (%s).", devIdx+1, devCount[0], devName.getString(0, true)); } return devCount[0]; } void initTabletDevice(int devIdx) throws TabletWinImplException { LOGCONTEXTW lc = new LOGCONTEXTW(); if (Wintab32.INSTANCE.WTInfoW((int) Wintab32.WTI_DDCTXS + devIdx, 0, lc) == 0) { throw new TabletWinImplException( "Failed to acquire device's digitizing context."); } String wt_name = String.format( "WT%d%X", devIdx, Pointer.nativeValue(impl.winFuncs.hInstance)); wt_name.getChars( 0, (int) (wt_name.length() > Wintab32.LCNAMELEN ? Wintab32.LCNAMELEN : wt_name.length()), lc.lcName, 0); lc.lcOptions |= Wintab32.CXO_MESSAGES | Wintab32.CXO_CSRMESSAGES; lc.lcStatus |= Wintab32.CXS_ONTOP; lc.lcLocks |= Wintab32.CXL_INSIZE | Wintab32.CXL_INASPECT; lc.lcDevice = devIdx; lc.lcPktRate = getMaxSupportedPacketRate(0); lc.lcPktData = (int) ( Wintab32.PK_CONTEXT | Wintab32.PK_STATUS | Wintab32.PK_TIME | Wintab32.PK_CHANGED | Wintab32.PK_SERIAL_NUMBER | Wintab32.PK_CURSOR | Wintab32.PK_BUTTONS | Wintab32.PK_X | Wintab32.PK_Y | Wintab32.PK_Z | Wintab32.PK_NORMAL_PRESSURE | Wintab32.PK_TANGENT_PRESSURE | Wintab32.PK_ORIENTATION | Wintab32.PK_ROTATION ); lc.lcPktMode = 0; // return all values as absolute (or set tablet to absolute mode?) lc.lcMoveMask = (int) Long.MAX_VALUE; lc.lcBtnUpMask = (int) Long.MAX_VALUE; lc.lcBtnDnMask = (int) Long.MAX_VALUE; AXIS xAxis = new AXIS(); AXIS yAxis = new AXIS(); AXIS zAxis = new AXIS(); if (Wintab32.INSTANCE.WTInfoW((int) Wintab32.WTI_DEVICES + devIdx, (int) Wintab32.DVC_X, xAxis) == 0) { throw new TabletWinImplException("WTInfoW() for DVC_X failed."); } if (Wintab32.INSTANCE.WTInfoW((int) Wintab32.WTI_DEVICES + devIdx, (int) Wintab32.DVC_Y, yAxis) == 0) { throw new TabletWinImplException("WTInfoW() for DVC_Y failed."); } if (Wintab32.INSTANCE.WTInfoW((int) Wintab32.WTI_DEVICES + devIdx, (int) Wintab32.DVC_Z, zAxis) == 0) { throw new TabletWinImplException("WTInfoW() for DVC_Z failed."); } // x lc.lcInOrgX = lc.lcOutOrgX = 0; lc.lcInExtX = lc.lcOutExtX = xAxis.axMax; // y lc.lcInOrgY = lc.lcOutOrgY = 0; lc.lcInExtY = lc.lcOutExtY = yAxis.axMax; // z lc.lcInOrgZ = lc.lcOutOrgZ = 0; lc.lcInExtZ = lc.lcOutExtZ = zAxis.axMax; wtDevices[devIdx].xAxis = xAxis; wtDevices[devIdx].yAxis = yAxis; wtDevices[devIdx].zAxis = zAxis; log.debug( "Device %d) Xmin:%d, Xmax:%d, Xres:%d, Xunits:%d", devIdx+1, xAxis.axMin, xAxis.axMax, xAxis.axResolution, xAxis.axUnits ); log.debug( "Device %d) Ymin:%d, Ymax:%d, Yres:%d, Yunits:%d", devIdx+1, yAxis.axMin, yAxis.axMax, yAxis.axResolution, yAxis.axUnits ); log.debug( "Device %d) Zmin:%d, Zmax:%d, Zres:%d, Zunits:%d", devIdx+1, zAxis.axMin, zAxis.axMax, zAxis.axResolution, zAxis.axUnits ); Pointer hCtx = Wintab32.INSTANCE.WTOpenW(impl.winFuncs.hWnd, lc, 0); if (hCtx == Pointer.NULL) { throw new TabletWinImplException( "Failed to open tablet device %d.", devIdx); } // if // set wintab packet queue size int queueSize = 0; for (queueSize=500; queueSize>=0; queueSize>>=1) { if (Wintab32.INSTANCE.WTQueueSizeSet(hCtx, queueSize) != 0) break; } if (queueSize == 0) { throw new TabletWinImplException( "Failed to set queue size on tablet #%d.", devIdx); } wtDevices[devIdx].hCtx = hCtx; } int getMaxSupportedPacketRate(int devIdx) throws TabletWinImplException { /* * The lcPktRate field is not functional in the non-Wacom Wintab which shipped * with older Wacom tablets. The Wacom Wintab currently doesn't support * lcPktRate either. In both Wintabs, lcPktRate is reported as 100, but the * tablet will actually be sending data at the highest rate possible * for whatever mode in which the tablet is set. * - from of wacom dev site */ int pktRate[] = new int[1]; if (Wintab32.INSTANCE.WTInfoW( (int) (Wintab32.WTI_DEVICES + devIdx), (int) Wintab32.DVC_PKTRATE, pktRate) == 0 ) { throw new TabletWinImplException( "Failed to acquire maximum packet rate for device #%d.", devIdx+1); } return pktRate[0]; } void enableTablet(int devIdx) { if (Wintab32.INSTANCE.WTEnable(wtDevices[devIdx].hCtx, 1) == 0) { new TabletWinImplException( "Failed to enable tablet #%d.", devIdx+1 ).printStackTrace(); impl.winFuncs.sendWMDESTROY(); return; } if (Wintab32.INSTANCE.WTOverlap(wtDevices[devIdx].hCtx, 1) == 0) { new TabletWinImplException( "Failed to enable tablet #%d (bring context to top).", devIdx+1 ).printStackTrace(); impl.winFuncs.sendWMDESTROY(); return; } } void disableTablet(int devIdx) { if (Wintab32.INSTANCE.WTOverlap(wtDevices[devIdx].hCtx, 0) == 0) { new TabletWinImplException( "Failed to disable tablet #%d (push context to bottom).", devIdx+1 ).printStackTrace(); impl.winFuncs.sendWMDESTROY(); return; } if (Wintab32.INSTANCE.WTEnable(wtDevices[devIdx].hCtx, 0) == 0) { new TabletWinImplException( "Failed to disable tablet #%d.", devIdx+1 ).printStackTrace(); impl.winFuncs.sendWMDESTROY(); return; } } void tabletCleanup() { log.debug("tabletCleanup()"); for (int i=0; i<wtDevices.length; i++) wtCloseTablet(i); } void wtCloseTablet(int devIdx) { if (Wintab32.INSTANCE.WTClose(wtDevices[devIdx].hCtx) == 0) { new TabletWinImplException( "Failed cleanup (close tablet #%d).", devIdx+1 ).printStackTrace(); } // if } int pktNum, wtDevIdx; boolean wtDevIdxFound; void handleWTPACKET(Pointer hCtx, long pktSerial) { final int pktsReceived = Wintab32Direct.WTPacketsGet(hCtx, maxPktsInBuf, pktBuffer); if (pktsReceived == 0) return; wtDevIdxFound = false; for (wtDevIdx=0; wtDevIdx<wtDevices.length; wtDevIdx++) { if (Pointer.nativeValue(hCtx) == Pointer.nativeValue(wtDevices[wtDevIdx].hCtx)) { wtDevIdxFound = true; break; } } // for if (!wtDevIdxFound) { log.error("WT_PACKET received but no matching initialize device found. Packet's hCtx is %d", Pointer.nativeValue(hCtx)); } for (pktNum=0; pktNum<pktsReceived; pktNum++) { pkts[pktNum].read(); impl.handlePacket(wtDevIdx, pkts[pktNum]); } // for } // method }