View Javadoc

1   /**
2    * JIntellitype
3    * -----------------
4    * Copyright 2005-2008 Emil A. Lefkof III, Melloware Inc.
5    *
6    * I always give it my best shot to make a program useful and solid, but
7    * remeber that there is absolutely no warranty for using this program as
8    * stated in the following terms:
9    *
10   * Licensed under the Apache License, Version 2.0 (the "License");
11   * you may not use this file except in compliance with the License.
12   * You may obtain a copy of the License at
13   *
14   *     http://www.apache.org/licenses/LICENSE-2.0
15   *
16   * Unless required by applicable law or agreed to in writing, software
17   * distributed under the License is distributed on an "AS IS" BASIS,
18   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19   * See the License for the specific language governing permissions and
20   * limitations under the License.
21   */
22  package com.melloware.jintellitype;
23  
24  import java.awt.event.InputEvent;
25  import java.util.Collections;
26  import java.util.List;
27  import java.util.concurrent.CopyOnWriteArrayList;
28  
29  import javax.swing.SwingUtilities;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  
34  /**
35   * JIntellitype A Java Implementation for using the Windows API Intellitype
36   * commands and the RegisterHotKey and UnRegisterHotkey API calls for globally
37   * responding to key events. Intellitype are commands that are using for Play,
38   * Stop, Next on Media keyboards or some laptops that have those special keys.
39   * <p>
40   * JIntellitype class that is used to call Windows API calls using the
41   * JIntellitype.dll.
42   * <p>
43   * This file comes with native code in JINTELLITYPE.DLL The DLL should go in
44   * C:/WINDOWS/SYSTEM or in your current directory
45   * <p>
46   * <p>
47   * Copyright (c) 1999-2008
48   * Melloware, Inc. <http://www.melloware.com>
49   * @author Emil A. Lefkof III <info@melloware.com>
50   * @version 1.3.1
51   */
52  public final class JIntellitype implements JIntellitypeConstants {
53  
54     /**
55      * Logger for this class
56      */
57     private static final Log LOG = LogFactory.getLog(JIntellitype.class);
58  
59     /**
60      * Static variable to hold singleton.
61      */
62     private static JIntellitype jintellitype = null;
63  
64     /**
65      * Static variable for double checked thread safety.
66      */
67     private static boolean isInitialized = false;
68  
69     /**
70      * Listeners collection for Hotkey events
71      */
72     private final List<HotkeyListener> hotkeyListeners = Collections
73              .synchronizedList(new CopyOnWriteArrayList<HotkeyListener>());
74  
75     /**
76      * Listeners collection for Hotkey events
77      */
78     private final List<IntellitypeListener> intellitypeListeners = Collections
79              .synchronizedList(new CopyOnWriteArrayList<IntellitypeListener>());
80  
81     /**
82      * Handler is used by JNI code to keep different JVM instances separate
83      */
84     @SuppressWarnings("unused")
85     private int handler = 0;
86  
87     /**
88      * Private Constructor to prevent instantiation. Initialize the library for
89      * calling.
90      */
91     private JIntellitype() {
92        // Load JNI library
93        try {
94           LOG.info("Loading JIntellitype DLL");
95           System.loadLibrary("JIntellitype");
96  
97           LOG.info("Initializing JIntellitype library");
98           initializeLibrary();
99        } catch (UnsatisfiedLinkError ex) {
100          throw new JIntellitypeException(ex);
101       } catch (RuntimeException ex) {
102          throw new JIntellitypeException(ex);
103       }
104    }
105 
106    /**
107     * Gets the singleton instance of the JIntellitype object.
108     * <p>
109     * But the possibility of creation of more instance is only before the
110     * instance is created. Since all code defined inside getInstance method is
111     * in the synchronized block, even the subsequent requests will also come and
112     * wait in the synchronized block. This is a performance issue. The same can
113     * be solved using double-checked lock. Following is the implementation of
114     * Singleton with lazy initialization and double-checked lock.
115     * <p>
116     * @return an instance of JIntellitype class
117     */
118    public static JIntellitype getInstance() {
119       if (!isInitialized) {
120          synchronized (JIntellitype.class) {
121             if (!isInitialized) {
122                jintellitype = new JIntellitype();
123                isInitialized = true;
124             }
125          }
126       }
127       return jintellitype;
128    }
129 
130    /**
131     * Adds a listener for hotkeys.
132     * <p>
133     * @param listener the HotKeyListener to be added
134     */
135    public void addHotKeyListener(HotkeyListener listener) {
136       hotkeyListeners.add(listener);
137    }
138 
139    /**
140     * Adds a listener for intellitype commands.
141     * <p>
142     * @param listener the IntellitypeListener to be added
143     */
144    public void addIntellitypeListener(IntellitypeListener listener) {
145       intellitypeListeners.add(listener);
146    }
147 
148    /**
149     * Cleans up all resources used by JIntellitype.
150     */
151    public void cleanUp() {
152       try {
153          LOG.info("Cleaning up JIntellitype resources");
154          terminate();
155       } catch (UnsatisfiedLinkError ex) {
156          throw new JIntellitypeException(ERROR_MESSAGE, ex);
157       } catch (RuntimeException ex) {
158          throw new JIntellitypeException(ex);
159       }
160    }
161 
162    /**
163     * Registers a Hotkey with windows. This combination will be responded to by
164     * all registered HotKeyListeners. Uses the JIntellitypeConstants for MOD,
165     * ALT, CTRL, and WINDOWS keys.
166     * <p>
167     * @param identifier a unique identifier for this key combination
168     * @param modifier MOD_SHIFT, MOD_ALT, MOD_CONTROL, MOD_WIN from
169     *           JIntellitypeConstants, or 0 if no modifier needed
170     * @param keycode the key to respond to in Ascii integer, 65 for A
171     */
172    public void registerHotKey(int identifier, int modifier, int keycode) {
173       if (LOG.isInfoEnabled()) {
174          LOG.info("registerHotKey " + Integer.toString(identifier));
175       }
176       try {
177          int modifiers = swingToIntelliType(modifier);
178          if (modifiers == 0) {
179             modifiers = modifier;
180          }
181          regHotKey(identifier, modifier, keycode);
182       } catch (UnsatisfiedLinkError ex) {
183          throw new JIntellitypeException(ERROR_MESSAGE, ex);
184       } catch (RuntimeException ex) {
185          throw new JIntellitypeException(ex);
186       }
187    }
188 
189    /**
190     * Registers a Hotkey with windows. This combination will be responded to by
191     * all registered HotKeyListeners. Use the Swing InputEvent constants from
192     * java.awt.InputEvent.
193     * <p>
194     * @param identifier a unique identifier for this key combination
195     * @param modifier InputEvent.SHIFT_MASK, InputEvent.ALT_MASK,
196     *           InputEvent.CTRL_MASK, or 0 if no modifier needed
197     * @param keycode the key to respond to in Ascii integer, 65 for A
198     */
199    public void registerSwingHotKey(int identifier, int modifier, int keycode) {
200       if (LOG.isInfoEnabled()) {
201          LOG.info("registerHotKey " + Integer.toString(identifier));
202       }
203       try {
204          regHotKey(identifier, swingToIntelliType(modifier), keycode);
205       } catch (UnsatisfiedLinkError ex) {
206          throw new JIntellitypeException(ERROR_MESSAGE, ex);
207       } catch (RuntimeException ex) {
208          throw new JIntellitypeException(ex);
209       }
210    }
211 
212    /**
213     * Registers a Hotkey with windows. This combination will be responded to by
214     * all registered HotKeyListeners. Use the identifiers CTRL, SHIFT, ALT
215     * and/or WIN.
216     * <p>
217     * @param identifier a unique identifier for this key combination
218     * @param modifierAndKeyCode String with modifiers separated by + and keycode
219     *           (e.g. CTRL+SHIFT+A)
220     * @see #registerHotKey(int, int, int)
221     * @see #registerSwingHotKey(int, int, int)
222     */
223    public void registerHotKey(int identifier, String modifierAndKeyCode) {
224       if (LOG.isInfoEnabled()) {
225          LOG.info("registerHotKey " + modifierAndKeyCode);
226       }
227       String[] split = modifierAndKeyCode.split("\\+");
228       int mask = 0;
229       for (int i = 0; i < split.length - 1; i++) {
230          if ("ALT".equalsIgnoreCase(split[i])) {
231             mask += JIntellitype.MOD_ALT;
232          } else if ("CTRL".equalsIgnoreCase(split[i]) || "CONTROL".equalsIgnoreCase(split[i])) {
233             mask += JIntellitype.MOD_CONTROL;
234          } else if ("SHIFT".equalsIgnoreCase(split[i])) {
235             mask += JIntellitype.MOD_SHIFT;
236          } else if ("WIN".equalsIgnoreCase(split[i])) {
237             mask += JIntellitype.MOD_WIN;
238          }
239       }
240       registerHotKey(identifier, mask, split[split.length - 1].charAt(0));
241    }
242 
243    /**
244     * Removes a listener for hotkeys.
245     */
246    public void removeHotKeyListener(HotkeyListener listener) {
247       hotkeyListeners.remove(listener);
248    }
249 
250    /**
251     * Removes a listener for intellitype commands.
252     */
253    public void removeIntellitypeListener(IntellitypeListener listener) {
254       intellitypeListeners.remove(listener);
255    }
256 
257    /**
258     * Unregisters a previously registered Hotkey identified by its unique
259     * identifier.
260     * <p>
261     * @param identifier the unique identifer of this Hotkey
262     */
263    public void unregisterHotKey(int identifier) {
264       if (LOG.isInfoEnabled()) {
265          LOG.info("unregisterHotKey " + Integer.toString(identifier));
266       }
267       try {
268          unregHotKey(identifier);
269       } catch (UnsatisfiedLinkError ex) {
270          throw new JIntellitypeException(ERROR_MESSAGE, ex);
271       } catch (RuntimeException ex) {
272          throw new JIntellitypeException(ex);
273       }
274    }
275 
276    /**
277     * Checks to see if this application is already running.
278     * <p>
279     * @param appTitle the name of the application to check for
280     * @return true if running, false if not running
281     */
282    public static boolean checkInstanceAlreadyRunning(String appTitle) {
283       if (LOG.isInfoEnabled()) {
284          LOG.info("checkInstanceAlreadyRunning " + appTitle);
285       }
286       return getInstance().isRunning(appTitle);
287    }
288 
289    /**
290     * Checks to make sure the OS is a Windows flavor and that the JIntellitype
291     * DLL is found in the path and the JDK is 32 bit not 64 bit. The DLL
292     * currently only supports 32 bit JDK.
293     * <p>
294     * @return true if Jintellitype may be used, false if not
295     */
296    public static boolean isJIntellitypeSupported() {
297       boolean result = false;
298       String os = "none";
299       String architecture = "none";
300 
301       if (LOG.isInfoEnabled()) {
302          LOG.info("isJIntellitypeSupported checking for Windows and DLL in path");
303       }
304 
305       try {
306          os = System.getProperty("os.name").toLowerCase();
307          architecture = System.getProperty("sun.arch.data.model").toLowerCase();
308       } catch (SecurityException ex) {
309          // we are not allowed to look at this property
310          LOG.error("Caught a SecurityException reading the system property "
311                   + "'os.name'; the SystemUtils property value will default to null.");
312       }
313 
314       // only works on 32 bit Windows OS currently
315       if ((os.startsWith("windows")) && (architecture.equalsIgnoreCase("32"))) {
316          // try an get the instance and if it succeeds then return true
317          try {
318             getInstance();
319             result = true;
320          } catch (Exception e) {
321             result = false;
322          }
323       }
324 
325       if (LOG.isInfoEnabled()) {
326          LOG.info("isJIntellitypeSupported = " + result);
327       }
328 
329       return result;
330    }
331 
332    /**
333     * Notifies all listeners that Hotkey was pressed.
334     * <p>
335     * @param identifier the unique identifier received
336     */
337    protected void onHotKey(final int identifier) {
338       for (final HotkeyListener hotkeyListener : hotkeyListeners) {
339          SwingUtilities.invokeLater(new Runnable() {
340             public void run() {
341                hotkeyListener.onHotKey(identifier);
342             }
343          });
344       }
345    }
346 
347    /**
348     * Notifies all listeners that Intellitype command was received.
349     * <p>
350     * @param command the unique WM_APPCOMMAND received
351     */
352    protected void onIntellitype(final int command) {
353       for (final IntellitypeListener intellitypeListener : intellitypeListeners) {
354          SwingUtilities.invokeLater(new Runnable() {
355             public void run() {
356                intellitypeListener.onIntellitype(command);
357             }
358          });
359       }
360    }
361 
362    /**
363     * Swing modifier value to Jintellipad conversion. If no conversion needed
364     * just return the original value. This lets users pass either the original
365     * JIntellitype constants or Swing InputEvent constants.
366     * <p>
367     * @param swingKeystrokeModifier the Swing KeystrokeModifier to check
368     * @return Jintellitype the JIntellitype modifier value
369     */
370    protected static int swingToIntelliType(int swingKeystrokeModifier) {
371       int mask = 0;
372       if ((swingKeystrokeModifier & InputEvent.SHIFT_MASK) == InputEvent.SHIFT_MASK) {
373          mask += JIntellitype.MOD_SHIFT;
374       }
375       if ((swingKeystrokeModifier & InputEvent.ALT_MASK) == InputEvent.ALT_MASK) {
376          mask += JIntellitype.MOD_ALT;
377       }
378       if ((swingKeystrokeModifier & InputEvent.CTRL_MASK) == InputEvent.CTRL_MASK) {
379          mask += JIntellitype.MOD_CONTROL;
380       }
381       if ((swingKeystrokeModifier & InputEvent.SHIFT_DOWN_MASK) == InputEvent.SHIFT_DOWN_MASK) {
382          mask += JIntellitype.MOD_SHIFT;
383       }
384       if ((swingKeystrokeModifier & InputEvent.ALT_DOWN_MASK) == InputEvent.ALT_DOWN_MASK) {
385          mask += JIntellitype.MOD_ALT;
386       }
387       if ((swingKeystrokeModifier & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK) {
388          mask += JIntellitype.MOD_CONTROL;
389       }
390       return mask;
391    }
392 
393    private synchronized native void initializeLibrary() throws UnsatisfiedLinkError;
394 
395    private synchronized native void regHotKey(int identifier, int modifier, int keycode) throws UnsatisfiedLinkError;
396 
397    private synchronized native void terminate() throws UnsatisfiedLinkError;
398 
399    private synchronized native void unregHotKey(int identifier) throws UnsatisfiedLinkError;
400 
401    /**
402     * Checks if there's an instance with hidden window title = appName running
403     * Can be used to detect that another instance of your app is already running
404     * (so exit..)
405     * <p>
406     * @param appName = the title of the hidden window to search for
407     */
408    private synchronized native boolean isRunning(String appName);
409 }