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 }