Skip to content
Open
17 changes: 10 additions & 7 deletions src/bms/player/beatoraja/config/KeyConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public void render() {
int[] keysa = KEYSA[mode];

if (keyinput) {
if (keyinput && input.getKeyBoardInputProcesseor().getLastPressedKey() != -1) {
if (keyinput && input.getKeyBoardInputProcesseor().getLibgdxLastPressedKey() != -1) {
setKeyboardKeyAssign(keysa[cursorpos]);
// System.out.println(input.getKeyBoardInputProcesseor().getLastPressedKey());
keyinput = false;
Expand Down Expand Up @@ -235,7 +235,10 @@ public void render() {
midiconfig.setKeyAssign(MODE_HINT[mode], true);
}

if (input.isControlKeyPressed(ControlKeys.ENTER)) {
// We get newly assigned keycodes via `getLibgdxLastPressedKey`. Since this
// function is incompatible with other keyboard relate functions, we cannot
// use `input.isControlKeyPressed` here. See `getLibgdxLastPressedKey` documentation.
if (input.getKeyBoardInputProcesseor().getLibgdxLastPressedKey() == ControlKeys.ENTER.keycode) {
setKeyAssignMode(cursorpos);
}

Expand Down Expand Up @@ -316,7 +319,7 @@ public void render() {
}

public void setKeyAssignMode(final int index) {
input.getKeyBoardInputProcesseor().setLastPressedKey(-1);
input.getKeyBoardInputProcesseor().setLibgdxLastPresssedKey(-1);
input.getKeyBoardInputProcesseor().getMouseScratchInput().setLastMouseScratch(-1);
for (BMControllerInputProcessor bmc : controllers) {
bmc.setLastPressedButton(-1);
Expand Down Expand Up @@ -396,16 +399,16 @@ private int getKeyboardKeyAssign(int index) {
}

private void setKeyboardKeyAssign(int index) {
if (keyboard.isReservedKey(keyboard.getLastPressedKey())) {
if (keyboard.isReservedKey(keyboard.getLibgdxLastPressedKey())) {
return;
}
resetKeyAssign(index);
if (index >= 0) {
keyboardConfig.getKeyAssign()[index] = keyboard.getLastPressedKey();
keyboardConfig.getKeyAssign()[index] = keyboard.getLibgdxLastPressedKey();
} else if (index == -1) {
keyboardConfig.setStart(keyboard.getLastPressedKey());
keyboardConfig.setStart(keyboard.getLibgdxLastPressedKey());
} else if (index == -2) {
keyboardConfig.setSelect(keyboard.getLastPressedKey());
keyboardConfig.setSelect(keyboard.getLibgdxLastPressedKey());
}
}

Expand Down
36 changes: 22 additions & 14 deletions src/bms/player/beatoraja/input/KeyBoardInputProcesseor.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class KeyBoardInputProcesseor extends BMSPlayerInputDevice implements Inp
/**
* 最後に押されたキー
*/
private int lastPressedKey = -1;
private int libgdxLastPressedKey = -1;

private boolean textmode = false;

Expand Down Expand Up @@ -77,7 +77,7 @@ public void setConfig(KeyboardConfig config) {
}

public boolean keyDown(int keycode) {
setLastPressedKey(keycode);
setLibgdxLastPresssedKey(keycode);
return true;
}

Expand All @@ -92,7 +92,7 @@ public boolean keyUp(int keycode) {
public void clear() {
// Arrays.fill(keystate, false);
Arrays.fill(keytime, Long.MIN_VALUE);
lastPressedKey = -1;
libgdxLastPressedKey = -1;
mouseScratchInput.clear();
}

Expand All @@ -102,7 +102,8 @@ public void poll(final long microtime) {
if(keys[i] < 0) {
continue;
}
final boolean pressed = Gdx.input.isKeyPressed(keys[i]);

final boolean pressed = KeyPressedPreferNative.isKeyPressed(keys[i]);
if (pressed != keystate[keys[i]] && microtime >= keytime[keys[i]] + duration * 1000) {
keystate[keys[i]] = pressed;
keytime[keys[i]] = microtime;
Expand All @@ -111,20 +112,20 @@ public void poll(final long microtime) {
}
}

final boolean startpressed = Gdx.input.isKeyPressed(control[0]);
final boolean startpressed = KeyPressedPreferNative.isKeyPressed(control[0]);
if (startpressed != keystate[control[0]]) {
keystate[control[0]] = startpressed;
this.bmsPlayerInputProcessor.startChanged(startpressed);
}
final boolean selectpressed = Gdx.input.isKeyPressed(control[1]);
final boolean selectpressed = KeyPressedPreferNative.isKeyPressed(control[1]);
if (selectpressed != keystate[control[1]]) {
keystate[control[1]] = selectpressed;
this.bmsPlayerInputProcessor.setSelectPressed(selectpressed);
}
}

for (ControlKeys key : ControlKeys.values()) {
final boolean pressed = Gdx.input.isKeyPressed(key.keycode);
final boolean pressed = KeyPressedPreferNative.isKeyPressed(key.keycode);
if (!(textmode && key.text) && pressed != keystate[key.keycode]) {
keystate[key.keycode] = pressed;
keytime[key.keycode] = microtime;
Expand All @@ -136,9 +137,9 @@ public void poll(final long microtime) {
}

private int currentlyHeldModifiers() {
boolean shift = Gdx.input.isKeyPressed(Keys.SHIFT_LEFT) || Gdx.input.isKeyPressed(Keys.SHIFT_RIGHT);
boolean ctrl = Gdx.input.isKeyPressed(Keys.CONTROL_LEFT) || Gdx.input.isKeyPressed(Keys.CONTROL_RIGHT);
boolean alt = Gdx.input.isKeyPressed(Keys.ALT_LEFT) || Gdx.input.isKeyPressed(Keys.ALT_RIGHT);
boolean shift = KeyPressedPreferNative.isKeyPressed(Keys.SHIFT_LEFT) || KeyPressedPreferNative.isKeyPressed(Keys.SHIFT_RIGHT);
boolean ctrl = KeyPressedPreferNative.isKeyPressed(Keys.CONTROL_LEFT) || KeyPressedPreferNative.isKeyPressed(Keys.CONTROL_RIGHT);
boolean alt = KeyPressedPreferNative.isKeyPressed(Keys.ALT_LEFT) || KeyPressedPreferNative.isKeyPressed(Keys.ALT_RIGHT);
return (shift ? MASK_SHIFT : 0) | (ctrl ? MASK_CTRL : 0) | (alt ? MASK_ALT : 0);
}

Expand Down Expand Up @@ -214,12 +215,19 @@ public boolean touchUp(int arg0, int arg1, int arg2, int arg3) {
return false;
}

public int getLastPressedKey() {
return lastPressedKey;
/**
* Get last key input from libgdx event system. Other functions like 'isKeyPressed' may utilize native
* system APIs for faster key input, so those functions may reflect key state FASTER than this functions.
* Don't mix `getLibgdxLastPressedKey` and other keyboard state functions.
*
* @return Last inputed key, as reported by libgdx event system.
*/
public int getLibgdxLastPressedKey() {
return libgdxLastPressedKey;
}

public void setLastPressedKey(int lastPressedKey) {
this.lastPressedKey = lastPressedKey;
public void setLibgdxLastPresssedKey(int lastPressedKey) {
this.libgdxLastPressedKey = lastPressedKey;
}

public MouseScratchInput getMouseScratchInput() {
Expand Down
249 changes: 249 additions & 0 deletions src/bms/player/beatoraja/input/KeyPressedPreferNative.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
// Copyright (c) 2024 Park Hyunwoo
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

package bms.player.beatoraja.input;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import org.lwjgl.LWJGLUtil;
import org.lwjgl.opengl.Display;

import java.lang.reflect.Method;

import static bms.player.beatoraja.input.WinNativeMethods.GetForegroundWindow;
import static bms.player.beatoraja.input.WinNativeMethods.isKeyPressedAsync;
import static bms.player.beatoraja.input.WinVKCode.*;

/**
* Performant key getter for beatoraja
* Gdx.input.isKeyPressed uses GLFW event system to process keys, so in case of
* delay or CPU overuse, it might not be performant enough to process keys ASAP.
* <p>
* Windows has `User32.GetAsyncKeyState` as a faster alternative, so this library
* wraps around the api for libgdx.
*/
public class KeyPressedPreferNative {
private static long beatorajaHWND = 0;

/**
* Get HWND pointer value of beatoraja window.
*
* @return
*/
private static long windowsGetBeatorajaHWND() {
if (beatorajaHWND == 0) {
// Reflection hack.
try {
Method getImplementationMethod = Display.class.getDeclaredMethod("getImplementation");
getImplementationMethod.setAccessible(true);
Object implementation = getImplementationMethod.invoke(null);
Class<?> windowsDisplayClass = Class.forName("org.lwjgl.opengl.WindowsDisplay");

if (!windowsDisplayClass.isInstance(implementation)) {
throw new Exception("The current platform must be Windows!");
}

Method getHwndMethod = windowsDisplayClass.getDeclaredMethod("getHwnd");
getHwndMethod.setAccessible(true);

beatorajaHWND = (long) getHwndMethod.invoke(implementation);
} catch (Exception exception) {
throw new RuntimeException(exception);
}
}
return beatorajaHWND;
}

private static boolean isKeyPressedAsyncVK(WinVKCode vk) {
return isKeyPressedAsync(vk.code);
}

private static boolean windowsIsKeyPressed(int gdxKey) {
// Note: GetAsyncKeyState checks if the key is pressed regardless of whether the application window
// is in focus or not. This may severely interfere with user usability when beatoraja is NOT
// in foreground. (e.g minimized). We check if the beatoraja is the focused window before
// using GetAsyncKeyState.
long foregroundWindow = GetForegroundWindow();
if (foregroundWindow != windowsGetBeatorajaHWND()) {
return Gdx.input.isKeyPressed(gdxKey);
}

// Key list reference: https://github.com/libgdx/libgdx/blob/1.8.0/backends/gdx-backend-lwjgl/src/com/badlogic/gdx/backends/lwjgl/LwjglInput.java
// Vkey reference: https://github.com/LWJGL/lwjgl/blob/master/src/java/org/lwjgl/opengl/WindowsKeycodes.java
switch (gdxKey) {
case Input.Keys.LEFT_BRACKET:
return isKeyPressedAsyncVK(VK_OEM_4);
case Input.Keys.RIGHT_BRACKET:
return isKeyPressedAsyncVK(VK_OEM_6);
case Input.Keys.GRAVE:
return isKeyPressedAsyncVK(VK_OEM_8);
case Input.Keys.STAR:
return isKeyPressedAsyncVK(VK_MULTIPLY);
case Input.Keys.NUM:
return isKeyPressedAsyncVK(VK_NUMLOCK);
case Input.Keys.PERIOD:
return isKeyPressedAsyncVK(VK_OEM_PERIOD);
case Input.Keys.SLASH:
return isKeyPressedAsyncVK(VK_OEM_2);
case Input.Keys.SYM:
return isKeyPressedAsyncVK(VK_RWIN);
case Input.Keys.EQUALS:
return isKeyPressedAsyncVK(VK_OEM_PLUS);
case Input.Keys.COMMA:
return isKeyPressedAsyncVK(VK_OEM_COMMA);
case Input.Keys.ENTER:
return isKeyPressedAsyncVK(VK_RETURN);
case Input.Keys.NUM_0:
case Input.Keys.NUM_1:
case Input.Keys.NUM_2:
case Input.Keys.NUM_3:
case Input.Keys.NUM_4:
case Input.Keys.NUM_5:
case Input.Keys.NUM_6:
case Input.Keys.NUM_7:
case Input.Keys.NUM_8:
case Input.Keys.NUM_9:
return isKeyPressedAsync(0x30 + gdxKey - Input.Keys.NUM_0);
case Input.Keys.A:
case Input.Keys.B:
case Input.Keys.C:
case Input.Keys.D:
case Input.Keys.E:
case Input.Keys.F:
case Input.Keys.G:
case Input.Keys.H:
case Input.Keys.I:
case Input.Keys.J:
case Input.Keys.K:
case Input.Keys.L:
case Input.Keys.M:
case Input.Keys.N:
case Input.Keys.O:
case Input.Keys.P:
case Input.Keys.Q:
case Input.Keys.R:
case Input.Keys.S:
case Input.Keys.T:
case Input.Keys.U:
case Input.Keys.V:
case Input.Keys.W:
case Input.Keys.X:
case Input.Keys.Y:
case Input.Keys.Z:
return isKeyPressedAsync(0x41 + gdxKey - Input.Keys.A);

// Some people *might* want to use control keys for gameplays, so
// unlike F1~F12 keys we just use GetAsyncKeyState here.
case Input.Keys.ALT_LEFT:
return isKeyPressedAsyncVK(VK_LMENU);
case Input.Keys.ALT_RIGHT:
return isKeyPressedAsyncVK(VK_RMENU);
case Input.Keys.BACKSLASH:
return isKeyPressedAsyncVK(VK_OEM_5);
case Input.Keys.FORWARD_DEL:
return isKeyPressedAsyncVK(VK_DELETE);
case Input.Keys.DPAD_LEFT:
return isKeyPressedAsyncVK(VK_LEFT);
case Input.Keys.DPAD_RIGHT:
return isKeyPressedAsyncVK(VK_RIGHT);
case Input.Keys.DPAD_UP:
return isKeyPressedAsyncVK(VK_UP);
case Input.Keys.DPAD_DOWN:
return isKeyPressedAsyncVK(VK_DOWN);
case Input.Keys.HOME:
return isKeyPressedAsyncVK(VK_HOME);
case Input.Keys.MINUS:
return isKeyPressedAsyncVK(VK_SUBTRACT);
case Input.Keys.PLUS:
return isKeyPressedAsyncVK(VK_ADD);
case Input.Keys.SEMICOLON:
case Input.Keys.COLON:
return isKeyPressedAsyncVK(VK_OEM_1);
case Input.Keys.SHIFT_LEFT:
return isKeyPressedAsyncVK(VK_LSHIFT);
case Input.Keys.SHIFT_RIGHT:
return isKeyPressedAsyncVK(VK_RSHIFT);
case Input.Keys.SPACE:
return isKeyPressedAsyncVK(VK_SPACE);
case Input.Keys.TAB:
return isKeyPressedAsyncVK(VK_TAB);
case Input.Keys.CONTROL_LEFT:
return isKeyPressedAsyncVK(VK_LCONTROL);
case Input.Keys.CONTROL_RIGHT:
return isKeyPressedAsyncVK(VK_RCONTROL);
case Input.Keys.PAGE_DOWN:
return isKeyPressedAsyncVK(VK_NEXT);
case Input.Keys.PAGE_UP:
return isKeyPressedAsyncVK(VK_PRIOR);
case Input.Keys.ESCAPE:
return isKeyPressedAsyncVK(VK_ESCAPE);
case Input.Keys.END:
return isKeyPressedAsyncVK(VK_END);
case Input.Keys.INSERT:
return isKeyPressedAsyncVK(VK_INSERT);
case Input.Keys.DEL:
return isKeyPressedAsyncVK(VK_BACK);
case Input.Keys.APOSTROPHE:
return isKeyPressedAsyncVK(VK_OEM_7);
case Input.Keys.F1:
case Input.Keys.F2:
case Input.Keys.F3:
case Input.Keys.F4:
case Input.Keys.F5:
case Input.Keys.F6:
case Input.Keys.F7:
case Input.Keys.F8:
case Input.Keys.F9:
case Input.Keys.F10:
case Input.Keys.F11:
case Input.Keys.F12: {
if (isKeyPressedAsync(VK_F1.code + (gdxKey - Input.Keys.F1))) {
// If we just use GetAsyncKeyState for polling F(\d+) keys,
// common keystrokes like Alt+F4 would not work.
// I suspect nobody would use function keys for gaming, so
// some delay in processing keys won't matter that much?
return Gdx.input.isKeyPressed(gdxKey);
}
return false;
}
case Input.Keys.NUMPAD_0:
case Input.Keys.NUMPAD_1:
case Input.Keys.NUMPAD_2:
case Input.Keys.NUMPAD_3:
case Input.Keys.NUMPAD_4:
case Input.Keys.NUMPAD_5:
case Input.Keys.NUMPAD_6:
case Input.Keys.NUMPAD_7:
case Input.Keys.NUMPAD_8:
case Input.Keys.NUMPAD_9:
return isKeyPressedAsync(gdxKey - Input.Keys.NUMPAD_0 + VK_NUMPAD0.code);
default: // Fallback
// TODO: above list should be exhaustive. Show error
return Gdx.input.isKeyJustPressed(gdxKey);
}
}

public static boolean isKeyPressed(int gdxKey) {
int platform = LWJGLUtil.getPlatform();
if (platform == LWJGLUtil.PLATFORM_WINDOWS) {
return windowsIsKeyPressed(gdxKey);
} else {
return Gdx.input.isKeyPressed(gdxKey);
}
}
}
Loading