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}