Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion app/src/main/java/com/limelight/Game.java
Original file line number Diff line number Diff line change
Expand Up @@ -4183,7 +4183,9 @@ private void applyMouseMode(int mode) {
} else if (mode == 3) {
touchContextMap[i] = new RelativeTouchContext(conn, i, REFERENCE_HORIZ_RES, REFERENCE_VERT_RES, streamContainer, prefConfig);
} else {
touchContextMap[i] = new TrackpadContext(conn, i);
// Mode 2: Natural trackpad — pass sensitivity from Virtual Trackpad Settings
touchContextMap[i] = new TrackpadContext(conn, i, false,
prefConfig.touchPadSensitivity, prefConfig.touchPadYSensitity);
}
}

Expand All @@ -4209,6 +4211,23 @@ public void toggleHUD() {
//切换触控灵敏度开关
public void switchTouchSensitivity(){
prefConfig.enableTouchSensitivity = !prefConfig.enableTouchSensitivity;
String status = prefConfig.enableTouchSensitivity ? "ON" : "OFF";
Toast.makeText(this, "Touch Sensitivity: " + status, Toast.LENGTH_SHORT).show();
}

/**
* Inject a keyboard key event with proper modifier tracking.
* Used by ControllerHandler for X→Ctrl / Y→Esc remapping
* to match the virtual keyboard's modifier handling.
*/
public void injectKey(short keyCode, byte modifierMask, boolean down) {
if (down) {
modifierFlags |= modifierMask;
} else {
modifierFlags &= ~modifierMask;
}
byte direction = down ? KeyboardPacket.KEY_DOWN : KeyboardPacket.KEY_UP;
conn.sendKeyboardInput(keyCode, direction, getModifierState(), (byte)0);
}

public void disconnect() {
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/limelight/GameMenu.java
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ private void showAdvancedMenu(GameInputDevice device) {
showSpecialKeysMenu();
}));

options.add(new MenuOption(getString(R.string.game_menu_switch_touch_sensitivity_model), true, game::switchTouchSensitivity));
options.add(new MenuOption(getString(R.string.game_menu_switch_touch_sensitivity_model), game::switchTouchSensitivity));
if (device != null) {
options.addAll(device.getGameMenuOptions());
}
Expand Down
117 changes: 92 additions & 25 deletions app/src/main/java/com/limelight/binding/input/ControllerHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import android.view.Surface;
import android.widget.Toast;

import com.limelight.Game;
import com.limelight.GameMenu;
import com.limelight.LimeLog;
import com.limelight.R;
Expand All @@ -41,6 +42,7 @@
import com.limelight.binding.input.driver.UsbDriverService;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.input.ControllerPacket;
import com.limelight.nvstream.input.KeyboardPacket;
import com.limelight.nvstream.input.MouseButtonPacket;
import com.limelight.nvstream.jni.MoonBridge;
import com.limelight.preferences.PreferenceConfiguration;
Expand Down Expand Up @@ -1344,10 +1346,36 @@ private void sendControllerInputPacket(GenericControllerContext originalContext)
}
}

// Remap X→Ctrl / Y→Esc in mouse emulation mode.
// Route through Game.injectKey() for proper modifier state tracking
// (matches the virtual keyboard's modifier handling exactly).
if (prefConfig.remapXToCtrl) {
boolean xPressed = (inputMap & ControllerPacket.X_FLAG) != 0;
if (xPressed && !remapXActive) {
Game.instance.injectKey((short)0xA2, KeyboardPacket.MODIFIER_CTRL, true);
remapXActive = true;
} else if (!xPressed && remapXActive) {
Game.instance.injectKey((short)0xA2, KeyboardPacket.MODIFIER_CTRL, false);
remapXActive = false;
}
}
if (prefConfig.remapYToEsc) {
boolean yPressed = (inputMap & ControllerPacket.Y_FLAG) != 0;
if (yPressed && !remapYActive) {
Game.instance.injectKey((short)27, (byte)0, true);
remapYActive = true;
} else if (!yPressed && remapYActive) {
Game.instance.injectKey((short)27, (byte)0, false);
remapYActive = false;
}
}

conn.sendControllerInput(controllerNumber, getActiveControllerMask(),
(short)0, (byte)0, (byte)0, (short)0, (short)0, (short)0, (short)0);
}
else {
// X→Ctrl / Y→Esc only active in mouse emulation mode;
// in normal gamepad mode X/Y keep their original function.
conn.sendControllerInput(controllerNumber, getActiveControllerMask(),
inputMap,
leftTrigger, rightTrigger,
Expand Down Expand Up @@ -1957,36 +1985,75 @@ private Vector2d convertRawStickAxisToPixelMovement(short stickX, short stickY)
Vector2d vector = new Vector2d();
vector.initialize(stickX, stickY);
vector.scalarMultiply(1 / 32766.0f);
vector.scalarMultiply(4);
if (vector.getMagnitude() > 0) {
// Move faster as the stick is pressed further from center
vector.scalarMultiply(Math.pow(vector.getMagnitude(), 2));
double magnitude = vector.getMagnitude();
// sqrt curve for fine control at small deflections
// Full deflection: 12 pixels per frame (at 60Hz = 720 px/s)
double speedMultiplier = 12.0 * Math.sqrt(magnitude);
// Apply stick mouse sensitivity (default 100 = 1.0x)
speedMultiplier *= (prefConfig.stickMouseSensitivity / 100.0);
vector.scalarMultiply(speedMultiplier);
}
return vector;
}

private void sendEmulatedMouseMove(short x, short y, boolean mouseEmulationXDown, int mouseEmulationPixelMultiplier) {
Vector2d vector = convertRawStickAxisToPixelMovement(x, y);
if (vector.getMagnitude() >= 1) {
// Sub-pixel accumulation for smooth fractional deltas (per-stick: left & right)
private final double[] emulatedMousePendingX = new double[2];
private final double[] emulatedMousePendingY = new double[2];

// Used a fixed amount of mouse movement while the X button is pressed
if(mouseEmulationXDown == true )
{
// convert the vector number to -1 if negative and +1 if positive and then send the mouse movement in pixels
conn.sendMouseMove((short)(Integer.signum((int)vector.getX()) * mouseEmulationPixelMultiplier) , (short)(Integer.signum((int)-vector.getY()) * mouseEmulationPixelMultiplier) );
}
else {
// If X button is not pressed, base the movement on how much the stick is moved from the center
conn.sendMouseMove((short) vector.getX(), (short) -vector.getY());
private void sendEmulatedMouseMove(short x, short y, int stickIndex,
boolean xDown, int pixelMultiplier) {
Vector2d vector = convertRawStickAxisToPixelMovement(x, y);
if (vector.getMagnitude() >= 0.15) {
if (xDown) {
// X-button precision mode: fixed-step movement in stick direction
conn.sendMouseMove(
(short)(Integer.signum((int)vector.getX()) * pixelMultiplier),
(short)(Integer.signum((int)-vector.getY()) * pixelMultiplier));
} else {
// Accumulate sub-pixel deltas for smooth cursor movement
emulatedMousePendingX[stickIndex] += vector.getX();
emulatedMousePendingY[stickIndex] += -vector.getY();
short moveX = (short) emulatedMousePendingX[stickIndex];
short moveY = (short) emulatedMousePendingY[stickIndex];
if (moveX != 0 || moveY != 0) {
conn.sendMouseMove(moveX, moveY);
emulatedMousePendingX[stickIndex] -= moveX;
emulatedMousePendingY[stickIndex] -= moveY;
}
}
} else {
// Stick at center: clear pending deltas to prevent cursor flickering
emulatedMousePendingX[stickIndex] = 0;
emulatedMousePendingY[stickIndex] = 0;
}
}

private double emulatedScrollPendingY = 0;
private double emulatedScrollPendingX = 0;

// State machine for X→Ctrl / Y→Esc remapping
private boolean remapXActive = false;
private boolean remapYActive = false;

private void sendEmulatedMouseScroll(short x, short y) {
Vector2d vector = convertRawStickAxisToPixelMovement(x, y);
if (vector.getMagnitude() >= 1) {
conn.sendMouseHighResScroll((short)vector.getY());
conn.sendMouseHighResHScroll((short)vector.getX());
if (vector.getMagnitude() >= 0.15) {
emulatedScrollPendingY += vector.getY();
emulatedScrollPendingX += vector.getX();
short scrollY = (short) emulatedScrollPendingY;
short scrollX = (short) emulatedScrollPendingX;
if (scrollY != 0) {
conn.sendMouseHighResScroll(scrollY);
emulatedScrollPendingY -= scrollY;
}
if (scrollX != 0) {
conn.sendMouseHighResHScroll(scrollX);
emulatedScrollPendingX -= scrollX;
}
} else {
emulatedScrollPendingY = 0;
emulatedScrollPendingX = 0;
}
}

Expand Down Expand Up @@ -3056,7 +3123,9 @@ class GenericControllerContext implements GameInputDevice{
public int mouseEmulationPixelMultiplier = 1;

public int mouseEmulationLastInputMap;
public final int mouseEmulationReportPeriod = 50;
public int inputMapLastSent;
// Poll at ~60Hz for smooth cursor movement (was 50ms = 20Hz)
public final int mouseEmulationReportPeriod = 16;

public final Runnable mouseEmulationRunnable = new Runnable() {
@Override
Expand All @@ -3067,18 +3136,16 @@ public void run() {

// Send mouse events from analog sticks
if (prefConfig.analogStickForScrolling == PreferenceConfiguration.AnalogStickForScrolling.RIGHT) {

// Changed absolute value
sendEmulatedMouseMove(leftStickX, leftStickY, mouseEmulationXDown, mouseEmulationPixelMultiplier);
sendEmulatedMouseMove(leftStickX, leftStickY, 0, mouseEmulationXDown, mouseEmulationPixelMultiplier);
sendEmulatedMouseScroll(rightStickX, rightStickY);
}
else if (prefConfig.analogStickForScrolling == PreferenceConfiguration.AnalogStickForScrolling.LEFT) {
sendEmulatedMouseMove(rightStickX, rightStickY, mouseEmulationXDown, mouseEmulationPixelMultiplier);
sendEmulatedMouseMove(rightStickX, rightStickY, 0, mouseEmulationXDown, mouseEmulationPixelMultiplier);
sendEmulatedMouseScroll(leftStickX, leftStickY);
}
else {
sendEmulatedMouseMove(leftStickX, leftStickY, mouseEmulationXDown, mouseEmulationPixelMultiplier);
sendEmulatedMouseMove(rightStickX, rightStickY, mouseEmulationXDown, mouseEmulationPixelMultiplier);
sendEmulatedMouseMove(leftStickX, leftStickY, 0, mouseEmulationXDown, mouseEmulationPixelMultiplier);
sendEmulatedMouseMove(rightStickX, rightStickY, 1, mouseEmulationXDown, mouseEmulationPixelMultiplier);
}

// Requeue the callback
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ public void refreshLayout() {
} else {
DisplayMetrics screen = context.getResources().getDisplayMetrics();
width = screen.widthPixels;
height = (int) (screen.heightPixels * 0.5);
height = (int) (screen.heightPixels * (prefConfig.keyboardHeightPercent / 100.0));
}
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(width, height);
params.gravity = Gravity.BOTTOM;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,9 @@ public enum AnalogStickForScrolling {

public int touchPadYSensitity;

//摇杆鼠标灵敏度
public int stickMouseSensitivity;

//多点触控模式
public boolean enableMultiTouchScreen;

Expand Down Expand Up @@ -354,6 +357,9 @@ public enum AnalogStickForScrolling {

public boolean bindAllUsb;
public boolean mouseEmulation;
public boolean remapXToCtrl;
public boolean remapYToEsc;
public int keyboardHeightPercent;
public AnalogStickForScrolling analogStickForScrolling;
public boolean mouseNavButtons;
public boolean rememberMouseMode;
Expand Down Expand Up @@ -926,6 +932,9 @@ else if (audioConfig.equals("51")) {
config.enablePerfOverlayBottom = prefs.getBoolean("checkbox_enable_perf_overlay_bottom",DEFAULT_PERF_OVERLAY_BOTTOM);
config.bindAllUsb = prefs.getBoolean(BIND_ALL_USB_STRING, DEFAULT_BIND_ALL_USB);
config.mouseEmulation = prefs.getBoolean(MOUSE_EMULATION_STRING, DEFAULT_MOUSE_EMULATION);
config.remapXToCtrl = prefs.getBoolean("checkbox_remap_x_to_ctrl", false);
config.remapYToEsc = prefs.getBoolean("checkbox_remap_y_to_esc", false);
config.keyboardHeightPercent = prefs.getInt("seekbar_keyboard_height_percent", 80);
config.mouseNavButtons = prefs.getBoolean(MOUSE_NAV_BUTTONS_STRING, DEFAULT_MOUSE_NAV_BUTTONS);
config.rememberMouseMode = prefs.getBoolean(REMEMBER_MOUSE_MODE_PREF_STRING, DEFAULT_REMEMBER_MOUSE_MODE);
config.unlockFps = prefs.getBoolean(UNLOCK_FPS_STRING, DEFAULT_UNLOCK_FPS);
Expand Down Expand Up @@ -996,6 +1005,8 @@ else if (audioConfig.equals("51")) {

config.touchPadYSensitity=prefs.getInt("seekbar_touchpad_sensitivity_y_opacity",100);

config.stickMouseSensitivity = prefs.getInt("seekbar_stick_mouse_sensitivity", 100);

config.trackpadSensitivityX = prefs.getInt(SEEKBAR_TRACKPAD_SENSITIVITY_X, DEFAULT_TRACKPAD_SENSITIVITY_X);
config.trackpadSensitivityY = prefs.getInt(SEEKBAR_TRACKPAD_SENSITIVITY_Y, DEFAULT_TRACKPAD_SENSITIVITY_Y);
config.trackpadDragDropVibration = prefs.getBoolean(CHECKBOX_TRACKPAD_DRAG_DROP_VIBRATION, DEFAULT_TRACKPAD_DRAG_DROP_VIBRATION);
Expand Down
Loading