1 /*
2  * Copyright (c) 2002-2008 LWJGL Project
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * * Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * * Redistributions in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in the
14  *   documentation and/or other materials provided with the distribution.
15  *
16  * * Neither the name of 'LWJGL' nor the names of
17  *   its contributors may be used to endorse or promote products derived
18  *   from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 package org.lwjgl.opengl;
33 
34 /**
35  * @author elias_naur
36  */
37 
38 import java.nio.ByteBuffer;
39 import java.nio.CharBuffer;
40 import java.nio.charset.Charset;
41 import java.nio.charset.CharsetDecoder;
42 
43 import org.lwjgl.BufferUtils;
44 import org.lwjgl.LWJGLUtil;
45 import org.lwjgl.input.Keyboard;
46 
47 final class LinuxKeyboard {
48 	private static final int LockMapIndex                      = 1;
49 	private static final long NoSymbol = 0;
50 	private static final long ShiftMask = 1 << 0;
51 	private static final long LockMask = 1 << 1;
52 	private static final int XLookupChars            = 2;
53 	private static final int XLookupBoth             = 4;
54 
55 	private static final int KEYBOARD_BUFFER_SIZE = 50;
56 
57 	private final long xim;
58 	private final long xic;
59 
60 	private final int numlock_mask;
61 	private final int modeswitch_mask;
62 	private final int caps_lock_mask;
63 	private final int shift_lock_mask;
64 
65 	private final ByteBuffer compose_status;
66 
67 	private final byte[] key_down_buffer = new byte[Keyboard.KEYBOARD_SIZE];
68 	private final EventQueue event_queue = new EventQueue(Keyboard.EVENT_SIZE);
69 
70 	private final ByteBuffer tmp_event = ByteBuffer.allocate(Keyboard.EVENT_SIZE);
71 	private final int[] temp_translation_buffer = new int[KEYBOARD_BUFFER_SIZE];
72 	private final ByteBuffer native_translation_buffer = BufferUtils.createByteBuffer(KEYBOARD_BUFFER_SIZE);
73 	private final CharsetDecoder utf8_decoder = Charset.forName("UTF-8").newDecoder();
74 	private final CharBuffer char_buffer = CharBuffer.allocate(KEYBOARD_BUFFER_SIZE);
75 
76 	// Deferred key released event, to detect key repeat
77 	private boolean has_deferred_event;
78 	private int deferred_keycode;
79 	private int deferred_event_keycode;
80 	private long deferred_nanos;
81 	private byte deferred_key_state;
82 
LinuxKeyboard(long display, long window)83 	LinuxKeyboard(long display, long window) {
84 		long modifier_map = getModifierMapping(display);
85 		int tmp_numlock_mask = 0;
86 		int tmp_modeswitch_mask = 0;
87 		int tmp_caps_lock_mask = 0;
88 		int tmp_shift_lock_mask = 0;
89 		if (modifier_map != 0) {
90 			int max_keypermod = getMaxKeyPerMod(modifier_map);
91 			// Find modifier masks
92 			int i, j;
93 			for (i = 0; i < 8; i++) {
94 				for (j = 0; j < max_keypermod; j++) {
95 					int key_code = lookupModifierMap(modifier_map, i*max_keypermod + j);
96 					int key_sym = (int)keycodeToKeySym(display, key_code);
97 					int mask = 1 << i;
98 					switch (key_sym) {
99 						case LinuxKeycodes.XK_Num_Lock:
100 							tmp_numlock_mask |= mask;
101 							break;
102 						case LinuxKeycodes.XK_Mode_switch:
103 							tmp_modeswitch_mask |= mask;
104 							break;
105 						case LinuxKeycodes.XK_Caps_Lock:
106 							if (i == LockMapIndex) {
107 								tmp_caps_lock_mask = mask;
108 								tmp_shift_lock_mask = 0;
109 							}
110 							break;
111 						case LinuxKeycodes.XK_Shift_Lock:
112 							if (i == LockMapIndex && tmp_caps_lock_mask == 0)
113 								tmp_shift_lock_mask = mask;
114 							break;
115 						default:
116 							break;
117 					}
118 				}
119 			}
120 			freeModifierMapping(modifier_map);
121 		}
122 		numlock_mask = tmp_numlock_mask;
123 		modeswitch_mask = tmp_modeswitch_mask;
124 		caps_lock_mask = tmp_caps_lock_mask;
125 		shift_lock_mask = tmp_shift_lock_mask;
126 		setDetectableKeyRepeat(display, true);
127 		xim = openIM(display);
128 		if (xim != 0) {
129 			xic = createIC(xim, window);
130 			if (xic != 0) {
131 				setupIMEventMask(display, window, xic);
132 			} else {
133 				destroy(display);
134 			}
135 		} else {
136 			xic = 0;
137 		}
138 		compose_status = allocateComposeStatus();
139 	}
getModifierMapping(long display)140 	private static native long getModifierMapping(long display);
freeModifierMapping(long modifier_map)141 	private static native void freeModifierMapping(long modifier_map);
getMaxKeyPerMod(long modifier_map)142 	private static native int getMaxKeyPerMod(long modifier_map);
lookupModifierMap(long modifier_map, int index)143 	private static native int lookupModifierMap(long modifier_map, int index);
keycodeToKeySym(long display, int key_code)144 	private static native long keycodeToKeySym(long display, int key_code);
145 
openIM(long display)146 	private static native long openIM(long display);
createIC(long xim, long window)147 	private static native long createIC(long xim, long window);
setupIMEventMask(long display, long window, long xic)148 	private static native void setupIMEventMask(long display, long window, long xic);
allocateComposeStatus()149 	private static native ByteBuffer allocateComposeStatus();
150 
setDetectableKeyRepeat(long display, boolean enabled)151 	private static void setDetectableKeyRepeat(long display, boolean enabled) {
152 		boolean success = nSetDetectableKeyRepeat(display, enabled);
153 		if (!success)
154 			LWJGLUtil.log("Failed to set detectable key repeat to " + enabled);
155 	}
nSetDetectableKeyRepeat(long display, boolean enabled)156 	private static native boolean nSetDetectableKeyRepeat(long display, boolean enabled);
157 
destroy(long display)158 	public void destroy(long display) {
159 		if (xic != 0)
160 			destroyIC(xic);
161 		if (xim != 0)
162 			closeIM(xim);
163 		setDetectableKeyRepeat(display, false);
164 	}
destroyIC(long xic)165 	private static native void destroyIC(long xic);
closeIM(long xim)166 	private static native void closeIM(long xim);
167 
read(ByteBuffer buffer)168 	public void read(ByteBuffer buffer) {
169 		flushDeferredEvent();
170 		event_queue.copyEvents(buffer);
171 	}
172 
poll(ByteBuffer keyDownBuffer)173 	public void poll(ByteBuffer keyDownBuffer) {
174 		flushDeferredEvent();
175 		int old_position = keyDownBuffer.position();
176 		keyDownBuffer.put(key_down_buffer);
177 		keyDownBuffer.position(old_position);
178 	}
179 
putKeyboardEvent(int keycode, byte state, int ch, long nanos, boolean repeat)180 	private void putKeyboardEvent(int keycode, byte state, int ch, long nanos, boolean repeat) {
181 		tmp_event.clear();
182 		tmp_event.putInt(keycode).put(state).putInt(ch).putLong(nanos).put(repeat ? (byte)1 : (byte)0);
183 		tmp_event.flip();
184 		event_queue.putEvent(tmp_event);
185 	}
186 
lookupStringISO88591(long event_ptr, int[] translation_buffer)187 	private int lookupStringISO88591(long event_ptr, int[] translation_buffer) {
188 		int i;
189 
190 		int num_chars = lookupString(event_ptr, native_translation_buffer, compose_status);
191 		for (i = 0; i < num_chars; i++) {
192 			translation_buffer[i] = ((int)native_translation_buffer.get(i)) & 0xff;
193 		}
194 		return num_chars;
195 	}
lookupString(long event_ptr, ByteBuffer buffer, ByteBuffer compose_status)196 	private static native int lookupString(long event_ptr, ByteBuffer buffer, ByteBuffer compose_status);
197 
lookupStringUnicode(long event_ptr, int[] translation_buffer)198 	private int lookupStringUnicode(long event_ptr, int[] translation_buffer) {
199 		int status = utf8LookupString(xic, event_ptr, native_translation_buffer, native_translation_buffer.position(), native_translation_buffer.remaining());
200 		if (status != XLookupChars && status != XLookupBoth)
201 			return 0;
202 		native_translation_buffer.flip();
203 		utf8_decoder.decode(native_translation_buffer, char_buffer, true);
204 		native_translation_buffer.compact();
205 		char_buffer.flip();
206 		int i = 0;
207 		while (char_buffer.hasRemaining() && i < translation_buffer.length) {
208 			translation_buffer[i++] = char_buffer.get();
209 		}
210 		char_buffer.compact();
211 		return i;
212 	}
utf8LookupString(long xic, long event_ptr, ByteBuffer buffer, int pos, int size)213 	private static native int utf8LookupString(long xic, long event_ptr, ByteBuffer buffer, int pos, int size);
214 
lookupString(long event_ptr, int[] translation_buffer)215 	private int lookupString(long event_ptr, int[] translation_buffer) {
216 		if (xic != 0) {
217 			return lookupStringUnicode(event_ptr, translation_buffer);
218 		} else
219 			return lookupStringISO88591(event_ptr, translation_buffer);
220 	}
221 
translateEvent(long event_ptr, int keycode, byte key_state, long nanos, boolean repeat)222 	private void translateEvent(long event_ptr, int keycode, byte key_state, long nanos, boolean repeat) {
223 		int num_chars, i;
224 		int ch;
225 
226 		num_chars = lookupString(event_ptr, temp_translation_buffer);
227 		if (num_chars > 0) {
228 			ch = temp_translation_buffer[0];
229 			putKeyboardEvent(keycode, key_state, ch, nanos, repeat);
230 			for (i = 1; i < num_chars; i++) {
231 				ch = temp_translation_buffer[i];
232 				putKeyboardEvent(0, (byte)0, ch, nanos, repeat);
233 			}
234 		} else {
235 			putKeyboardEvent(keycode, key_state, 0, nanos, repeat);
236 		}
237 	}
238 
isKeypadKeysym(long keysym)239 	private static boolean isKeypadKeysym(long keysym) {
240 		return (0xFF80 <= keysym && keysym <= 0xFFBD) ||
241 			(0x11000000 <= keysym && keysym <= 0x1100FFFF);
242 	}
243 
isNoSymbolOrVendorSpecific(long keysym)244 	private static boolean isNoSymbolOrVendorSpecific(long keysym) {
245 		return keysym == NoSymbol || (keysym & (1 << 28)) != 0;
246 	}
247 
getKeySym(long event_ptr, int group, int index)248 	private static long getKeySym(long event_ptr, int group, int index) {
249 		long keysym = lookupKeysym(event_ptr, group*2 + index);
250 		if (isNoSymbolOrVendorSpecific(keysym) && index == 1) {
251 			keysym = lookupKeysym(event_ptr, group*2 + 0);
252 		}
253 		if (isNoSymbolOrVendorSpecific(keysym) && group == 1)
254 			keysym = getKeySym(event_ptr, 0, index);
255 		return keysym;
256 	}
lookupKeysym(long event_ptr, int index)257 	private static native long lookupKeysym(long event_ptr, int index);
toUpper(long keysym)258 	private static native long toUpper(long keysym);
259 
getKeycode(long event_ptr, int event_state)260 	private int getKeycode(long event_ptr, int event_state) {
261 		boolean shift = (event_state & (ShiftMask | shift_lock_mask)) != 0;
262 		int group = (event_state & modeswitch_mask) != 0 ? 1 : 0;
263 		long keysym;
264 		if ((event_state & numlock_mask) != 0 && isKeypadKeysym(keysym = getKeySym(event_ptr, group, 1))) {
265 			if ( shift )
266 				keysym = getKeySym(event_ptr, group, 0);
267 		} else {
268 			keysym = getKeySym(event_ptr, group, 0);
269 			if ( shift ^ ((event_state & caps_lock_mask) != 0) )
270 				keysym = toUpper(keysym);
271 		}
272 		return LinuxKeycodes.mapKeySymToLWJGLKeyCode(keysym);
273 	}
274 
getKeyState(int event_type)275 	private static byte getKeyState(int event_type) {
276 		switch (event_type) {
277 			case LinuxEvent.KeyPress:
278 				return 1;
279 			case LinuxEvent.KeyRelease:
280 				return 0;
281 			default:
282 				throw new IllegalArgumentException("Unknown event_type: " + event_type);
283 		}
284 	}
285 
286 	/** This is called when the window loses focus: we release all currently pressed keys. */
releaseAll()287 	void releaseAll() {
288 		for ( int i = 0; i < key_down_buffer.length; i++ ) {
289 			if ( key_down_buffer[i] != 0 ) {
290 				key_down_buffer[i] = 0;
291 				putKeyboardEvent(i, (byte)0, 0, 0L, false);
292 			}
293 		}
294 	}
295 
handleKeyEvent(long event_ptr, long millis, int event_type, int event_keycode, int event_state)296 	private void handleKeyEvent(long event_ptr, long millis, int event_type, int event_keycode, int event_state) {
297 		int keycode = getKeycode(event_ptr, event_state);
298 		byte key_state = getKeyState(event_type);
299 		boolean repeat = key_state == key_down_buffer[keycode];
300 		if ( repeat && event_type == LinuxEvent.KeyRelease ) // This can happen for modifier keys after losing and regaining focus.
301 			return;
302 		key_down_buffer[keycode] = key_state;
303 		long nanos = millis*1000000;
304 		if (event_type == LinuxEvent.KeyPress) {
305 			if (has_deferred_event) {
306 				if (nanos == deferred_nanos && event_keycode == deferred_event_keycode) {
307 					has_deferred_event = false;
308 					repeat = true; // Repeated event
309 				} else
310 					flushDeferredEvent();
311 			}
312 			translateEvent(event_ptr, keycode, key_state, nanos, repeat);
313 		} else {
314 			flushDeferredEvent();
315 			has_deferred_event = true;
316 			deferred_keycode = keycode;
317 			deferred_event_keycode = event_keycode;
318 			deferred_nanos = nanos;
319 			deferred_key_state = key_state;
320 		}
321 	}
322 
flushDeferredEvent()323 	private void flushDeferredEvent() {
324 		if (has_deferred_event) {
325 			putKeyboardEvent(deferred_keycode, deferred_key_state, 0, deferred_nanos, false);
326 			has_deferred_event = false;
327 		}
328 	}
329 
filterEvent(LinuxEvent event)330 	public boolean filterEvent(LinuxEvent event) {
331 		switch (event.getType()) {
332 			case LinuxEvent.KeyPress: /* Fall through */
333 			case LinuxEvent.KeyRelease:
334 				handleKeyEvent(event.getKeyAddress(), event.getKeyTime(), event.getKeyType(), event.getKeyKeyCode(), event.getKeyState());
335 				return true;
336 			default:
337 				break;
338 		}
339 		return false;
340 	}
341 }
342