001/* [{ 002Copyright 2007, 2008 Nicolas Carranza <nicarran at gmail.com> 003 004This file is part of jpen. 005 006jpen is free software: you can redistribute it and/or modify 007it under the terms of the GNU Lesser General Public License as published by 008the Free Software Foundation, either version 3 of the License, 009or (at your option) any later version. 010 011jpen is distributed in the hope that it will be useful, 012but WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014GNU Lesser General Public License for more details. 015 016You should have received a copy of the GNU Lesser General Public License 017along with jpen. If not, see <http://www.gnu.org/licenses/>. 018}] */ 019package jpen.provider.wintab; 020 021import java.awt.AWTEvent; 022import java.awt.event.AWTEventListener; 023import java.awt.event.InputEvent; 024import java.awt.KeyboardFocusManager; 025import java.awt.Toolkit; 026import java.awt.Window; 027import java.util.HashMap; 028import java.util.logging.Level; 029import java.util.logging.Logger; 030import java.util.Map; 031import jpen.internal.BuildInfo; 032import jpen.internal.ObjectUtils; 033import jpen.internal.Range; 034import jpen.PenManager; 035import jpen.PenProvider; 036import jpen.PLevel; 037import jpen.provider.AbstractPenProvider; 038import jpen.provider.NativeLibraryLoader; 039import jpen.provider.VirtualScreenBounds; 040 041public class WintabProvider 042 extends AbstractPenProvider { 043 private static final Logger L=Logger.getLogger(WintabProvider.class.getName()); 044 045 private static final NativeLibraryLoader LIB_LOADER=new NativeLibraryLoader(new String[]{""}, 046 new String[]{"64"}, 047 Integer.valueOf(BuildInfo.getProperties().getString("jpen.provider.wintab.nativeVersion"))); 048 //static{L.setLevel(Level.ALL);} 049 050 static void loadLibrary(){ 051 LIB_LOADER.load(); 052 } 053 054 /** 055 When this system property is set to true then the provider stops sending events if no AWT events are received after one second. 056 */ 057 public static final String WAIT_AWT_ACTIVITY_SYSTEM_PROPERTY="jpen.provider.wintab.waitAwtActivity"; 058 private static final boolean WAIT_AWT_ACTIVITY=Boolean.valueOf( 059 System.getProperty(WAIT_AWT_ACTIVITY_SYSTEM_PROPERTY)); 060 static{ 061 if(WAIT_AWT_ACTIVITY) 062 L.info("WAIT_AWT_ACTIVITY set to true"); 063 } 064 065 public static final String PERIOD_SYSTEM_PROPERTY="jpen.provider.wintab.period"; 066 public static final int PERIOD; 067 static{ 068 String periodString=System.getProperty(PERIOD_SYSTEM_PROPERTY, null); 069 int periodValue=10; 070 if(periodString!=null) 071 try{ 072 periodValue=Integer.valueOf(periodString); 073 if(periodValue<=0){ 074 L.severe("ignored illegal PERIOD value "+periodValue+", period value must be >= 0"); 075 periodValue=10; 076 }else 077 L.info("PERIOD set to "+periodValue); 078 }catch(NumberFormatException ex){ 079 } 080 PERIOD=periodValue; 081 } 082 083 084 public final WintabAccess wintabAccess; 085 private final Map<Integer, WintabDevice> cursorToDevice=new HashMap<Integer, WintabDevice>(); 086 private final Range[] levelRanges=new Range[PLevel.Type.VALUES.size()]; 087 final VirtualScreenBounds screenBounds=VirtualScreenBounds.getInstance(); 088 private final Thread thread; 089 private volatile boolean paused=true; 090 private boolean systemCursorEnabled=true; // by default the tablet device moves the system pointer (cursor) 091 092 public static class Constructor 093 extends AbstractPenProvider.AbstractConstructor{ 094 //@Override 095 public String getName() { 096 return "Wintab"; 097 } 098 //@Override 099 public boolean constructable(PenManager penManager) { 100 return System.getProperty("os.name").toLowerCase().contains("windows"); 101 } 102 103 @Override 104 public PenProvider constructProvider() throws Throwable { 105 loadLibrary(); 106 WintabAccess wintabAccess=new WintabAccess(); 107 return new WintabProvider(this, wintabAccess); 108 } 109 @Override 110 public int getNativeVersion(){ 111 return LIB_LOADER.nativeVersion; 112 } 113 @Override 114 public int getNativeBuild(){ 115 loadLibrary(); 116 return WintabAccess.getNativeBuild(); 117 } 118 @Override 119 public int getExpectedNativeBuild(){ 120 return Integer.valueOf(BuildInfo.getProperties().getString("jpen.provider.wintab.nativeBuild")); 121 } 122 } 123 124 class MyThread 125 extends Thread implements AWTEventListener{ 126 127 private long scheduleTime; 128 private long awtEventTime; 129 private boolean waitingAwtEvent; 130 private int inputEventModifiers; 131 private boolean awtSleep; 132 private final Object awtLock=new Object(); 133 134 { 135 setName("jpen-WintabProvider"); 136 setDaemon(true); 137 setPriority(Thread.MAX_PRIORITY); 138 if(WAIT_AWT_ACTIVITY) 139 Toolkit.getDefaultToolkit().addAWTEventListener(this, ~0); 140 } 141 142 public void run() { 143 try{ 144 KeyboardFocusManager keyboardFocusManager=KeyboardFocusManager.getCurrentKeyboardFocusManager(); 145 long processingTime; 146 long correctPeriod; 147 boolean waited=true; 148 while(true) { 149 processingTime=waited? System.currentTimeMillis(): scheduleTime; 150 schedule(); 151 processingTime=scheduleTime-processingTime; 152 correctPeriod=PERIOD-processingTime; 153 waited=false; 154 synchronized(this){ 155 if(correctPeriod>0){ 156 wait(correctPeriod); 157 waited=true; 158 } 159 if(WAIT_AWT_ACTIVITY){ 160 waitingAwtEvent=scheduleTime-awtEventTime>1000 && 161 (inputEventModifiers==0 || keyboardFocusManager.getActiveWindow()==null); 162 if(waitingAwtEvent){ 163 wait(500); 164 waited=true; 165 } 166 } 167 while(paused){ 168 L.fine("going to wait..."); 169 wait(); 170 L.fine("notified"); 171 waited=true; 172 } 173 } 174 } 175 }catch(InterruptedException ex){ 176 throw new AssertionError(ex); 177 } 178 } 179 180 private void schedule(){ 181 processQueuedEvents(); 182 scheduleTime=System.currentTimeMillis(); 183 } 184 //@Override 185 public synchronized void eventDispatched(AWTEvent ev){ 186 InputEvent inputEvent=ev instanceof InputEvent? (InputEvent)ev: null; 187 synchronized(this){ 188 awtEventTime=System.currentTimeMillis(); 189 if(inputEvent!=null) 190 inputEventModifiers=inputEvent.getModifiersEx(); 191 if(!paused && waitingAwtEvent) 192 notify(); 193 } 194 } 195 } 196 197 private WintabProvider(Constructor constructor, WintabAccess wintabAccess) { 198 super(constructor); 199 L.fine("start"); 200 this.wintabAccess=wintabAccess; 201 202 for(int i=PLevel.Type.VALUES.size(); --i>=0;){ 203 PLevel.Type levelType=PLevel.Type.VALUES.get(i); 204 levelRanges[levelType.ordinal()]=wintabAccess.getLevelRange(levelType); 205 } 206 207 thread=new MyThread(); 208 thread.start(); 209 //System.out.println("wintabAccess=" + ( wintabAccess )); 210 L.fine("end"); 211 } 212 213 Range getLevelRange(PLevel.Type type) { 214 return levelRanges[type.ordinal()]; 215 } 216 217 private void processQueuedEvents() { 218 //L.finer("start"); 219 //boolean gotPacket=false; 220 while(wintabAccess.nextPacket() && !paused) { 221 //gotPacket=true; 222 WintabDevice device=getDevice(wintabAccess.getCursor()); 223 if(L.isLoggable(Level.FINE)){ 224 L.finer("device: "); 225 L.finer(device.getName()); 226 } 227 device.scheduleEvents(); 228 } 229 //System.out.println("gotPacket=" + ( gotPacket )); 230 //L.finer("end"); 231 } 232 233 private WintabDevice getDevice(int cursor) { 234 WintabDevice wintabDevice=cursorToDevice.get(cursor); 235 if(wintabDevice==null) { 236 cursorToDevice.put(cursor, wintabDevice=new WintabDevice(this, cursor)); 237 devices.clear(); 238 devices.addAll(cursorToDevice.values()); 239 getPenManager().firePenDeviceAdded(getConstructor(), wintabDevice); 240 } 241 return wintabDevice; 242 } 243 244 //@Override 245 public void penManagerPaused(boolean paused) { 246 setPaused(paused); 247 } 248 249 synchronized void setPaused(boolean paused) { 250 L.fine("start"); 251 if(paused==this.paused) 252 return; 253 this.paused=paused; 254 if(!paused){ 255 L.fine("false paused value"); 256 screenBounds.reset(); 257 synchronized(thread) { 258 L.fine("going to notify all..."); 259 thread.notifyAll(); 260 L.fine("done notifying "); 261 } 262 wintabAccess.enable(true); 263 } 264 L.fine("end"); 265 } 266 267 @Override 268 public boolean getUseRelativeLocationFilter(){ 269 return systemCursorEnabled; 270 } 271 272 /** 273 @param systemCursorEnabled If {@code false} then tablet movement on Wintab devices doesn't cause movement on the system mouse pointer. {@code true} then tablet movement on Wintab devices cause movement on the system mouse pointer, this is the default value. 274 */ 275 public synchronized void setSystemCursorEnabled(boolean systemCursorEnabled){ 276 if(this.systemCursorEnabled==systemCursorEnabled) 277 return; 278 this.systemCursorEnabled=systemCursorEnabled; 279 wintabAccess.setSystemCursorEnabled(systemCursorEnabled); 280 } 281 282 public synchronized boolean getSystemCursorEnabled(){ 283 return systemCursorEnabled; 284 } 285}