Skip to content

Commit b2e0c67

Browse files
committed
新增远程鼠标显示自动切换功能;
修复了ctrl shift alt c在本地鼠标模式下无法正确切换的问题。
1 parent cd2467a commit b2e0c67

7 files changed

Lines changed: 129 additions & 17 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@
77

88
* **修复了开启无障碍服务后无法使用音量键的问题**
99

10+
* **修复了Ctrl+Shift+Alt+C在本地鼠标模式下无法正确切换模式的问题**
11+
1012
* **自动获取无障碍权限**
1113
> *需要通过ADB授权android.permission.WRITE_SECURE_SETTINGS*
1214
15+
* **自动切换远程电脑的光标显示**
16+
> *在本地鼠标模式下,自动根据状态发送电脑光标显示开关请求*
17+
1318
* **安卓本地鼠标特殊滚轮的识别**
1419
> *在使用安卓本地鼠标光标时,部分设备无法正确获取滚轮事件,如华为安卓平板,滚动滚动时会产生触屏效果造成大范围滑动*
1520
30 Bytes
Binary file not shown.
33 Bytes
Binary file not shown.

app/src/main/java/com/limelight/Game.java

Lines changed: 111 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,6 @@
9292
import android.widget.TextView;
9393
import android.widget.Toast;
9494

95-
import androidx.annotation.RequiresApi;
96-
9795
import java.io.ByteArrayInputStream;
9896
import java.lang.reflect.InvocationTargetException;
9997
import java.lang.reflect.Method;
@@ -366,6 +364,20 @@ private void stopAndUnbindUsbDriverService() {
366364
private boolean detectScrolling = false;
367365
private boolean detectMouseMiddle = false;
368366
private boolean detectMouseMiddleDown = false;
367+
private static final String CURSOR_PREFS = "CursorPrefs";
368+
private static final String KEY_CURSOR_RESTORE = "cursor_switch_state";
369+
370+
private void saveCursorState(int state) {
371+
getSharedPreferences(CURSOR_PREFS, MODE_PRIVATE)
372+
.edit()
373+
.putInt(KEY_CURSOR_RESTORE, state)
374+
.commit(); // 使用 commit 确保即使进程随后被杀也能保存
375+
}
376+
377+
private int getCursorState() {
378+
return getSharedPreferences(CURSOR_PREFS, MODE_PRIVATE)
379+
.getInt(KEY_CURSOR_RESTORE, 0);
380+
}
369381

370382
@Override
371383
protected void onCreate(Bundle savedInstanceState) {
@@ -1106,6 +1118,9 @@ public void onStreamViewReady(StreamView streamView) {
11061118
protected void onResume() {
11071119
super.onResume();
11081120

1121+
// 回到游戏时,重置标志位,允许下次暂停时再次恢复
1122+
isCursorRestored = false;
1123+
11091124
// 当 Activity 回到前台时,通知服务开始拦截键盘事件。
11101125
KeyboardAccessibilityService.setIntercepting(true);
11111126

@@ -1805,8 +1820,28 @@ protected void onDestroy() {
18051820
}
18061821
}
18071822

1823+
private boolean isCursorRestored = false;
1824+
1825+
/**
1826+
* 封装恢复逻辑:确保只执行一次
1827+
*/
1828+
public void restoreCursorIfNecessary() {
1829+
if (!isCursorRestored) {
1830+
if (prefConfig.cursorAutoShow && cursorVisible && conn != null && connected) {
1831+
switchRemoteCursorShow();
1832+
saveCursorState(1); // 标记为已恢复/待处理
1833+
}
1834+
isCursorRestored = true; // 锁定,防止重复执行
1835+
}
1836+
}
1837+
18081838
@Override
18091839
protected void onPause() {
1840+
// 本地光标下,结束串流自动恢复光标显示
1841+
// 只有在非销毁(按 Home 键或切应用)的情况下尝试自动恢复
1842+
// 如果是通过按钮退出的,标志位会提前变为 true,这里就不会再跑一次
1843+
restoreCursorIfNecessary();
1844+
18101845
// 当 Activity 进入后台时,必须停止拦截,否则会影响手机的正常使用!
18111846
KeyboardAccessibilityService.setIntercepting(false);
18121847

@@ -2003,6 +2038,40 @@ private void setInputGrabState(boolean grab) {
20032038

20042039
private final Runnable toggleGrab = () -> setInputGrabState(!grabbedInput);
20052040

2041+
private void switchRemoteCursorShow() {
2042+
final short[] keys = {
2043+
KeyboardTranslator.VK_LCONTROL,
2044+
KeyboardTranslator.VK_MENU,
2045+
KeyboardTranslator.VK_LSHIFT,
2046+
KeyboardTranslator.VK_N
2047+
};
2048+
2049+
final byte[] modifier = {0};
2050+
2051+
for (short key : keys) {
2052+
conn.sendKeyboardInput(key, KeyboardPacket.KEY_DOWN, modifier[0], (byte) 0);
2053+
modifier[0] |= getModBit(key);
2054+
}
2055+
2056+
handler.postDelayed(() -> {
2057+
for (int i = keys.length - 1; i >= 0; i--) {
2058+
short key = keys[i];
2059+
modifier[0] &= ~getModBit(key);
2060+
conn.sendKeyboardInput(key, KeyboardPacket.KEY_UP, modifier[0], (byte) 0);
2061+
}
2062+
}, 25L);
2063+
}
2064+
2065+
private byte getModBit(short key) {
2066+
switch (key) {
2067+
case KeyboardTranslator.VK_LSHIFT: return KeyboardPacket.MODIFIER_SHIFT;
2068+
case KeyboardTranslator.VK_LCONTROL: return KeyboardPacket.MODIFIER_CTRL;
2069+
case KeyboardTranslator.VK_LWIN: return KeyboardPacket.MODIFIER_META;
2070+
case KeyboardTranslator.VK_MENU: return KeyboardPacket.MODIFIER_ALT;
2071+
default: return 0;
2072+
}
2073+
}
2074+
20062075
// Returns true if the key stroke was consumed
20072076
private boolean handleSpecialKeys(int androidKeyCode, boolean down) {
20082077
int modifierMask = 0;
@@ -2061,10 +2130,17 @@ private boolean handleSpecialKeys(int androidKeyCode, boolean down) {
20612130
grabbedInput = true;
20622131
}
20632132
cursorVisible = !cursorVisible;
2064-
if (cursorVisible) {
2065-
inputCaptureProvider.showCursor();
2066-
} else {
2067-
inputCaptureProvider.hideCursor();
2133+
// 切换本地光标时自动切换远程鼠标可见性
2134+
if (prefConfig.cursorAutoShow && conn != null && connected) {
2135+
switchRemoteCursorShow();
2136+
}
2137+
if (inputCaptureProvider.isCapturingEnabled()){
2138+
Toast.makeText(this, "切换为本地鼠标模式", Toast.LENGTH_SHORT).show();
2139+
inputCaptureProvider.disableCapture();
2140+
}
2141+
else {
2142+
Toast.makeText(this, "切换为远程鼠标模式", Toast.LENGTH_SHORT).show();
2143+
inputCaptureProvider.enableCapture();
20682144
}
20692145
break;
20702146

@@ -2582,11 +2658,17 @@ public void toggleKeyboard() {
25822658
/**
25832659
* 启用或禁用安卓本地鼠标指针
25842660
*/
2585-
public void enableNativeMousePointer(boolean enable) {
2661+
public void enableNativeMousePointer(boolean enable, boolean autoSwitch) {
25862662
LimeLog.info("Setting native mouse pointer: " + enable);
25872663

25882664
prefConfig.enableNativeMousePointer = enable;
25892665

2666+
if (autoSwitch && cursorVisible != enable) {
2667+
if (prefConfig.cursorAutoShow && conn != null && connected) {
2668+
switchRemoteCursorShow();
2669+
}
2670+
}
2671+
25902672
if (enable) {
25912673
// 启用本地鼠标指针:释放鼠标捕获但保持键盘捕获
25922674
inputCaptureProvider.disableCapture();
@@ -3149,7 +3231,7 @@ else if (eventSource == InputDevice.SOURCE_MOUSE_RELATIVE &&
31493231
int deviceSources = event.getDevice() != null ? event.getDevice().getSources() : 0;
31503232

31513233
// 本地鼠标指针模式的特殊处理
3152-
if (prefConfig.enableNativeMousePointer && (eventSource & InputDevice.SOURCE_CLASS_POINTER) != 0) {
3234+
if (cursorVisible && (eventSource & InputDevice.SOURCE_CLASS_POINTER) != 0) {
31533235
// 检查是否为真正的鼠标设备(而不是触摸屏)
31543236
boolean isActualMouse = (eventSource == InputDevice.SOURCE_MOUSE) ||
31553237
(eventSource == InputDevice.SOURCE_MOUSE_RELATIVE) ||
@@ -3913,9 +3995,25 @@ public void connectionStarted() {
39133995
Handler h = new Handler();
39143996
h.postDelayed(() -> {
39153997
// 根据配置决定是否启用原生鼠标指针
3998+
int state = getCursorState();
39163999
if (prefConfig.enableNativeMousePointer) {
3917-
enableNativeMousePointer(true);
4000+
// 使用本地鼠标时,连接开始自动关闭远程光标显示
4001+
if (state == 1) {
4002+
// 状态为 1,说明上次退出或暂停时重新打开了远程光标显示,现在需要关闭
4003+
if (prefConfig.cursorAutoShow) {
4004+
switchRemoteCursorShow();
4005+
saveCursorState(0); // 恢复后重置状态为 0
4006+
}
4007+
}
4008+
enableNativeMousePointer(true, false);
39184009
} else {
4010+
if (state == 0) {
4011+
// 状态为 0,说明上次意外退出,已经是关闭状态,现在因为设置改变需要开启
4012+
if (prefConfig.cursorAutoShow) {
4013+
switchRemoteCursorShow();
4014+
}
4015+
saveCursorState(1);
4016+
}
39194017
setInputGrabState(true);
39204018
}
39214019
}, 500);
@@ -4346,7 +4444,7 @@ private void initializeLocalCursorRenderers(int width, int height) {
43464444
// 3. 必须没开启原生鼠标 (防止冲突)
43474445
boolean shouldShow = prefConfig.enableLocalCursorRendering
43484446
&& prefConfig.touchscreenTrackpad
4349-
&& !prefConfig.enableNativeMousePointer;
4447+
&& !cursorVisible;
43504448

43514449
relativeContext.setEnableLocalCursorRendering(shouldShow);
43524450
}
@@ -4367,7 +4465,7 @@ private void destroyLocalCursorRenderers() {
43674465
}
43684466

43694467
public void refreshLocalCursorState(boolean enabled) {
4370-
boolean shouldRender = enabled && !prefConfig.enableNativeMousePointer;
4468+
boolean shouldRender = enabled && !cursorVisible;
43714469

43724470
for (TouchContext context : relativeTouchContextMap) {
43734471
if (context instanceof RelativeTouchContext) {
@@ -4546,7 +4644,7 @@ private void startCursorService(String hostIp) {
45464644
if (targetBitmap != null) {
45474645
final android.graphics.Bitmap finalBmp = targetBitmap;
45484646
runOnUiThread(() -> {
4549-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && prefConfig.enableNativeMousePointer) {
4647+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && cursorVisible) {
45504648
// 方案B:当启用了原生指针且API版本符合时,使用 PointerIcon
45514649
PointerIcon pointerIcon = PointerIcon.create(finalBmp, hotX, hotY);
45524650
streamView.setPointerIcon(pointerIcon);
@@ -4575,7 +4673,7 @@ private void startCursorService(String hostIp) {
45754673
lastReceiveTime = System.currentTimeMillis(); // 重置计时,避免疯狂触发
45764674

45774675
runOnUiThread(() -> {
4578-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && prefConfig.enableNativeMousePointer) {
4676+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && cursorVisible) {
45794677
// 恢复为默认箭头
45804678
streamView.setPointerIcon(PointerIcon.getSystemIcon(Game.this, PointerIcon.TYPE_ARROW));
45814679
} else {

app/src/main/java/com/limelight/GameMenu.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ private void showTouchModeMenu() {
309309
() -> {
310310
game.prefConfig.enableEnhancedTouch = true;
311311
game.prefConfig.enableNativeMousePointer = false;
312-
game.enableNativeMousePointer(false); // 关闭本地鼠标模式
312+
game.enableNativeMousePointer(false, true); // 关闭本地鼠标模式
313313
game.setTouchMode(false);
314314
updateEnhancedTouchSetting(true);
315315
updateTouchModeSetting(false);
@@ -325,7 +325,7 @@ private void showTouchModeMenu() {
325325
() -> {
326326
game.prefConfig.enableEnhancedTouch = false;
327327
game.prefConfig.enableNativeMousePointer = false;
328-
game.enableNativeMousePointer(false); // 关闭本地鼠标模式
328+
game.enableNativeMousePointer(false, true); // 关闭本地鼠标模式
329329
game.setTouchMode(false);
330330
updateEnhancedTouchSetting(false);
331331
updateTouchModeSetting(false);
@@ -340,7 +340,7 @@ private void showTouchModeMenu() {
340340
isTouchscreenTrackpad && !isNativeMousePointer,
341341
() -> {
342342
game.prefConfig.enableNativeMousePointer = false;
343-
game.enableNativeMousePointer(false); // 关闭本地鼠标模式
343+
game.enableNativeMousePointer(false, true); // 关闭本地鼠标模式
344344
game.setTouchMode(true);
345345
updateTouchModeSetting(true);
346346
Toast.makeText(game, getString(R.string.toast_touch_mode_trackpad_on), Toast.LENGTH_SHORT).show();
@@ -392,7 +392,7 @@ private void showTouchModeMenu() {
392392
game.prefConfig.enableNativeMousePointer = true;
393393
game.prefConfig.enableEnhancedTouch = false;
394394
game.setTouchMode(false);
395-
game.enableNativeMousePointer(true);
395+
game.enableNativeMousePointer(true, true);
396396
updateTouchModeSetting(false);
397397
Toast.makeText(game, getString(R.string.toast_touch_mode_native_mouse_on), Toast.LENGTH_SHORT).show();
398398
},
@@ -1033,6 +1033,7 @@ private void setupQuickButtons(View customView, AlertDialog dialog) {
10331033
});
10341034

10351035
setupButtonWithAnimation(customView.findViewById(R.id.btnQuit), scaleDown, scaleUp, v -> {
1036+
game.restoreCursorIfNecessary();
10361037
if (game.prefConfig.swapQuitAndDisconnect) {
10371038
game.disconnect();
10381039
}

app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,8 @@ public enum PerfOverlayPosition {
239239
public boolean fixMouseMiddle;
240240
//修复本地鼠标滚轮识别
241241
public boolean fixMouseWheel;
242+
//自动切换远程鼠标可见性
243+
public boolean cursorAutoShow;
242244
public static final int FRAME_PACING_MIN_LATENCY = 0;
243245
public static final int FRAME_PACING_BALANCED = 1;
244246
public static final int FRAME_PACING_CAP_FPS = 2;
@@ -795,6 +797,7 @@ else if (audioConfig.equals("51")) {
795797
config.enableCustomKeyMap=prefs.getBoolean("checkbox_special_key_map",false);
796798
config.fixMouseMiddle=prefs.getBoolean("checkbox_mouse_middle",false);
797799
config.fixMouseWheel=prefs.getBoolean("checkbox_mouse_wheel",false);
800+
config.cursorAutoShow=prefs.getBoolean("checkbox_cursor_auto_show",false);
798801
config.enableSops = prefs.getBoolean(SOPS_PREF_STRING, DEFAULT_SOPS);
799802
config.stretchVideo = prefs.getBoolean(STRETCH_PREF_STRING, DEFAULT_STRETCH);
800803
config.playHostAudio = prefs.getBoolean(HOST_AUDIO_PREF_STRING, DEFAULT_HOST_AUDIO);

app/src/main/res/xml/preferences.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,11 @@
321321
android:entries="@array/native_mouse_mode_preset_names"
322322
android:entryValues="@array/native_mouse_mode_preset_values"
323323
android:defaultValue="classic" />
324+
<CheckBoxPreference
325+
android:key="checkbox_cursor_auto_show"
326+
android:title="自动切换远程鼠标可见性"
327+
android:summary="使用本地鼠标指针时,自动切换远程鼠标可见性,避免拖影"
328+
android:defaultValue="true" />
324329
<CheckBoxPreference
325330
android:key="checkbox_absolute_mouse_mode"
326331
android:title="@string/title_checkbox_absolute_mouse_mode"

0 commit comments

Comments
 (0)