* feat: android volume and power actions Signed-off-by: fufesou <linlong1266@gmail.com> * Add translations and refact action menus Signed-off-by: fufesou <linlong1266@gmail.com> * Remove divider Signed-off-by: fufesou <linlong1266@gmail.com> * fix: recover deleted translations Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com>
		
			
				
	
	
		
			1179 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			1179 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'dart:async';
 | |
| import 'dart:convert';
 | |
| import 'dart:io';
 | |
| import 'dart:math';
 | |
| import 'dart:ui' as ui;
 | |
| 
 | |
| import 'package:desktop_multi_window/desktop_multi_window.dart';
 | |
| import 'package:flutter/gestures.dart';
 | |
| import 'package:flutter/services.dart';
 | |
| import 'package:flutter/widgets.dart';
 | |
| import 'package:flutter_hbb/main.dart';
 | |
| import 'package:flutter_hbb/utils/multi_window_manager.dart';
 | |
| import 'package:get/get.dart';
 | |
| 
 | |
| import '../../models/model.dart';
 | |
| import '../../models/platform_model.dart';
 | |
| import '../common.dart';
 | |
| import '../consts.dart';
 | |
| 
 | |
| /// Mouse button enum.
 | |
| enum MouseButtons { left, right, wheel }
 | |
| 
 | |
| const _kMouseEventDown = 'mousedown';
 | |
| const _kMouseEventUp = 'mouseup';
 | |
| const _kMouseEventMove = 'mousemove';
 | |
| 
 | |
| class CanvasCoords {
 | |
|   double x = 0;
 | |
|   double y = 0;
 | |
|   double scale = 1.0;
 | |
|   double scrollX = 0;
 | |
|   double scrollY = 0;
 | |
|   ScrollStyle scrollStyle = ScrollStyle.scrollauto;
 | |
|   Size size = Size.zero;
 | |
| 
 | |
|   CanvasCoords();
 | |
| 
 | |
|   Map<String, dynamic> toJson() {
 | |
|     return {
 | |
|       'x': x,
 | |
|       'y': y,
 | |
|       'scale': scale,
 | |
|       'scrollX': scrollX,
 | |
|       'scrollY': scrollY,
 | |
|       'scrollStyle':
 | |
|           scrollStyle == ScrollStyle.scrollauto ? 'scrollauto' : 'scrollbar',
 | |
|       'size': {
 | |
|         'w': size.width,
 | |
|         'h': size.height,
 | |
|       }
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   static CanvasCoords fromJson(Map<String, dynamic> json) {
 | |
|     final model = CanvasCoords();
 | |
|     model.x = json['x'];
 | |
|     model.y = json['y'];
 | |
|     model.scale = json['scale'];
 | |
|     model.scrollX = json['scrollX'];
 | |
|     model.scrollY = json['scrollY'];
 | |
|     model.scrollStyle = json['scrollStyle'] == 'scrollauto'
 | |
|         ? ScrollStyle.scrollauto
 | |
|         : ScrollStyle.scrollbar;
 | |
|     model.size = Size(json['size']['w'], json['size']['h']);
 | |
|     return model;
 | |
|   }
 | |
| 
 | |
|   static CanvasCoords fromCanvasModel(CanvasModel model) {
 | |
|     final coords = CanvasCoords();
 | |
|     coords.x = model.x;
 | |
|     coords.y = model.y;
 | |
|     coords.scale = model.scale;
 | |
|     coords.scrollX = model.scrollX;
 | |
|     coords.scrollY = model.scrollY;
 | |
|     coords.scrollStyle = model.scrollStyle;
 | |
|     coords.size = model.size;
 | |
|     return coords;
 | |
|   }
 | |
| }
 | |
| 
 | |
| class CursorCoords {
 | |
|   Offset offset = Offset.zero;
 | |
| 
 | |
|   CursorCoords();
 | |
| 
 | |
|   Map<String, dynamic> toJson() {
 | |
|     return {
 | |
|       'offset_x': offset.dx,
 | |
|       'offset_y': offset.dy,
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   static CursorCoords fromJson(Map<String, dynamic> json) {
 | |
|     final model = CursorCoords();
 | |
|     model.offset = Offset(json['offset_x'], json['offset_y']);
 | |
|     return model;
 | |
|   }
 | |
| 
 | |
|   static CursorCoords fromCursorModel(CursorModel model) {
 | |
|     final coords = CursorCoords();
 | |
|     coords.offset = model.offset;
 | |
|     return coords;
 | |
|   }
 | |
| }
 | |
| 
 | |
| class RemoteWindowCoords {
 | |
|   RemoteWindowCoords(
 | |
|       this.windowRect, this.canvas, this.cursor, this.remoteRect);
 | |
|   Rect windowRect;
 | |
|   CanvasCoords canvas;
 | |
|   CursorCoords cursor;
 | |
|   Rect remoteRect;
 | |
|   Offset relativeOffset = Offset.zero;
 | |
| 
 | |
|   Map<String, dynamic> toJson() {
 | |
|     return {
 | |
|       'canvas': canvas.toJson(),
 | |
|       'cursor': cursor.toJson(),
 | |
|       'windowRect': rectToJson(windowRect),
 | |
|       'remoteRect': rectToJson(remoteRect),
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   static Map<String, dynamic> rectToJson(Rect r) {
 | |
|     return {
 | |
|       'l': r.left,
 | |
|       't': r.top,
 | |
|       'w': r.width,
 | |
|       'h': r.height,
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   static Rect rectFromJson(Map<String, dynamic> json) {
 | |
|     return Rect.fromLTWH(
 | |
|       json['l'],
 | |
|       json['t'],
 | |
|       json['w'],
 | |
|       json['h'],
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   RemoteWindowCoords.fromJson(Map<String, dynamic> json)
 | |
|       : windowRect = rectFromJson(json['windowRect']),
 | |
|         canvas = CanvasCoords.fromJson(json['canvas']),
 | |
|         cursor = CursorCoords.fromJson(json['cursor']),
 | |
|         remoteRect = rectFromJson(json['remoteRect']);
 | |
| }
 | |
| 
 | |
| extension ToString on MouseButtons {
 | |
|   String get value {
 | |
|     switch (this) {
 | |
|       case MouseButtons.left:
 | |
|         return 'left';
 | |
|       case MouseButtons.right:
 | |
|         return 'right';
 | |
|       case MouseButtons.wheel:
 | |
|         return 'wheel';
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| class PointerEventToRust {
 | |
|   final String kind;
 | |
|   final String type;
 | |
|   final dynamic value;
 | |
| 
 | |
|   PointerEventToRust(this.kind, this.type, this.value);
 | |
| 
 | |
|   Map<String, dynamic> toJson() {
 | |
|     return {
 | |
|       'k': kind,
 | |
|       'v': {
 | |
|         't': type,
 | |
|         'v': value,
 | |
|       }
 | |
|     };
 | |
|   }
 | |
| }
 | |
| 
 | |
| class ToReleaseKeys {
 | |
|   RawKeyEvent? lastLShiftKeyEvent;
 | |
|   RawKeyEvent? lastRShiftKeyEvent;
 | |
|   RawKeyEvent? lastLCtrlKeyEvent;
 | |
|   RawKeyEvent? lastRCtrlKeyEvent;
 | |
|   RawKeyEvent? lastLAltKeyEvent;
 | |
|   RawKeyEvent? lastRAltKeyEvent;
 | |
|   RawKeyEvent? lastLCommandKeyEvent;
 | |
|   RawKeyEvent? lastRCommandKeyEvent;
 | |
|   RawKeyEvent? lastSuperKeyEvent;
 | |
| 
 | |
|   reset() {
 | |
|     lastLShiftKeyEvent = null;
 | |
|     lastRShiftKeyEvent = null;
 | |
|     lastLCtrlKeyEvent = null;
 | |
|     lastRCtrlKeyEvent = null;
 | |
|     lastLAltKeyEvent = null;
 | |
|     lastRAltKeyEvent = null;
 | |
|     lastLCommandKeyEvent = null;
 | |
|     lastRCommandKeyEvent = null;
 | |
|     lastSuperKeyEvent = null;
 | |
|   }
 | |
| 
 | |
|   updateKeyDown(LogicalKeyboardKey logicKey, RawKeyDownEvent e) {
 | |
|     if (e.isAltPressed) {
 | |
|       if (logicKey == LogicalKeyboardKey.altLeft) {
 | |
|         lastLAltKeyEvent = e;
 | |
|       } else if (logicKey == LogicalKeyboardKey.altRight) {
 | |
|         lastRAltKeyEvent = e;
 | |
|       }
 | |
|     } else if (e.isControlPressed) {
 | |
|       if (logicKey == LogicalKeyboardKey.controlLeft) {
 | |
|         lastLCtrlKeyEvent = e;
 | |
|       } else if (logicKey == LogicalKeyboardKey.controlRight) {
 | |
|         lastRCtrlKeyEvent = e;
 | |
|       }
 | |
|     } else if (e.isShiftPressed) {
 | |
|       if (logicKey == LogicalKeyboardKey.shiftLeft) {
 | |
|         lastLShiftKeyEvent = e;
 | |
|       } else if (logicKey == LogicalKeyboardKey.shiftRight) {
 | |
|         lastRShiftKeyEvent = e;
 | |
|       }
 | |
|     } else if (e.isMetaPressed) {
 | |
|       if (logicKey == LogicalKeyboardKey.metaLeft) {
 | |
|         lastLCommandKeyEvent = e;
 | |
|       } else if (logicKey == LogicalKeyboardKey.metaRight) {
 | |
|         lastRCommandKeyEvent = e;
 | |
|       } else if (logicKey == LogicalKeyboardKey.superKey) {
 | |
|         lastSuperKeyEvent = e;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   updateKeyUp(LogicalKeyboardKey logicKey, RawKeyUpEvent e) {
 | |
|     if (e.isAltPressed) {
 | |
|       if (logicKey == LogicalKeyboardKey.altLeft) {
 | |
|         lastLAltKeyEvent = null;
 | |
|       } else if (logicKey == LogicalKeyboardKey.altRight) {
 | |
|         lastRAltKeyEvent = null;
 | |
|       }
 | |
|     } else if (e.isControlPressed) {
 | |
|       if (logicKey == LogicalKeyboardKey.controlLeft) {
 | |
|         lastLCtrlKeyEvent = null;
 | |
|       } else if (logicKey == LogicalKeyboardKey.controlRight) {
 | |
|         lastRCtrlKeyEvent = null;
 | |
|       }
 | |
|     } else if (e.isShiftPressed) {
 | |
|       if (logicKey == LogicalKeyboardKey.shiftLeft) {
 | |
|         lastLShiftKeyEvent = null;
 | |
|       } else if (logicKey == LogicalKeyboardKey.shiftRight) {
 | |
|         lastRShiftKeyEvent = null;
 | |
|       }
 | |
|     } else if (e.isMetaPressed) {
 | |
|       if (logicKey == LogicalKeyboardKey.metaLeft) {
 | |
|         lastLCommandKeyEvent = null;
 | |
|       } else if (logicKey == LogicalKeyboardKey.metaRight) {
 | |
|         lastRCommandKeyEvent = null;
 | |
|       } else if (logicKey == LogicalKeyboardKey.superKey) {
 | |
|         lastSuperKeyEvent = null;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   release(KeyEventResult Function(RawKeyEvent e) handleRawKeyEvent) {
 | |
|     for (final key in [
 | |
|       lastLShiftKeyEvent,
 | |
|       lastRShiftKeyEvent,
 | |
|       lastLCtrlKeyEvent,
 | |
|       lastRCtrlKeyEvent,
 | |
|       lastLAltKeyEvent,
 | |
|       lastRAltKeyEvent,
 | |
|       lastLCommandKeyEvent,
 | |
|       lastRCommandKeyEvent,
 | |
|       lastSuperKeyEvent,
 | |
|     ]) {
 | |
|       if (key != null) {
 | |
|         handleRawKeyEvent(RawKeyUpEvent(
 | |
|           data: key.data,
 | |
|           character: key.character,
 | |
|         ));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| class InputModel {
 | |
|   final WeakReference<FFI> parent;
 | |
|   String keyboardMode = '';
 | |
| 
 | |
|   // keyboard
 | |
|   var shift = false;
 | |
|   var ctrl = false;
 | |
|   var alt = false;
 | |
|   var command = false;
 | |
| 
 | |
|   final ToReleaseKeys toReleaseKeys = ToReleaseKeys();
 | |
| 
 | |
|   // trackpad
 | |
|   var _trackpadLastDelta = Offset.zero;
 | |
|   var _stopFling = true;
 | |
|   var _fling = false;
 | |
|   Timer? _flingTimer;
 | |
|   final _flingBaseDelay = 30;
 | |
|   // trackpad, peer linux
 | |
|   final _trackpadSpeed = 0.06;
 | |
|   var _trackpadScrollUnsent = Offset.zero;
 | |
| 
 | |
|   var _lastScale = 1.0;
 | |
| 
 | |
|   bool _pointerMovedAfterEnter = false;
 | |
| 
 | |
|   // mouse
 | |
|   final isPhysicalMouse = false.obs;
 | |
|   int _lastButtons = 0;
 | |
|   Offset lastMousePos = Offset.zero;
 | |
| 
 | |
|   bool _queryOtherWindowCoords = false;
 | |
|   Rect? _windowRect;
 | |
|   List<RemoteWindowCoords> _remoteWindowCoords = [];
 | |
| 
 | |
|   late final SessionID sessionId;
 | |
| 
 | |
|   bool get keyboardPerm => parent.target!.ffiModel.keyboard;
 | |
|   String get id => parent.target?.id ?? '';
 | |
|   String? get peerPlatform => parent.target?.ffiModel.pi.platform;
 | |
|   bool get isViewOnly => parent.target!.ffiModel.viewOnly;
 | |
|   double get devicePixelRatio => parent.target!.canvasModel.devicePixelRatio;
 | |
| 
 | |
|   InputModel(this.parent) {
 | |
|     sessionId = parent.target!.sessionId;
 | |
|   }
 | |
| 
 | |
|   // This function must be called after the peer info is received.
 | |
|   // Because `sessionGetKeyboardMode` relies on the peer version.
 | |
|   updateKeyboardMode() async {
 | |
|     // * Currently mobile does not enable map mode
 | |
|     if (isDesktop || isWebDesktop) {
 | |
|       keyboardMode = await bind.sessionGetKeyboardMode(sessionId: sessionId) ??
 | |
|           kKeyLegacyMode;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   KeyEventResult handleRawKeyEvent(RawKeyEvent e) {
 | |
|     if (isViewOnly) return KeyEventResult.handled;
 | |
|     if ((isDesktop || isWebDesktop) && !isInputSourceFlutter) {
 | |
|       return KeyEventResult.handled;
 | |
|     }
 | |
| 
 | |
|     final key = e.logicalKey;
 | |
|     if (e is RawKeyDownEvent) {
 | |
|       if (!e.repeat) {
 | |
|         if (e.isAltPressed && !alt) {
 | |
|           alt = true;
 | |
|         } else if (e.isControlPressed && !ctrl) {
 | |
|           ctrl = true;
 | |
|         } else if (e.isShiftPressed && !shift) {
 | |
|           shift = true;
 | |
|         } else if (e.isMetaPressed && !command) {
 | |
|           command = true;
 | |
|         }
 | |
|       }
 | |
|       toReleaseKeys.updateKeyDown(key, e);
 | |
|     }
 | |
|     if (e is RawKeyUpEvent) {
 | |
|       if (key == LogicalKeyboardKey.altLeft ||
 | |
|           key == LogicalKeyboardKey.altRight) {
 | |
|         alt = false;
 | |
|       } else if (key == LogicalKeyboardKey.controlLeft ||
 | |
|           key == LogicalKeyboardKey.controlRight) {
 | |
|         ctrl = false;
 | |
|       } else if (key == LogicalKeyboardKey.shiftRight ||
 | |
|           key == LogicalKeyboardKey.shiftLeft) {
 | |
|         shift = false;
 | |
|       } else if (key == LogicalKeyboardKey.metaLeft ||
 | |
|           key == LogicalKeyboardKey.metaRight ||
 | |
|           key == LogicalKeyboardKey.superKey) {
 | |
|         command = false;
 | |
|       }
 | |
| 
 | |
|       toReleaseKeys.updateKeyUp(key, e);
 | |
|     }
 | |
| 
 | |
|     // * Currently mobile does not enable map mode
 | |
|     if ((isDesktop || isWebDesktop) && keyboardMode == 'map') {
 | |
|       mapKeyboardMode(e);
 | |
|     } else {
 | |
|       legacyKeyboardMode(e);
 | |
|     }
 | |
| 
 | |
|     return KeyEventResult.handled;
 | |
|   }
 | |
| 
 | |
|   void mapKeyboardMode(RawKeyEvent e) {
 | |
|     int positionCode = -1;
 | |
|     int platformCode = -1;
 | |
|     bool down;
 | |
| 
 | |
|     if (e.data is RawKeyEventDataMacOs) {
 | |
|       RawKeyEventDataMacOs newData = e.data as RawKeyEventDataMacOs;
 | |
|       positionCode = newData.keyCode;
 | |
|       platformCode = newData.keyCode;
 | |
|     } else if (e.data is RawKeyEventDataWindows) {
 | |
|       RawKeyEventDataWindows newData = e.data as RawKeyEventDataWindows;
 | |
|       positionCode = newData.scanCode;
 | |
|       platformCode = newData.keyCode;
 | |
|     } else if (e.data is RawKeyEventDataLinux) {
 | |
|       RawKeyEventDataLinux newData = e.data as RawKeyEventDataLinux;
 | |
|       // scanCode and keyCode of RawKeyEventDataLinux are incorrect.
 | |
|       // 1. scanCode means keycode
 | |
|       // 2. keyCode means keysym
 | |
|       positionCode = newData.scanCode;
 | |
|       platformCode = newData.keyCode;
 | |
|     } else if (e.data is RawKeyEventDataAndroid) {
 | |
|       RawKeyEventDataAndroid newData = e.data as RawKeyEventDataAndroid;
 | |
|       positionCode = newData.scanCode + 8;
 | |
|       platformCode = newData.keyCode;
 | |
|     } else {}
 | |
| 
 | |
|     if (e is RawKeyDownEvent) {
 | |
|       down = true;
 | |
|     } else {
 | |
|       down = false;
 | |
|     }
 | |
|     inputRawKey(e.character ?? '', platformCode, positionCode, down);
 | |
|   }
 | |
| 
 | |
|   /// Send raw Key Event
 | |
|   void inputRawKey(String name, int platformCode, int positionCode, bool down) {
 | |
|     const capslock = 1;
 | |
|     const numlock = 2;
 | |
|     const scrolllock = 3;
 | |
|     int lockModes = 0;
 | |
|     if (HardwareKeyboard.instance.lockModesEnabled
 | |
|         .contains(KeyboardLockMode.capsLock)) {
 | |
|       lockModes |= (1 << capslock);
 | |
|     }
 | |
|     if (HardwareKeyboard.instance.lockModesEnabled
 | |
|         .contains(KeyboardLockMode.numLock)) {
 | |
|       lockModes |= (1 << numlock);
 | |
|     }
 | |
|     if (HardwareKeyboard.instance.lockModesEnabled
 | |
|         .contains(KeyboardLockMode.scrollLock)) {
 | |
|       lockModes |= (1 << scrolllock);
 | |
|     }
 | |
|     bind.sessionHandleFlutterKeyEvent(
 | |
|         sessionId: sessionId,
 | |
|         name: name,
 | |
|         platformCode: platformCode,
 | |
|         positionCode: positionCode,
 | |
|         lockModes: lockModes,
 | |
|         downOrUp: down);
 | |
|   }
 | |
| 
 | |
|   void legacyKeyboardMode(RawKeyEvent e) {
 | |
|     if (e is RawKeyDownEvent) {
 | |
|       if (e.repeat) {
 | |
|         sendRawKey(e, press: true);
 | |
|       } else {
 | |
|         sendRawKey(e, down: true);
 | |
|       }
 | |
|     }
 | |
|     if (e is RawKeyUpEvent) {
 | |
|       sendRawKey(e);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void sendRawKey(RawKeyEvent e, {bool? down, bool? press}) {
 | |
|     // for maximum compatibility
 | |
|     final label = physicalKeyMap[e.physicalKey.usbHidUsage] ??
 | |
|         logicalKeyMap[e.logicalKey.keyId] ??
 | |
|         e.logicalKey.keyLabel;
 | |
|     inputKey(label, down: down, press: press ?? false);
 | |
|   }
 | |
| 
 | |
|   /// Send key stroke event.
 | |
|   /// [down] indicates the key's state(down or up).
 | |
|   /// [press] indicates a click event(down and up).
 | |
|   void inputKey(String name, {bool? down, bool? press}) {
 | |
|     if (!keyboardPerm) return;
 | |
|     bind.sessionInputKey(
 | |
|         sessionId: sessionId,
 | |
|         name: name,
 | |
|         down: down ?? false,
 | |
|         press: press ?? true,
 | |
|         alt: alt,
 | |
|         ctrl: ctrl,
 | |
|         shift: shift,
 | |
|         command: command);
 | |
|   }
 | |
| 
 | |
|   Map<String, dynamic> _getMouseEvent(PointerEvent evt, String type) {
 | |
|     final Map<String, dynamic> out = {};
 | |
| 
 | |
|     // Check update event type and set buttons to be sent.
 | |
|     int buttons = _lastButtons;
 | |
|     if (type == _kMouseEventMove) {
 | |
|       // flutter may emit move event if one button is pressed and another button
 | |
|       // is pressing or releasing.
 | |
|       if (evt.buttons != _lastButtons) {
 | |
|         // For simplicity
 | |
|         // Just consider 3 - 1 ((Left + Right buttons) - Left button)
 | |
|         // Do not consider 2 - 1 (Right button - Left button)
 | |
|         // or 6 - 5 ((Right + Mid buttons) - (Left + Mid buttons))
 | |
|         // and so on
 | |
|         buttons = evt.buttons - _lastButtons;
 | |
|         if (buttons > 0) {
 | |
|           type = _kMouseEventDown;
 | |
|         } else {
 | |
|           type = _kMouseEventUp;
 | |
|           buttons = -buttons;
 | |
|         }
 | |
|       }
 | |
|     } else {
 | |
|       if (evt.buttons != 0) {
 | |
|         buttons = evt.buttons;
 | |
|       }
 | |
|     }
 | |
|     _lastButtons = evt.buttons;
 | |
| 
 | |
|     out['buttons'] = buttons;
 | |
|     out['type'] = type;
 | |
|     return out;
 | |
|   }
 | |
| 
 | |
|   /// Send a mouse tap event(down and up).
 | |
|   void tap(MouseButtons button) {
 | |
|     sendMouse('down', button);
 | |
|     sendMouse('up', button);
 | |
|   }
 | |
| 
 | |
|   void tapDown(MouseButtons button) {
 | |
|     sendMouse('down', button);
 | |
|   }
 | |
| 
 | |
|   void tapUp(MouseButtons button) {
 | |
|     sendMouse('up', button);
 | |
|   }
 | |
| 
 | |
|   /// Send scroll event with scroll distance [y].
 | |
|   void scroll(int y) {
 | |
|     bind.sessionSendMouse(
 | |
|         sessionId: sessionId,
 | |
|         msg: json
 | |
|             .encode(modify({'id': id, 'type': 'wheel', 'y': y.toString()})));
 | |
|   }
 | |
| 
 | |
|   /// Reset key modifiers to false, including [shift], [ctrl], [alt] and [command].
 | |
|   void resetModifiers() {
 | |
|     shift = ctrl = alt = command = false;
 | |
|   }
 | |
| 
 | |
|   /// Modify the given modifier map [evt] based on current modifier key status.
 | |
|   Map<String, dynamic> modify(Map<String, dynamic> evt) {
 | |
|     if (ctrl) evt['ctrl'] = 'true';
 | |
|     if (shift) evt['shift'] = 'true';
 | |
|     if (alt) evt['alt'] = 'true';
 | |
|     if (command) evt['command'] = 'true';
 | |
|     return evt;
 | |
|   }
 | |
| 
 | |
|   /// Send mouse press event.
 | |
|   void sendMouse(String type, MouseButtons button) {
 | |
|     if (!keyboardPerm) return;
 | |
|     bind.sessionSendMouse(
 | |
|         sessionId: sessionId,
 | |
|         msg: json.encode(modify({'type': type, 'buttons': button.value})));
 | |
|   }
 | |
| 
 | |
|   void enterOrLeave(bool enter) {
 | |
|     toReleaseKeys.release(handleRawKeyEvent);
 | |
|     _pointerMovedAfterEnter = false;
 | |
| 
 | |
|     // Fix status
 | |
|     if (!enter) {
 | |
|       resetModifiers();
 | |
|     }
 | |
|     _flingTimer?.cancel();
 | |
|     if (!isInputSourceFlutter) {
 | |
|       bind.sessionEnterOrLeave(sessionId: sessionId, enter: enter);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /// Send mouse movement event with distance in [x] and [y].
 | |
|   void moveMouse(double x, double y) {
 | |
|     if (!keyboardPerm) return;
 | |
|     var x2 = x.toInt();
 | |
|     var y2 = y.toInt();
 | |
|     bind.sessionSendMouse(
 | |
|         sessionId: sessionId,
 | |
|         msg: json.encode(modify({'x': '$x2', 'y': '$y2'})));
 | |
|   }
 | |
| 
 | |
|   void onPointHoverImage(PointerHoverEvent e) {
 | |
|     _stopFling = true;
 | |
|     if (isViewOnly) return;
 | |
|     if (e.kind != ui.PointerDeviceKind.mouse) return;
 | |
|     if (!isPhysicalMouse.value) {
 | |
|       isPhysicalMouse.value = true;
 | |
|     }
 | |
|     if (isPhysicalMouse.value) {
 | |
|       handleMouse(_getMouseEvent(e, _kMouseEventMove), e.position);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void onPointerPanZoomStart(PointerPanZoomStartEvent e) {
 | |
|     _lastScale = 1.0;
 | |
|     _stopFling = true;
 | |
|     if (isViewOnly) return;
 | |
|     if (peerPlatform == kPeerPlatformAndroid) {
 | |
|       handlePointerEvent('touch', 'pan_start', e.position);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // https://docs.flutter.dev/release/breaking-changes/trackpad-gestures
 | |
|   void onPointerPanZoomUpdate(PointerPanZoomUpdateEvent e) {
 | |
|     if (isViewOnly) return;
 | |
|     if (peerPlatform != kPeerPlatformAndroid) {
 | |
|       final scale = ((e.scale - _lastScale) * 1000).toInt();
 | |
|       _lastScale = e.scale;
 | |
| 
 | |
|       if (scale != 0) {
 | |
|         bind.sessionSendPointer(
 | |
|             sessionId: sessionId,
 | |
|             msg: json.encode(
 | |
|                 PointerEventToRust(kPointerEventKindTouch, 'scale', scale)
 | |
|                     .toJson()));
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     final delta = e.panDelta;
 | |
|     _trackpadLastDelta = delta;
 | |
| 
 | |
|     var x = delta.dx.toInt();
 | |
|     var y = delta.dy.toInt();
 | |
|     if (peerPlatform == kPeerPlatformLinux) {
 | |
|       _trackpadScrollUnsent += (delta * _trackpadSpeed);
 | |
|       x = _trackpadScrollUnsent.dx.truncate();
 | |
|       y = _trackpadScrollUnsent.dy.truncate();
 | |
|       _trackpadScrollUnsent -= Offset(x.toDouble(), y.toDouble());
 | |
|     } else {
 | |
|       if (x == 0 && y == 0) {
 | |
|         final thr = 0.1;
 | |
|         if (delta.dx.abs() > delta.dy.abs()) {
 | |
|           x = delta.dx > thr ? 1 : (delta.dx < -thr ? -1 : 0);
 | |
|         } else {
 | |
|           y = delta.dy > thr ? 1 : (delta.dy < -thr ? -1 : 0);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     if (x != 0 || y != 0) {
 | |
|       if (peerPlatform == kPeerPlatformAndroid) {
 | |
|         handlePointerEvent(
 | |
|             'touch', 'pan_update', Offset(x.toDouble(), y.toDouble()));
 | |
|       } else {
 | |
|         bind.sessionSendMouse(
 | |
|             sessionId: sessionId,
 | |
|             msg: '{"type": "trackpad", "x": "$x", "y": "$y"}');
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void _scheduleFling(double x, double y, int delay) {
 | |
|     if ((x == 0 && y == 0) || _stopFling) {
 | |
|       _fling = false;
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     _flingTimer = Timer(Duration(milliseconds: delay), () {
 | |
|       if (_stopFling) {
 | |
|         _fling = false;
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       final d = 0.97;
 | |
|       x *= d;
 | |
|       y *= d;
 | |
| 
 | |
|       // Try set delta (x,y) and delay.
 | |
|       var dx = x.toInt();
 | |
|       var dy = y.toInt();
 | |
|       if (parent.target?.ffiModel.pi.platform == kPeerPlatformLinux) {
 | |
|         dx = (x * _trackpadSpeed).toInt();
 | |
|         dy = (y * _trackpadSpeed).toInt();
 | |
|       }
 | |
| 
 | |
|       var delay = _flingBaseDelay;
 | |
| 
 | |
|       if (dx == 0 && dy == 0) {
 | |
|         _fling = false;
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       bind.sessionSendMouse(
 | |
|           sessionId: sessionId,
 | |
|           msg: '{"type": "trackpad", "x": "$dx", "y": "$dy"}');
 | |
|       _scheduleFling(x, y, delay);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   void waitLastFlingDone() {
 | |
|     if (_fling) {
 | |
|       _stopFling = true;
 | |
|     }
 | |
|     for (var i = 0; i < 5; i++) {
 | |
|       if (!_fling) {
 | |
|         break;
 | |
|       }
 | |
|       sleep(Duration(milliseconds: 10));
 | |
|     }
 | |
|     _flingTimer?.cancel();
 | |
|   }
 | |
| 
 | |
|   void onPointerPanZoomEnd(PointerPanZoomEndEvent e) {
 | |
|     if (peerPlatform == kPeerPlatformAndroid) {
 | |
|       handlePointerEvent('touch', 'pan_end', e.position);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     bind.sessionSendPointer(
 | |
|         sessionId: sessionId,
 | |
|         msg: json.encode(
 | |
|             PointerEventToRust(kPointerEventKindTouch, 'scale', 0).toJson()));
 | |
| 
 | |
|     waitLastFlingDone();
 | |
|     _stopFling = false;
 | |
| 
 | |
|     // 2.0 is an experience value
 | |
|     double minFlingValue = 2.0;
 | |
|     if (_trackpadLastDelta.dx.abs() > minFlingValue ||
 | |
|         _trackpadLastDelta.dy.abs() > minFlingValue) {
 | |
|       _fling = true;
 | |
|       _scheduleFling(
 | |
|           _trackpadLastDelta.dx, _trackpadLastDelta.dy, _flingBaseDelay);
 | |
|     }
 | |
|     _trackpadLastDelta = Offset.zero;
 | |
|   }
 | |
| 
 | |
|   void onPointDownImage(PointerDownEvent e) {
 | |
|     debugPrint("onPointDownImage ${e.kind}");
 | |
|     _stopFling = true;
 | |
|     if (isDesktop) _queryOtherWindowCoords = true;
 | |
|     _remoteWindowCoords = [];
 | |
|     _windowRect = null;
 | |
|     if (isViewOnly) return;
 | |
|     if (e.kind != ui.PointerDeviceKind.mouse) {
 | |
|       if (isPhysicalMouse.value) {
 | |
|         isPhysicalMouse.value = false;
 | |
|       }
 | |
|     }
 | |
|     if (isPhysicalMouse.value) {
 | |
|       handleMouse(_getMouseEvent(e, _kMouseEventDown), e.position);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void onPointUpImage(PointerUpEvent e) {
 | |
|     if (isDesktop) _queryOtherWindowCoords = false;
 | |
|     if (isViewOnly) return;
 | |
|     if (e.kind != ui.PointerDeviceKind.mouse) return;
 | |
|     if (isPhysicalMouse.value) {
 | |
|       handleMouse(_getMouseEvent(e, _kMouseEventUp), e.position);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void onPointMoveImage(PointerMoveEvent e) {
 | |
|     if (isViewOnly) return;
 | |
|     if (e.kind != ui.PointerDeviceKind.mouse) return;
 | |
|     if (_queryOtherWindowCoords) {
 | |
|       Future.delayed(Duration.zero, () async {
 | |
|         _windowRect = await fillRemoteCoordsAndGetCurFrame(_remoteWindowCoords);
 | |
|       });
 | |
|       _queryOtherWindowCoords = false;
 | |
|     }
 | |
|     if (isPhysicalMouse.value) {
 | |
|       handleMouse(_getMouseEvent(e, _kMouseEventMove), e.position);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static Future<Rect?> fillRemoteCoordsAndGetCurFrame(
 | |
|       List<RemoteWindowCoords> remoteWindowCoords) async {
 | |
|     final coords =
 | |
|         await rustDeskWinManager.getOtherRemoteWindowCoordsFromMain();
 | |
|     final wc = WindowController.fromWindowId(kWindowId!);
 | |
|     try {
 | |
|       final frame = await wc.getFrame();
 | |
|       for (final c in coords) {
 | |
|         c.relativeOffset = Offset(
 | |
|             c.windowRect.left - frame.left, c.windowRect.top - frame.top);
 | |
|         remoteWindowCoords.add(c);
 | |
|       }
 | |
|       return frame;
 | |
|     } catch (e) {
 | |
|       // Unreachable code
 | |
|       debugPrint("Failed to get frame of window $kWindowId, it may be hidden");
 | |
|     }
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   void onPointerSignalImage(PointerSignalEvent e) {
 | |
|     if (isViewOnly) return;
 | |
|     if (e is PointerScrollEvent) {
 | |
|       var dx = e.scrollDelta.dx.toInt();
 | |
|       var dy = e.scrollDelta.dy.toInt();
 | |
|       if (dx > 0) {
 | |
|         dx = -1;
 | |
|       } else if (dx < 0) {
 | |
|         dx = 1;
 | |
|       }
 | |
|       if (dy > 0) {
 | |
|         dy = -1;
 | |
|       } else if (dy < 0) {
 | |
|         dy = 1;
 | |
|       }
 | |
|       bind.sessionSendMouse(
 | |
|           sessionId: sessionId,
 | |
|           msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}');
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void refreshMousePos() => handleMouse({
 | |
|         'buttons': 0,
 | |
|         'type': _kMouseEventMove,
 | |
|       }, lastMousePos);
 | |
| 
 | |
|   void tryMoveEdgeOnExit(Offset pos) => handleMouse(
 | |
|         {
 | |
|           'buttons': 0,
 | |
|           'type': _kMouseEventMove,
 | |
|         },
 | |
|         pos,
 | |
|         onExit: true,
 | |
|       );
 | |
| 
 | |
|   int trySetNearestRange(int v, int min, int max, int n) {
 | |
|     if (v < min && v >= min - n) {
 | |
|       v = min;
 | |
|     }
 | |
|     if (v > max && v <= max + n) {
 | |
|       v = max;
 | |
|     }
 | |
|     return v;
 | |
|   }
 | |
| 
 | |
|   Offset setNearestEdge(double x, double y, Rect rect) {
 | |
|     double left = x - rect.left;
 | |
|     double right = rect.right - 1 - x;
 | |
|     double top = y - rect.top;
 | |
|     double bottom = rect.bottom - 1 - y;
 | |
|     if (left < right && left < top && left < bottom) {
 | |
|       x = rect.left;
 | |
|     }
 | |
|     if (right < left && right < top && right < bottom) {
 | |
|       x = rect.right - 1;
 | |
|     }
 | |
|     if (top < left && top < right && top < bottom) {
 | |
|       y = rect.top;
 | |
|     }
 | |
|     if (bottom < left && bottom < right && bottom < top) {
 | |
|       y = rect.bottom - 1;
 | |
|     }
 | |
|     return Offset(x, y);
 | |
|   }
 | |
| 
 | |
|   void handlePointerEvent(String kind, String type, Offset offset) {
 | |
|     double x = offset.dx;
 | |
|     double y = offset.dy;
 | |
|     if (_checkPeerControlProtected(x, y)) {
 | |
|       return;
 | |
|     }
 | |
|     // Only touch events are handled for now. So we can just ignore buttons.
 | |
|     // to-do: handle mouse events
 | |
| 
 | |
|     late final dynamic evtValue;
 | |
|     if (type == 'pan_update') {
 | |
|       evtValue = {
 | |
|         'x': x.toInt(),
 | |
|         'y': y.toInt(),
 | |
|       };
 | |
|     } else {
 | |
|       final isMoveTypes = ['pan_start', 'pan_end'];
 | |
|       final pos = handlePointerDevicePos(
 | |
|         kPointerEventKindTouch,
 | |
|         x,
 | |
|         y,
 | |
|         isMoveTypes.contains(type),
 | |
|         type,
 | |
|       );
 | |
|       if (pos == null) {
 | |
|         return;
 | |
|       }
 | |
|       evtValue = {
 | |
|         'x': pos.x,
 | |
|         'y': pos.y,
 | |
|       };
 | |
|     }
 | |
| 
 | |
|     final evt = PointerEventToRust(kind, type, evtValue).toJson();
 | |
|     bind.sessionSendPointer(
 | |
|         sessionId: sessionId, msg: json.encode(modify(evt)));
 | |
|   }
 | |
| 
 | |
|   bool _checkPeerControlProtected(double x, double y) {
 | |
|     final cursorModel = parent.target!.cursorModel;
 | |
|     if (cursorModel.isPeerControlProtected) {
 | |
|       lastMousePos = ui.Offset(x, y);
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     if (!cursorModel.gotMouseControl) {
 | |
|       bool selfGetControl =
 | |
|           (x - lastMousePos.dx).abs() > kMouseControlDistance ||
 | |
|               (y - lastMousePos.dy).abs() > kMouseControlDistance;
 | |
|       if (selfGetControl) {
 | |
|         cursorModel.gotMouseControl = true;
 | |
|       } else {
 | |
|         lastMousePos = ui.Offset(x, y);
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
|     lastMousePos = ui.Offset(x, y);
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   void handleMouse(
 | |
|     Map<String, dynamic> evt,
 | |
|     Offset offset, {
 | |
|     bool onExit = false,
 | |
|   }) {
 | |
|     double x = offset.dx;
 | |
|     double y = max(0.0, offset.dy);
 | |
|     if (_checkPeerControlProtected(x, y)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     var type = '';
 | |
|     var isMove = false;
 | |
|     switch (evt['type']) {
 | |
|       case _kMouseEventDown:
 | |
|         type = 'down';
 | |
|         break;
 | |
|       case _kMouseEventUp:
 | |
|         type = 'up';
 | |
|         break;
 | |
|       case _kMouseEventMove:
 | |
|         _pointerMovedAfterEnter = true;
 | |
|         isMove = true;
 | |
|         break;
 | |
|       default:
 | |
|         return;
 | |
|     }
 | |
|     evt['type'] = type;
 | |
| 
 | |
|     if (type == 'down' && !_pointerMovedAfterEnter) {
 | |
|       // Move mouse to the position of the down event first.
 | |
|       lastMousePos = ui.Offset(x, y);
 | |
|       refreshMousePos();
 | |
|     }
 | |
| 
 | |
|     final pos = handlePointerDevicePos(
 | |
|       kPointerEventKindMouse,
 | |
|       x,
 | |
|       y,
 | |
|       isMove,
 | |
|       type,
 | |
|       onExit: onExit,
 | |
|       buttons: evt['buttons'],
 | |
|     );
 | |
|     if (pos == null) {
 | |
|       return;
 | |
|     }
 | |
|     if (type != '') {
 | |
|       evt['x'] = '0';
 | |
|       evt['y'] = '0';
 | |
|     } else {
 | |
|       evt['x'] = '${pos.x}';
 | |
|       evt['y'] = '${pos.y}';
 | |
|     }
 | |
| 
 | |
|     Map<int, String> mapButtons = {
 | |
|       kPrimaryMouseButton: 'left',
 | |
|       kSecondaryMouseButton: 'right',
 | |
|       kMiddleMouseButton: 'wheel',
 | |
|       kBackMouseButton: 'back',
 | |
|       kForwardMouseButton: 'forward'
 | |
|     };
 | |
|     evt['buttons'] = mapButtons[evt['buttons']] ?? '';
 | |
|     bind.sessionSendMouse(sessionId: sessionId, msg: json.encode(modify(evt)));
 | |
|   }
 | |
| 
 | |
|   Point? handlePointerDevicePos(
 | |
|     String kind,
 | |
|     double x,
 | |
|     double y,
 | |
|     bool isMove,
 | |
|     String evtType, {
 | |
|     bool onExit = false,
 | |
|     int buttons = kPrimaryMouseButton,
 | |
|   }) {
 | |
|     final ffiModel = parent.target!.ffiModel;
 | |
|     CanvasCoords canvas =
 | |
|         CanvasCoords.fromCanvasModel(parent.target!.canvasModel);
 | |
|     Rect? rect = ffiModel.rect;
 | |
| 
 | |
|     if (isMove) {
 | |
|       if (_remoteWindowCoords.isNotEmpty &&
 | |
|           _windowRect != null &&
 | |
|           !_isInCurrentWindow(x, y)) {
 | |
|         final coords =
 | |
|             findRemoteCoords(x, y, _remoteWindowCoords, devicePixelRatio);
 | |
|         if (coords != null) {
 | |
|           isMove = false;
 | |
|           canvas = coords.canvas;
 | |
|           rect = coords.remoteRect;
 | |
|           x -= coords.relativeOffset.dx / devicePixelRatio;
 | |
|           y -= coords.relativeOffset.dy / devicePixelRatio;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     y -= CanvasModel.topToEdge;
 | |
|     x -= CanvasModel.leftToEdge;
 | |
|     if (isMove) {
 | |
|       parent.target!.canvasModel.moveDesktopMouse(x, y);
 | |
|     }
 | |
| 
 | |
|     return _handlePointerDevicePos(
 | |
|       kind,
 | |
|       x,
 | |
|       y,
 | |
|       isMove,
 | |
|       canvas,
 | |
|       rect,
 | |
|       evtType,
 | |
|       onExit: onExit,
 | |
|       buttons: buttons,
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   bool _isInCurrentWindow(double x, double y) {
 | |
|     final w = _windowRect!.width / devicePixelRatio;
 | |
|     final h = _windowRect!.width / devicePixelRatio;
 | |
|     return x >= 0 && y >= 0 && x <= w && y <= h;
 | |
|   }
 | |
| 
 | |
|   static RemoteWindowCoords? findRemoteCoords(double x, double y,
 | |
|       List<RemoteWindowCoords> remoteWindowCoords, double devicePixelRatio) {
 | |
|     x *= devicePixelRatio;
 | |
|     y *= devicePixelRatio;
 | |
|     for (final c in remoteWindowCoords) {
 | |
|       if (x >= c.relativeOffset.dx &&
 | |
|           y >= c.relativeOffset.dy &&
 | |
|           x <= c.relativeOffset.dx + c.windowRect.width &&
 | |
|           y <= c.relativeOffset.dy + c.windowRect.height) {
 | |
|         return c;
 | |
|       }
 | |
|     }
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   Point? _handlePointerDevicePos(
 | |
|     String kind,
 | |
|     double x,
 | |
|     double y,
 | |
|     bool moveInCanvas,
 | |
|     CanvasCoords canvas,
 | |
|     Rect? rect,
 | |
|     String evtType, {
 | |
|     bool onExit = false,
 | |
|     int buttons = kPrimaryMouseButton,
 | |
|   }) {
 | |
|     if (rect == null) {
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     final nearThr = 3;
 | |
|     var nearRight = (canvas.size.width - x) < nearThr;
 | |
|     var nearBottom = (canvas.size.height - y) < nearThr;
 | |
|     final imageWidth = rect.width * canvas.scale;
 | |
|     final imageHeight = rect.height * canvas.scale;
 | |
|     if (canvas.scrollStyle == ScrollStyle.scrollbar) {
 | |
|       x += imageWidth * canvas.scrollX;
 | |
|       y += imageHeight * canvas.scrollY;
 | |
| 
 | |
|       // boxed size is a center widget
 | |
|       if (canvas.size.width > imageWidth) {
 | |
|         x -= ((canvas.size.width - imageWidth) / 2);
 | |
|       }
 | |
|       if (canvas.size.height > imageHeight) {
 | |
|         y -= ((canvas.size.height - imageHeight) / 2);
 | |
|       }
 | |
|     } else {
 | |
|       x -= canvas.x;
 | |
|       y -= canvas.y;
 | |
|     }
 | |
| 
 | |
|     x /= canvas.scale;
 | |
|     y /= canvas.scale;
 | |
|     if (canvas.scale > 0 && canvas.scale < 1) {
 | |
|       final step = 1.0 / canvas.scale - 1;
 | |
|       if (nearRight) {
 | |
|         x += step;
 | |
|       }
 | |
|       if (nearBottom) {
 | |
|         y += step;
 | |
|       }
 | |
|     }
 | |
|     x += rect.left;
 | |
|     y += rect.top;
 | |
| 
 | |
|     if (onExit) {
 | |
|       final pos = setNearestEdge(x, y, rect);
 | |
|       x = pos.dx;
 | |
|       y = pos.dy;
 | |
|     }
 | |
| 
 | |
|     var evtX = 0;
 | |
|     var evtY = 0;
 | |
|     try {
 | |
|       evtX = x.round();
 | |
|       evtY = y.round();
 | |
|     } catch (e) {
 | |
|       debugPrintStack(label: 'canvas.scale value ${canvas.scale}, $e');
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     int minX = rect.left.toInt();
 | |
|     // https://github.com/rustdesk/rustdesk/issues/6678
 | |
|     // For Windows, [0,maxX], [0,maxY] should be set to enable window snapping.
 | |
|     int maxX = (rect.left + rect.width).toInt() -
 | |
|         (peerPlatform == kPeerPlatformWindows ? 0 : 1);
 | |
|     int minY = rect.top.toInt();
 | |
|     int maxY = (rect.top + rect.height).toInt() -
 | |
|         (peerPlatform == kPeerPlatformWindows ? 0 : 1);
 | |
|     evtX = trySetNearestRange(evtX, minX, maxX, 5);
 | |
|     evtY = trySetNearestRange(evtY, minY, maxY, 5);
 | |
|     if (kind == kPointerEventKindMouse) {
 | |
|       if (evtX < minX || evtY < minY || evtX > maxX || evtY > maxY) {
 | |
|         // If left mouse up, no early return.
 | |
|         if (!(buttons == kPrimaryMouseButton && evtType == 'up')) {
 | |
|           return null;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return Point(evtX, evtY);
 | |
|   }
 | |
| 
 | |
|   /// Web only
 | |
|   void listenToMouse(bool yesOrNo) {
 | |
|     if (yesOrNo) {
 | |
|       platformFFI.startDesktopWebListener();
 | |
|     } else {
 | |
|       platformFFI.stopDesktopWebListener();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void onMobileBack() => tap(MouseButtons.right);
 | |
|   void onMobileHome() => tap(MouseButtons.wheel);
 | |
|   Future<void> onMobileApps() async {
 | |
|     sendMouse('down', MouseButtons.wheel);
 | |
|     await Future.delayed(const Duration(milliseconds: 500));
 | |
|     sendMouse('up', MouseButtons.wheel);
 | |
|   }
 | |
| 
 | |
|   // Simulate a key press event.
 | |
|   // `usbHidUsage` is the USB HID usage code of the key.
 | |
|   Future<void> tapHidKey(int usbHidUsage) async {
 | |
|     inputRawKey(kKeyFlutterKey, usbHidUsage, 0, true);
 | |
|     await Future.delayed(Duration(milliseconds: 100));
 | |
|     inputRawKey(kKeyFlutterKey, usbHidUsage, 0, false);
 | |
|   }
 | |
| 
 | |
|   Future<void> onMobileVolumeUp() async =>
 | |
|       await tapHidKey(PhysicalKeyboardKey.audioVolumeUp.usbHidUsage);
 | |
|   Future<void> onMobileVolumeDown() async =>
 | |
|       await tapHidKey(PhysicalKeyboardKey.audioVolumeDown.usbHidUsage);
 | |
|   Future<void> onMobilePower() async =>
 | |
|       await tapHidKey(PhysicalKeyboardKey.power.usbHidUsage);
 | |
| }
 |