mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-02 07:41:29 +03:00
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:
@@ -766,6 +766,11 @@ class InputModel {
|
||||
command: command);
|
||||
}
|
||||
|
||||
static Map<String, dynamic> getMouseEventMove() => {
|
||||
'type': _kMouseEventMove,
|
||||
'buttons': 0,
|
||||
};
|
||||
|
||||
Map<String, dynamic> _getMouseEvent(PointerEvent evt, String type) {
|
||||
final Map<String, dynamic> out = {};
|
||||
|
||||
@@ -1222,16 +1227,17 @@ class InputModel {
|
||||
return false;
|
||||
}
|
||||
|
||||
void handleMouse(
|
||||
Map<String, dynamic>? processEventToPeer(
|
||||
Map<String, dynamic> evt,
|
||||
Offset offset, {
|
||||
bool onExit = false,
|
||||
bool moveCanvas = true,
|
||||
}) {
|
||||
if (isViewCamera) return;
|
||||
if (isViewCamera) return null;
|
||||
double x = offset.dx;
|
||||
double y = max(0.0, offset.dy);
|
||||
if (_checkPeerControlProtected(x, y)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
var type = kMouseEventTypeDefault;
|
||||
@@ -1248,7 +1254,7 @@ class InputModel {
|
||||
isMove = true;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
evt['type'] = type;
|
||||
|
||||
@@ -1266,9 +1272,10 @@ class InputModel {
|
||||
type,
|
||||
onExit: onExit,
|
||||
buttons: evt['buttons'],
|
||||
moveCanvas: moveCanvas,
|
||||
);
|
||||
if (pos == null) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
if (type != '') {
|
||||
evt['x'] = '0';
|
||||
@@ -1286,7 +1293,22 @@ class InputModel {
|
||||
kForwardMouseButton: 'forward'
|
||||
};
|
||||
evt['buttons'] = mapButtons[evt['buttons']] ?? '';
|
||||
bind.sessionSendMouse(sessionId: sessionId, msg: json.encode(modify(evt)));
|
||||
return evt;
|
||||
}
|
||||
|
||||
Map<String, dynamic>? handleMouse(
|
||||
Map<String, dynamic> evt,
|
||||
Offset offset, {
|
||||
bool onExit = false,
|
||||
bool moveCanvas = true,
|
||||
}) {
|
||||
final evtToPeer =
|
||||
processEventToPeer(evt, offset, onExit: onExit, moveCanvas: moveCanvas);
|
||||
if (evtToPeer != null) {
|
||||
bind.sessionSendMouse(
|
||||
sessionId: sessionId, msg: json.encode(modify(evtToPeer)));
|
||||
}
|
||||
return evtToPeer;
|
||||
}
|
||||
|
||||
Point? handlePointerDevicePos(
|
||||
@@ -1297,6 +1319,7 @@ class InputModel {
|
||||
String evtType, {
|
||||
bool onExit = false,
|
||||
int buttons = kPrimaryMouseButton,
|
||||
bool moveCanvas = true,
|
||||
}) {
|
||||
final ffiModel = parent.target!.ffiModel;
|
||||
CanvasCoords canvas =
|
||||
@@ -1325,7 +1348,7 @@ class InputModel {
|
||||
|
||||
y -= CanvasModel.topToEdge;
|
||||
x -= CanvasModel.leftToEdge;
|
||||
if (isMove) {
|
||||
if (isMove && moveCanvas) {
|
||||
parent.target!.canvasModel.moveDesktopMouse(x, y);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user