Skip to content

Commit

Permalink
[WIP] IME support (#43)
Browse files Browse the repository at this point in the history
* Reapply "Rewrite Keyboard to behave like lwjgl3 (#42)"

This reverts commit 0713491.

* Everthing works now

* Add char mappings for Enter, Escape, Tab

Closes #44

---------

Co-authored-by: geeky_kappa <[email protected]>
  • Loading branch information
eigenraven and kappa-maintainer authored Mar 16, 2023
1 parent 0713491 commit 7b925da
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 132 deletions.
16 changes: 0 additions & 16 deletions src/main/java/me/eigenraven/lwjgl3ify/core/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,6 @@ public class Config {
public static boolean DEBUG_PRINT_KEY_EVENTS = false;
public static boolean DEBUG_PRINT_MOUSE_EVENTS = false;

public static boolean MBE_ENABLED = true;
public static boolean IME_ENABLED = false;
public static boolean IME_F12_TOGGLE = false;
public static boolean IME_SYS_TOGGLE = false;

public static boolean SHOW_JAVA_VERSION = true;
public static boolean SHOW_LWJGL_VERSION = true;

Expand Down Expand Up @@ -137,17 +132,6 @@ public static void reloadConfigObject() {
DEBUG_PRINT_MOUSE_EVENTS,
"Print mouse-related events to the log");

MBE_ENABLED = config
.getBoolean("handleMultibyteInput", CATEGORY_CORE, MBE_ENABLED, "Enables multibyte character input.");
IME_ENABLED = config.getBoolean("enabled", CATEGORY_IME, IME_ENABLED, "Enables IME support for CJK input.");
IME_F12_TOGGLE = config
.getBoolean("f12Toggle", CATEGORY_IME, IME_F12_TOGGLE, "Enables switching IME mode by pressing F12.");
IME_SYS_TOGGLE = config.getBoolean(
"sysToggle",
CATEGORY_IME,
IME_SYS_TOGGLE,
"Enables switching IME mode by pressing Ctrl/LWin + Shift/Space");

SHOW_JAVA_VERSION = config
.getBoolean("showJavaVersion", CATEGORY_CORE, SHOW_JAVA_VERSION, "Show java version in the debug hud");
SHOW_LWJGL_VERSION = config.getBoolean(
Expand Down
123 changes: 41 additions & 82 deletions src/main/java/org/lwjglx/input/Keyboard.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import me.eigenraven.lwjgl3ify.core.Config;

import org.apache.commons.lang3.StringUtils;
import org.lwjgl.glfw.GLFW;
Expand Down Expand Up @@ -163,10 +160,7 @@ public class Keyboard {

public static final int keyCount;

private static EventQueue queue = new EventQueue(128);
private static BlockingQueue<Character> imeCharQueue = new ArrayBlockingQueue<>(128);

private enum KeyState {
public enum KeyState {

PRESS(true),
RELEASE(false),
Expand All @@ -181,19 +175,8 @@ private enum KeyState {

private static boolean doRepeatEvents = true;

private static int[] keyEvents = new int[queue.getMaxEvents()];
private static char[] keySpecificChars = new char[queue.getMaxEvents()];
private static KeyState[] keyEventStates = new KeyState[queue.getMaxEvents()];

static {
Arrays.fill(keyEventStates, KeyState.RELEASE);
}

private static long[] nanoTimeEvents = new long[queue.getMaxEvents()];
private static char[] keyEventChars = new char[Short.MAX_VALUE];

public static final int KEYBOARD_SIZE = Short.MAX_VALUE;

public static Queue<KeyEvent> eventQueue = new ArrayBlockingQueue<>(256);
private static final String[] keyName = new String[Short.MAX_VALUE];
private static final Map<String, Integer> keyMap = new HashMap<>(Short.MAX_VALUE);

Expand Down Expand Up @@ -223,13 +206,10 @@ private enum KeyState {
}
keyMap.put(keyName[i], i);
}
for (int key = 32; key < 128; key++) {
keyEventChars[KeyCodes.glfwToLwjgl(key)] = (char) key;
}
keyEventChars[KEY_NONE] = '\0';
eventQueue.add(new KeyEvent(0, '\0', KeyState.RELEASE, Sys.getNanoTime()));
}

public static void addGlfwKeyEvent(long window, int key, int scancode, int action, int mods) {
public static void addGlfwKeyEvent(long window, int key, int scancode, int action, int mods, char c) {
final KeyState state;
switch (action) {
case GLFW.GLFW_PRESS -> state = KeyState.PRESS;
Expand All @@ -242,52 +222,21 @@ public static void addGlfwKeyEvent(long window, int key, int scancode, int actio
}
default -> state = KeyState.RELEASE;
}
final int nextPos = queue.getNextPos();
keyEvents[nextPos] = KeyCodes.glfwToLwjgl(key);
keyEventStates[nextPos] = state;
nanoTimeEvents[nextPos] = Sys.getNanoTime();
keySpecificChars[nextPos] = '\0';

queue.add();
try {
eventQueue.add(new KeyEvent(KeyCodes.glfwToLwjgl(key), c, state, Sys.getNanoTime()));
} catch (IllegalStateException ignored) {}
}

public static void addKeyEvent(int key, boolean pressed) {
final int nextPos = queue.getNextPos();
keyEvents[nextPos] = KeyCodes.glfwToLwjgl(key);
keyEventStates[nextPos] = pressed ? KeyState.PRESS : KeyState.RELEASE;
nanoTimeEvents[nextPos] = Sys.getNanoTime();
keySpecificChars[nextPos] = '\0';

queue.add();
}

private static void duplicateKeyEvent() {
final int nextPos = queue.getNextPos();
final int prevPos = queue.getLastWrittenPos();
keyEvents[nextPos] = keyEvents[prevPos];
keyEventStates[nextPos] = keyEventStates[prevPos];
nanoTimeEvents[nextPos] = nanoTimeEvents[prevPos];
keySpecificChars[nextPos] = keySpecificChars[prevPos];
queue.add();
try {
eventQueue.add(new KeyEvent(key, '\0', pressed ? KeyState.PRESS : KeyState.RELEASE, Sys.getNanoTime()));
} catch (IllegalStateException ignored) {}
}

public static void addCharEvent(int key, char c) {
final int nextPos = queue.getNextPos();
final int prevPos = queue.getLastWrittenPos();
int index = KeyCodes.glfwToLwjgl(key);
keyEventChars[index] = c;
if (Config.MBE_ENABLED) {
if (keySpecificChars[prevPos] == '\0') {
keySpecificChars[prevPos] = c;
} else {
duplicateKeyEvent();
keySpecificChars[nextPos] = c;
}
}
}

public static void addIMECharEvent(char c) {
imeCharQueue.offer(c);
try {
eventQueue.add(new KeyEvent(KEY_NONE, c, KeyState.PRESS, Sys.getNanoTime()));
} catch (IllegalStateException ignored) {}
}

public static void create() throws LWJGLException {}
Expand All @@ -313,41 +262,36 @@ public static int getKeyCount() {
}

public static int getNumKeyboardEvents() {
return queue.getEventCount();
return eventQueue.size();
}

public static boolean isRepeatEvent() {
return keyEventStates[queue.getCurrentPos()] == KeyState.REPEAT;
return eventQueue.peek().state == KeyState.REPEAT;
}

public static boolean next() {
return queue.next();
boolean next = eventQueue.size() > 1;
if (next) {
eventQueue.remove();
}
return next;
}

public static int getEventKey() {
return keyEvents[queue.getCurrentPos()];
return eventQueue.peek().key;
}

public static char getEventCharacter() {
if (!imeCharQueue.isEmpty() && Display.imeOn) {
return imeCharQueue.remove();
}
final int eventKey = getEventKey();
final char eventSpecificChar = keySpecificChars[queue.getCurrentPos()];
// On some systems it seems esc and backspace can generate broken chars sometimes, make sure they always work
return switch (eventKey) {
case KEY_ESCAPE -> '\0';
case KEY_BACK -> '\b';
default -> (eventSpecificChar != '\0') ? eventSpecificChar : keyEventChars[eventKey];
};
return eventQueue.peek().aChar;

}

public static boolean getEventKeyState() {
return keyEventStates[queue.getCurrentPos()].isPressed || (!imeCharQueue.isEmpty() && Display.imeOn);
return eventQueue.peek().state.isPressed;
}

public static long getEventNanoseconds() {
return nanoTimeEvents[queue.getCurrentPos()];
return eventQueue.peek().nano;
}

public static String getKeyName(int key) {
Expand All @@ -373,4 +317,19 @@ public static boolean isCreated() {
}

public static void destroy() {}

public static class KeyEvent {

public int key;
public char aChar;
public KeyState state;
public long nano;

public KeyEvent(int key, char aChar, KeyState state, long nano) {
this.key = key;
this.aChar = aChar;
this.state = state;
this.nano = nano;
}
}
}
67 changes: 33 additions & 34 deletions src/main/java/org/lwjglx/opengl/Display.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ public class Display {
private static boolean latestResized = false;
private static int latestWidth = 0;
private static int latestHeight = 0;
public static boolean imeOn = false;
private static boolean cancelNextChar = false;
private static Keyboard.KeyEvent ingredientKeyEvent;
private static ByteBuffer[] savedIcons;

static {
Expand Down Expand Up @@ -155,7 +156,7 @@ public static void create() {
public void invoke(long window, int key, int scancode, int action, int mods) {
if (Config.DEBUG_PRINT_KEY_EVENTS) {
Lwjgl3ify.LOG.info(
"[DEBUG-KEY] key window:{} key:{} scancode:{} action:{} mods:{} charname:{} char:{}",
"[DEBUG-KEY] key window:{} key:{} scancode:{} action:{} mods:{} charname:{} naive-char:{}",
window,
key,
scancode,
Expand All @@ -164,33 +165,30 @@ public void invoke(long window, int key, int scancode, int action, int mods) {
KeyEvent.getKeyText(KeyCodes.lwjglToAwt(KeyCodes.glfwToLwjgl(key))),
(key >= 32 && key < 127) ? ((char) key) : '?');
}
latestEventKey = key;
if (Config.IME_ENABLED) {
if (Config.IME_F12_TOGGLE && key == GLFW_KEY_F12 && action == 0) {
imeOn = !imeOn;
cancelNextChar = false;
if (key > GLFW_KEY_SPACE && key <= GLFW_KEY_GRAVE_ACCENT) { // Handle keys have a char. Exclude space to
// avoid extra input when switching IME
if ((GLFW_MOD_CONTROL & mods) != 0) { // Handle ctrl + x/c/v.
Keyboard.addGlfwKeyEvent(window, key, scancode, action, mods, (char) (key & 0x1f));
cancelNextChar = true; // Cancel char event from ctrl key since its already handled here
} else if (action > 0) { // Delay press and repeat key event to actual char input. There is ALWAYS a
// char after them
ingredientKeyEvent = new Keyboard.KeyEvent(
KeyCodes.glfwToLwjgl(key),
'\0',
action > 1 ? Keyboard.KeyState.REPEAT : Keyboard.KeyState.PRESS,
Sys.getNanoTime());
} else { // Release event
Keyboard.addGlfwKeyEvent(window, key, scancode, action, mods, '\0');
}
if (Config.IME_SYS_TOGGLE
&& (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL)
|| Keyboard.isKeyDown(Keyboard.KEY_LWIN))) {
if (key == GLFW_KEY_SPACE || key == GLFW_KEY_LEFT_SHIFT || key == GLFW_KEY_RIGHT_SHIFT) {
imeOn = !imeOn;
return;
}
}
if (imeOn) {
if (key > 256) {
Keyboard.addGlfwKeyEvent(window, key, scancode, action, mods);
}
} else {
Keyboard.addGlfwKeyEvent(window, key, scancode, action, mods);
}
} else {
Keyboard.addGlfwKeyEvent(window, key, scancode, action, mods);
}
// Ctrl should generate ASCII modifier keys (0x01-0x1F), glfw does not give use char events for this
if ((mods & GLFW_MOD_CONTROL) != 0 && key >= GLFW_KEY_A && key <= GLFW_KEY_Z) {
int codepoint = key & 0x1F;
Keyboard.addCharEvent(key, (char) codepoint);
} else { // Other key with no char event associated
char mappedChar = switch (key) {
case GLFW_KEY_ENTER -> 0x0D;
case GLFW_KEY_ESCAPE -> 0x1B;
case GLFW_KEY_TAB -> 0x09;
default -> '\0';
};
Keyboard.addGlfwKeyEvent(window, key, scancode, action, mods, mappedChar);
}
}
};
Expand All @@ -206,13 +204,14 @@ public void invoke(long window, int codepoint) {
codepoint,
(char) codepoint);
}
if (imeOn) {
Keyboard.addKeyEvent(GLFW_KEY_UNKNOWN, true);
Keyboard.addIMECharEvent((char) codepoint);
Keyboard.addKeyEvent(GLFW_KEY_UNKNOWN, false);

if (cancelNextChar) { // Char event being cancelled
cancelNextChar = false;
} else if (ingredientKeyEvent != null) {
ingredientKeyEvent.aChar = (char) codepoint; // Send char with ASCII key event here
Keyboard.eventQueue.add(ingredientKeyEvent);
ingredientKeyEvent = null;
} else {
Keyboard.addCharEvent(latestEventKey, (char) codepoint);
Keyboard.addCharEvent(0, (char) codepoint); // Non-ASCII chars
}
}
};
Expand Down

0 comments on commit 7b925da

Please sign in to comment.