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
90 changes: 70 additions & 20 deletions app/src/main/java/com/limelight/Game.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.limelight.binding.input.evdev.EvdevListener;
import com.limelight.binding.input.touch.TouchContext;
import com.limelight.binding.input.touch.TrackpadContext;
import com.limelight.binding.input.touch.RightStickTouchContext;
import com.limelight.binding.input.virtual_controller.VirtualController;
import com.limelight.binding.input.virtual_controller.keyboard.KeyBoardController;
import com.limelight.binding.input.virtual_controller.keyboard.KeyBoardLayoutController;
Expand Down Expand Up @@ -3144,12 +3145,12 @@ private boolean handleTouchInput(MotionEvent event, TouchContext[] inputContextM
if (aTouchContextMap.getActionIndex() < pointerCount)
{
int aActionIndex = shouldDuplicateMovement ? 0 : aTouchContextMap.getActionIndex();
int historicalX = (int)event.getHistoricalX(aActionIndex, i);
int historicalY = (int)event.getHistoricalY(aActionIndex, i);
float historicalX = event.getHistoricalX(aActionIndex, i);
float historicalY = event.getHistoricalY(aActionIndex, i);
if (isTouchScreen) {
float[] normalizedCoords = getNormalizedCoordinates(streamContainer, historicalX, historicalY);
historicalX = (int)normalizedCoords[0];
historicalY = (int)normalizedCoords[1];
historicalX = normalizedCoords[0];
historicalY = normalizedCoords[1];
}

// Invert axis again since synthetic events are not inverted
Expand Down Expand Up @@ -3179,12 +3180,12 @@ private boolean handleTouchInput(MotionEvent event, TouchContext[] inputContextM
if (aTouchContextMap.getActionIndex() < pointerCount)
{
int aActionIndex = shouldDuplicateMovement ? 0 : aTouchContextMap.getActionIndex();
int currentX = (int)event.getX(aActionIndex);
int currentY = (int)event.getY(aActionIndex);
float currentX = event.getX(aActionIndex);
float currentY = event.getY(aActionIndex);
if (isTouchScreen) {
float[] normalizedCoords = getNormalizedCoordinates(streamContainer, currentX, currentY);
currentX = (int)normalizedCoords[0];
currentY = (int)normalizedCoords[1];
currentX = normalizedCoords[0];
currentY = normalizedCoords[1];
}

// Invert axis again since synthetic events are not inverted
Expand All @@ -3206,14 +3207,14 @@ private boolean handleTouchInput(MotionEvent event, TouchContext[] inputContextM
return true;
}

int eventX = (int)event.getX(actualActionIndex);
int eventY = (int)event.getY(actualActionIndex);
float eventX = event.getX(actualActionIndex);
float eventY = event.getY(actualActionIndex);

// Handle view scaling
if (isTouchScreen) {
float[] normalizedCoords = getNormalizedCoordinates(streamContainer, eventX, eventY);
eventX = (int)normalizedCoords[0];
eventY = (int)normalizedCoords[1];
eventX = normalizedCoords[0];
eventY = normalizedCoords[1];
}

switch (eventAction)
Expand Down Expand Up @@ -3259,12 +3260,12 @@ private boolean handleTouchInput(MotionEvent event, TouchContext[] inputContextM
}
if (actionIndex == 0 && pointerCount > 1 && !context.isCancelled()) {
// The original secondary touch now becomes primary
int pointer1X = (int)event.getX(1);
int pointer1Y = (int)event.getY(1);
float pointer1X = event.getX(1);
float pointer1Y = event.getY(1);
if (isTouchScreen) {
float[] normalizedCoords = getNormalizedCoordinates(streamContainer, pointer1X, pointer1Y);
pointer1X = (int)normalizedCoords[0];
pointer1Y = (int)normalizedCoords[1];
pointer1X = normalizedCoords[0];
pointer1Y = normalizedCoords[1];
}
context.touchDownEvent(
pointer1X,
Expand Down Expand Up @@ -4151,27 +4152,37 @@ private void toggleMouseLocalCursor(){
}

private void applyMouseMode(int mode) {
if (prefConfig != null) {
prefConfig.mouseMode = mode;
}
switch (mode) {
case 0: // Multi-touch
prefConfig.enableMultiTouchScreen = true;
prefConfig.touchscreenTrackpad = false;
break;
case 1: // Normal mouse
case 5: // Normal mouse with swapped buttons
prefConfig.enableMultiTouchScreen = false;
prefConfig.touchscreenTrackpad = false;
break;
case 2: // Trackpad (natural)
case 3: // Trackpad (gaming)
prefConfig.enableMultiTouchScreen = false;
prefConfig.touchscreenTrackpad = true;
break;
case 4: // Touch mouse disabled
break;
case 5: // Normal mouse with swapped buttons
prefConfig.enableMultiTouchScreen = false;
prefConfig.touchscreenTrackpad = false;
break;
case 6: // 右搖桿向量觸控板
prefConfig.enableMultiTouchScreen = false;
prefConfig.touchscreenTrackpad = true;
break;
default:
break;
}

// 取得螢幕密度,用於標準化滑動距離
float density = getResources().getDisplayMetrics().density;

//Initialize touch contexts
for (int i = 0; i < touchContextMap.length; i++) {
if (touchContextMap[i] != null) touchContextMap[i].cancelTouch();
Expand All @@ -4182,6 +4193,16 @@ private void applyMouseMode(int mode) {
touchContextMap[i] = new AbsoluteTouchContext(conn, i, streamContainer, mode == 5);
} else if (mode == 3) {
touchContextMap[i] = new RelativeTouchContext(conn, i, REFERENCE_HORIZ_RES, REFERENCE_VERT_RES, streamContainer, prefConfig);
} else if (mode == 6) {
// 模式 6:初始化右搖桿向量觸控板
touchContextMap[i] = new RightStickTouchContext(
controllerHandler,
i,
prefConfig.rightStickVectorSensitivityX, // 傳入專屬靈敏度設定
prefConfig.rightStickVectorSensitivityY,
prefConfig.rightStickVectorAntiDeadzone,
density // 傳入螢幕密度
);
} else {
touchContextMap[i] = new TrackpadContext(conn, i);
}
Expand Down Expand Up @@ -4346,4 +4367,33 @@ private SurfaceView findFirstSurfaceViewFrom(View v) {
return null;
}

// 讓 GameMenu 可以詢問當前實際生效的模式 (記憶體狀態)
public boolean isRightStickMode() {
// 檢查 prefConfig 是否存在且 mouseMode 是否為 6 (右搖桿模式)
return prefConfig != null && prefConfig.mouseMode == 6;
}

// 只更新相關數值,不覆蓋整個設定物件
public void updateRightStickConfig() {
if (this.prefConfig == null) return;

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

// 1. 直接讀取拉桿的數值
int sensX = prefs.getInt("seekbar_right_stick_vector_sensitivity_x", 30);
int sensY = prefs.getInt("seekbar_right_stick_vector_sensitivity_y", 30);
int antiDeadzone = prefs.getInt("seekbar_right_stick_vector_anti_deadzone", 30);

// 2. 原地更新現有的 prefConfig 物件
// 這樣做不會影響到 mouseMode 或其他正在運行中的暫時性狀態
this.prefConfig.rightStickVectorSensitivityX = sensX / 10.0f;
this.prefConfig.rightStickVectorSensitivityY = sensY / 10.0f;
this.prefConfig.rightStickVectorAntiDeadzone = antiDeadzone / 100.0f;

// 3. 重新套用模式以刷新計算邏輯
if (isRightStickMode()) {
applyMouseMode(6);
}
}

}
106 changes: 105 additions & 1 deletion app/src/main/java/com/limelight/GameMenu.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

import com.limelight.binding.input.GameInputDevice;
Expand Down Expand Up @@ -242,12 +246,112 @@ private void showSpecialKeysMenu() {
showMenuDialog(getString(R.string.game_menu_send_keys), options.toArray(new MenuOption[options.size()]));
}

// [New] 使用 XML Layout 的整合設定對話框
private void showRightStickConfigDialog() {
int themeResId = game.getApplicationInfo().theme;
Context themedContext = new ContextThemeWrapper(dialogScreenContext, themeResId);
AlertDialog.Builder builder = new AlertDialog.Builder(themedContext);

builder.setTitle(getString(R.string.mouse_mode_right_stick_vector));

// 載入 XML Layout
LayoutInflater inflater = LayoutInflater.from(themedContext);
View view = inflater.inflate(R.layout.dialog_right_stick_config, null);
builder.setView(view);

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(game);
SharedPreferences.Editor editor = prefs.edit();

// --- 定義設定參數陣列 ---
int[] seekBarIds = {
R.id.seekbar_sens_x,
R.id.seekbar_sens_y,
R.id.seekbar_deadzone
};
int[] textIds = {
R.id.text_sens_x,
R.id.text_sens_y,
R.id.text_deadzone
};
String[] keys = {
"seekbar_right_stick_vector_sensitivity_x",
"seekbar_right_stick_vector_sensitivity_y",
"seekbar_right_stick_vector_anti_deadzone"
};
// 預設值
int[] defs = {30, 30, 30};
int[] mins = {10, 10, 0};
int[] maxs = {50, 50, 50};
float[] divisors = {10.0f, 10.0f, 1.0f};
String[] suffixes = {"", "", "%"};

// --- 迴圈初始化 ---
for (int i = 0; i < keys.length; i++) {
setupSeekBarLogic(view, prefs,
seekBarIds[i], textIds[i],
keys[i], defs[i], mins[i], maxs[i], divisors[i], suffixes[i]);
}

// --- 儲存邏輯 ---
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
for (int i = 0; i < keys.length; i++) {
SeekBar sb = view.findViewById(seekBarIds[i]);
// 加回 min 值
editor.putInt(keys[i], sb.getProgress() + mins[i]);
}
editor.apply();
game.updateRightStickConfig();
});

builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
}

// [New Helper] 輔助方法:設定單個 SeekBar 與 TextView 的連動
private void setupSeekBarLogic(View parent, SharedPreferences prefs, int seekBarId, int textId,
String key, int def, int min, int max, float divisor, String suffix) {
SeekBar seekBar = parent.findViewById(seekBarId);
TextView textView = parent.findViewById(textId);

int range = max - min;
seekBar.setMax(range);

int currentVal = prefs.getInt(key, def);
seekBar.setProgress(currentVal - min);

Runnable updateText = () -> {
int val = seekBar.getProgress() + min;
if (divisor == 1.0f) {
textView.setText(val + suffix);
} else {
textView.setText(String.format("%.1f%s", val / divisor, suffix));
}
};

// 初始化文字
updateText.run();

seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
updateText.run();
}
@Override public void onStartTrackingTouch(SeekBar seekBar) {}
@Override public void onStopTrackingTouch(SeekBar seekBar) {}
});
}

private void showAdvancedMenu(GameInputDevice device) {
List<MenuOption> options = new ArrayList<>();
if (game.allowChangeMouseMode) {
options.add(new MenuOption(getString(R.string.game_menu_select_mouse_mode), true, () -> game.selectMouseMode(dialogScreenContext)));
}


// 如果是右搖桿模式,顯示設定選項
if (game.isRightStickMode()) {
options.add(new MenuOption(getString(R.string.mouse_mode_right_stick_vector), true, this::showRightStickConfigDialog));
}

options.add(new MenuOption(getString(R.string.game_menu_toggle_hud), true, game::toggleHUD));
options.add(new MenuOption(getString(R.string.game_menu_toggle_floating_button), true, game::toggleFloatingButtonVisibility));
options.add(new MenuOption(getString(R.string.game_menu_toggle_keyboard_model), true, game::toggleKeyboardController));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
private final Activity activityContext;
private final double stickDeadzone;
private final InputDeviceContext defaultContext = new InputDeviceContext();
// 右搖桿向量觸控板專用 Context
private final GenericControllerContext touchAimContext = new GenericControllerContext();
private final GameGestures gestures;
private final InputManager inputManager;
private final Vibrator deviceVibrator;
Expand Down Expand Up @@ -201,6 +203,10 @@ public ControllerHandler(Activity activityContext, NvConnection conn, GameGestur
// consume these. Instead, let's ignore them since that's probably the
// most likely case.
defaultContext.ignoreBack = true;
// 初始化右搖桿向量觸控板專用 Context,設為 Player 1 (ID 0)
touchAimContext.controllerNumber = (short) 0;
touchAimContext.assignedControllerNumber = true;
touchAimContext.external = false;

// Get the initially attached set of gamepads. As each gamepad receives
// its initial InputEvent, we will move these from this set onto the
Expand All @@ -212,6 +218,17 @@ public ControllerHandler(Activity activityContext, NvConnection conn, GameGestur
inputManager.registerInputDeviceListener(this, null);
}

// 供 RightStickTouchContext 呼叫的介面方法
public void reportTouchAimState(short rightStickX, short rightStickY, boolean r3Active) {
touchAimContext.rightStickX = rightStickX;
touchAimContext.rightStickY = rightStickY;
// 設定 R3 按鍵旗標
touchAimContext.inputMap = r3Active ? ControllerPacket.RS_CLK_FLAG : 0;

// 觸發訊號發送流程
sendControllerInputPacket(touchAimContext);
}

private static InputDevice.MotionRange getMotionRangeForJoystickAxis(InputDevice dev, int axis) {
InputDevice.MotionRange range;

Expand Down Expand Up @@ -1266,6 +1283,16 @@ private void sendControllerInputPacket(GenericControllerContext originalContext)
rightStickY |= maxByMagnitude(rightStickY, defaultContext.rightStickY);
}

// 將右搖桿向量觸控板的訊號「疊加」到最終輸出
if (touchAimContext.controllerNumber == controllerNumber) {
// 使用 maxByMagnitude 取絕對值較大者,確保虛擬搖桿與觸控瞄準誰有動就聽誰的
rightStickX = maxByMagnitude(rightStickX, touchAimContext.rightStickX);
rightStickY = maxByMagnitude(rightStickY, touchAimContext.rightStickY);

// 合併按鍵 (R3)
inputMap |= touchAimContext.inputMap;
}

if (originalContext.mouseEmulationActive) {
int changedMask = inputMap ^ originalContext.mouseEmulationLastInputMap;

Expand Down
Loading