feat: mobile, virtual mouse (#12911)

* feat: mobile, virtual mouse

Signed-off-by: fufesou <linlong1266@gmail.com>

* feat: mobile, virtual mouse, mouse mode

Signed-off-by: fufesou <linlong1266@gmail.com>

* refact: mobile, virtual mouse, mouse mode

Signed-off-by: fufesou <linlong1266@gmail.com>

* feat: mobile, virtual mouse mode

Signed-off-by: fufesou <linlong1266@gmail.com>

* feat: mobile virtual mouse, options

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
This commit is contained in:
fufesou
2025-10-08 20:23:55 -04:00
committed by GitHub
parent 02f455b0cc
commit 0f3a03aab7
56 changed files with 2714 additions and 49 deletions

View File

@@ -114,6 +114,7 @@ class FfiModel with ChangeNotifier {
bool? _secure;
bool? _direct;
bool _touchMode = false;
late VirtualMouseMode virtualMouseMode;
Timer? _timer;
var _reconnects = 1;
bool _viewOnly = false;
@@ -166,6 +167,7 @@ class FfiModel with ChangeNotifier {
clear();
sessionId = parent.target!.sessionId;
cachedPeerData.permissions = _permissions;
virtualMouseMode = VirtualMouseMode(this);
}
Rect? globalDisplaysRect() => _getDisplaysRect(_pi.displays, true);
@@ -1109,6 +1111,9 @@ class FfiModel with ChangeNotifier {
sessionId: sessionId, arg: kOptionTouchMode) !=
'';
}
if (isMobile) {
virtualMouseMode.loadOptions();
}
if (connType == ConnType.fileTransfer) {
parent.target?.fileModel.onReady();
} else if (connType == ConnType.terminal) {
@@ -1508,6 +1513,72 @@ class FfiModel with ChangeNotifier {
}
}
class VirtualMouseMode with ChangeNotifier {
bool _showVirtualMouse = false;
double _virtualMouseScale = 1.0;
bool _showVirtualJoystick = false;
bool get showVirtualMouse => _showVirtualMouse;
double get virtualMouseScale => _virtualMouseScale;
bool get showVirtualJoystick => _showVirtualJoystick;
FfiModel ffiModel;
VirtualMouseMode(this.ffiModel);
bool _shouldShow() => !ffiModel.isPeerAndroid;
setShowVirtualMouse(bool b) {
if (b == _showVirtualMouse) return;
if (_shouldShow()) {
_showVirtualMouse = b;
notifyListeners();
}
}
setVirtualMouseScale(double s) {
if (s <= 0) return;
if (s == _virtualMouseScale) return;
_virtualMouseScale = s;
bind.mainSetLocalOption(key: kOptionVirtualMouseScale, value: s.toString());
notifyListeners();
}
setShowVirtualJoystick(bool b) {
if (b == _showVirtualJoystick) return;
if (_shouldShow()) {
_showVirtualJoystick = b;
notifyListeners();
}
}
void loadOptions() {
_showVirtualMouse =
bind.mainGetLocalOption(key: kOptionShowVirtualMouse) == 'Y';
_virtualMouseScale = double.tryParse(
bind.mainGetLocalOption(key: kOptionVirtualMouseScale)) ??
1.0;
_showVirtualJoystick =
bind.mainGetLocalOption(key: kOptionShowVirtualJoystick) == 'Y';
notifyListeners();
}
Future<void> toggleVirtualMouse() async {
await bind.mainSetLocalOption(
key: kOptionShowVirtualMouse, value: showVirtualMouse ? 'N' : 'Y');
setShowVirtualMouse(
bind.mainGetLocalOption(key: kOptionShowVirtualMouse) == 'Y');
}
Future<void> toggleVirtualJoystick() async {
await bind.mainSetLocalOption(
key: kOptionShowVirtualJoystick,
value: showVirtualJoystick ? 'N' : 'Y');
setShowVirtualJoystick(
bind.mainGetLocalOption(key: kOptionShowVirtualJoystick) == 'Y');
}
}
class ImageModel with ChangeNotifier {
ui.Image? _image;
@@ -2289,9 +2360,25 @@ class CursorModel with ChangeNotifier {
Rect? get keyHelpToolsRectToAdjustCanvas =>
_lastKeyboardIsVisible ? _keyHelpToolsRect : null;
keyHelpToolsVisibilityChanged(Rect? r, bool keyboardIsVisible) {
_keyHelpToolsRect = r;
if (r == null) {
// The blocked rect is used to block the pointer/touch events in the remote page.
final List<Rect> _blockedRects = [];
// Used in shouldBlock().
// _blockEvents is a flag to block pointer/touch events on the remote image.
// It is set to true to prevent accidental touch events in the following scenarios:
// 1. In floating mouse mode, when the scroll circle is shown.
// 2. In floating mouse widgets mode, when the left/right buttons are moving.
// 3. In floating mouse widgets mode, when using the virtual joystick.
// When _blockEvents is true, all pointer/touch events are blocked regardless of the contents of _blockedRects.
// _blockedRects contains specific rectangular regions where events are blocked; these are checked when _blockEvents is false.
// In summary: _blockEvents acts as a global block, while _blockedRects provides fine-grained blocking.
bool _blockEvents = false;
List<Rect> get blockedRects => List.unmodifiable(_blockedRects);
set blockEvents(bool v) => _blockEvents = v;
keyHelpToolsVisibilityChanged(Rect? rect, bool keyboardIsVisible) {
_keyHelpToolsRect = rect;
if (rect == null) {
_lastIsBlocked = false;
} else {
// Block the touch event is safe here.
@@ -2306,6 +2393,14 @@ class CursorModel with ChangeNotifier {
_lastKeyboardIsVisible = keyboardIsVisible;
}
addBlockedRect(Rect rect) {
_blockedRects.add(rect);
}
removeBlockedRect(Rect rect) {
_blockedRects.remove(rect);
}
get lastIsBlocked => _lastIsBlocked;
ui.Image? get image => _image;
@@ -2372,13 +2467,22 @@ class CursorModel with ChangeNotifier {
// mobile Soft keyboard, block touch event from the KeyHelpTools
shouldBlock(double x, double y) {
if (_blockEvents) {
return true;
}
final offset = Offset(x, y);
for (final rect in _blockedRects) {
if (isPointInRect(offset, rect)) {
return true;
}
}
// For help tools rectangle, only block touch event when in touch mode.
if (!(parent.target?.ffiModel.touchMode ?? false)) {
return false;
}
if (_keyHelpToolsRect == null) {
return false;
}
if (isPointInRect(Offset(x, y), _keyHelpToolsRect!)) {
if (_keyHelpToolsRect != null &&
isPointInRect(offset, _keyHelpToolsRect!)) {
return true;
}
return false;
@@ -2398,6 +2502,10 @@ class CursorModel with ChangeNotifier {
return true;
}
Future<void> syncCursorPosition() async {
await parent.target?.inputModel.moveMouse(_x, _y);
}
bool isInRemoteRect(Offset offset) {
return getRemotePosInRect(offset) != null;
}