1 /*  Copyright 2008  Edwin Stang (edwinstang@gmail.com),
2  *
3  *  This file is part of JXGrabKey.
4  *
5  *  JXGrabKey is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU Lesser General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  JXGrabKey is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public License
16  *  along with JXGrabKey.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 package jxgrabkey;
20 
21 import java.awt.event.KeyEvent;
22 import java.util.Vector;
23 
24 /**
25  * This class implements the API access.
26  * All public methods are synchronized, hence thread-safe.
27  *
28  * @author subes
29  */
30 public class JXGrabKey {
31 
32     private static final int SLEEP_WHILE_LISTEN_EXITS = 100;
33 
34     private static boolean debug;
35 
36     private static JXGrabKey instance;
37     private static Thread thread;
38     private static Vector<HotkeyListener> listeners = new Vector<HotkeyListener>();
39 
40     /**
41      * This constructor starts a seperate Thread for the main listen loop.
42      */
JXGrabKey()43     private JXGrabKey() {
44         thread = new Thread(){
45             @Override
46             public void run() {
47                 listen();
48                 debugCallback("-- listen()");
49             }
50         };
51         thread.start();
52     }
53 
54     /**
55      * Retrieves the singleton. Initializes it, if not yet done.
56      *
57      * @return
58      */
getInstance()59     public static synchronized JXGrabKey getInstance(){
60         if(instance == null){
61             instance = new JXGrabKey();
62         }
63         return instance;
64     }
65 
66     /**
67      * Adds a HotkeyListener.
68      *
69      * @param listener
70      */
addHotkeyListener(HotkeyListener listener)71     public void addHotkeyListener(HotkeyListener listener){
72         if(listener == null){
73             throw new IllegalArgumentException("listener must not be null");
74         }
75         JXGrabKey.listeners.add(listener);
76     }
77 
78     /**
79      * Removes a HotkeyListener.
80      *
81      * @param listener
82      */
removeHotkeyListener(HotkeyListener listener)83     public void removeHotkeyListener(HotkeyListener listener){
84         if(listener == null){
85             throw new IllegalArgumentException("listener must not be null");
86         }
87         JXGrabKey.listeners.remove(listener);
88     }
89 
90     /**
91      * Unregisters all hotkeys, removes all HotkeyListeners,
92      * stops the main listen loop and deinitializes the singleton.
93      */
cleanUp()94     public void cleanUp(){
95         clean();
96         if(thread.isAlive()){
97             while(thread.isAlive()){
98                 try {
99                     Thread.sleep(SLEEP_WHILE_LISTEN_EXITS);
100                 } catch (InterruptedException e) {
101                     debugCallback("cleanUp() - InterruptedException: "+e.getMessage());
102                 }
103             }
104             instance = null; //next time getInstance is called, reinitialize JXGrabKey
105         }
106         if(listeners.size() > 0){
107             listeners.clear();
108         }
109     }
110 
111     /**
112      * Registers a X11 hotkey.
113      *
114      * @param id
115      * @param x11Mask
116      * @param x11Keysym
117      * @throws jxgrabkey.HotkeyConflictException
118      */
registerX11Hotkey(int id, int x11Mask, int x11Keysym)119     public void registerX11Hotkey(int id, int x11Mask, int x11Keysym) throws HotkeyConflictException{
120         registerHotkey(id, x11Mask, x11Keysym);
121     }
122 
123     /**
124      * Converts an AWT hotkey into a X11 hotkey and registers it.
125      *
126      * @param id
127      * @param awtMask
128      * @param awtKey
129      * @throws jxgrabkey.HotkeyConflictException
130      */
registerAwtHotkey(int id, int awtMask, int awtKey)131     public void registerAwtHotkey(int id, int awtMask, int awtKey) throws HotkeyConflictException{
132         debugCallback("++ registerAwtHotkey("+id+", 0x"+
133                 Integer.toHexString(awtMask)+", 0x"+
134                 Integer.toHexString(awtKey)+")");
135 
136         int x11Mask = X11MaskDefinitions.awtMaskToX11Mask(awtMask);
137         int x11Keysym = X11KeysymDefinitions.awtKeyToX11Keysym(awtKey);
138 
139         debugCallback("registerAwtHotkey() - converted AWT mask '"+
140                 KeyEvent.getKeyModifiersText(awtMask)+"' (0x"+Integer.toHexString(awtMask)+
141                 ") to X11 mask (0x"+Integer.toHexString(x11Mask)+")");
142 
143         debugCallback("registerAwtHotkey() - converted AWT key '"+
144                 KeyEvent.getKeyText(awtKey)+"' (0x"+Integer.toHexString(awtKey)+
145                 ") to X11 keysym (0x"+Integer.toHexString(x11Keysym)+")");
146 
147         registerHotkey(id, x11Mask, x11Keysym);
148 
149         debugCallback("-- registerAwtHotkey()");
150     }
151 
152     /**
153      * Enables/Disables printing of debug messages.
154      *
155      * @param enabled
156      */
setDebugOutput(boolean enabled)157     public static void setDebugOutput(boolean enabled){
158         debug = enabled;
159         setDebug(enabled);
160     }
161 
162     /**
163      * Notifies HotkeyListeners about a received KeyEvent.
164      *
165      * This method is used by the C++ code.
166      * Do not use this method from externally.
167      *
168      * @param id
169      */
fireKeyEvent(int id)170     public static void fireKeyEvent(int id){
171         for(int i = 0; i < listeners.size(); i++){
172             listeners.get(i).onHotkey(id);
173         }
174     }
175 
176     /**
177      * Either gives debug messages to a HotkeyListenerDebugEnabled if registered,
178      * or prints to console otherwise.
179      * Does only print if debug is enabled.
180      *
181      * This method is both used by the C++ and Java code, so it should not be synchronized.
182      * Don't use this method from externally.
183      *
184      * @param debugmessage
185      */
debugCallback(String debugmessage)186     public static void debugCallback(String debugmessage){
187         if(debug){
188             debugmessage.trim();
189             if(debugmessage.charAt(debugmessage.length()-1) != '\n'){
190                 debugmessage += "\n";
191             }else{
192                 while(debugmessage.endsWith("\n\n")){
193                     debugmessage = debugmessage.substring(0, debugmessage.length()-1);
194                 }
195             }
196 
197             boolean found = false;
198             for(HotkeyListener l : listeners){
199                 if(l instanceof HotkeyListenerDebugEnabled){
200                     ((HotkeyListenerDebugEnabled)l).debugCallback(debugmessage);
201                     found = true;
202                 }
203             }
204 
205             if(found == false){
206                 System.out.print(debugmessage);
207             }
208         }
209     }
210 
211     /**
212      * This method unregisters a hotkey.
213      * If the hotkey is not yet registered, nothing will happen.
214      *
215      * @param id
216      */
unregisterHotKey(int id)217     public native void unregisterHotKey(int id);
218 
listen()219     private native void listen();
220 
setDebug(boolean debug)221     private static native void setDebug(boolean debug);
222 
clean()223     private native void clean();
224 
registerHotkey(int id, int mask, int key)225     private native void registerHotkey(int id, int mask, int key) throws HotkeyConflictException;
226 
227 }
228