Merge pull request #1633 from Heap-Hop/refactor_input_model

Refactor input model for mobile and desktop
This commit is contained in:
RustDesk 2022-09-27 23:38:44 +08:00 committed by GitHub
commit 215f0575a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 564 additions and 693 deletions

View File

@ -19,6 +19,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'common/widgets/overlay.dart'; import 'common/widgets/overlay.dart';
import 'mobile/pages/file_manager_page.dart'; import 'mobile/pages/file_manager_page.dart';
import 'mobile/pages/remote_page.dart'; import 'mobile/pages/remote_page.dart';
import 'models/input_model.dart';
import 'models/model.dart'; import 'models/model.dart';
import 'models/platform_model.dart'; import 'models/platform_model.dart';
@ -488,12 +489,12 @@ class OverlayDialogManager {
position: Offset(left, top), position: Offset(left, top),
width: overlayW, width: overlayW,
height: overlayH, height: overlayH,
onBackPressed: () => session.tap(MouseButtons.right), onBackPressed: () => session.inputModel.tap(MouseButtons.right),
onHomePressed: () => session.tap(MouseButtons.wheel), onHomePressed: () => session.inputModel.tap(MouseButtons.wheel),
onRecentPressed: () async { onRecentPressed: () async {
session.sendMouse('down', MouseButtons.wheel); session.inputModel.sendMouse('down', MouseButtons.wheel);
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 500));
session.sendMouse('up', MouseButtons.wheel); session.inputModel.sendMouse('up', MouseButtons.wheel);
}, },
onHidePressed: () => hideMobileActionsOverlay(), onHidePressed: () => hideMobileActionsOverlay(),
); );
@ -818,8 +819,10 @@ class PermissionManager {
} }
RadioListTile<T> getRadio<T>( RadioListTile<T> getRadio<T>(
String name, T toValue, T curValue, void Function(T?) onChange) { String name, T toValue, T curValue, void Function(T?) onChange,
{EdgeInsetsGeometry? contentPadding}) {
return RadioListTile<T>( return RadioListTile<T>(
contentPadding: contentPadding,
controlAffinity: ListTileControlAffinity.trailing, controlAffinity: ListTileControlAffinity.trailing,
title: Text(translate(name)), title: Text(translate(name)),
value: toValue, value: toValue,
@ -1097,3 +1100,10 @@ void connect(BuildContext context, String id,
currentFocus.unfocus(); currentFocus.unfocus();
} }
} }
Future<Map<String, String>> getHttpHeaders() async {
return {
'Authorization':
'Bearer ${await bind.mainGetLocalOption(key: 'access_token')}'
};
}

View File

@ -1,9 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:provider/provider.dart';
import '../../desktop/widgets/tabbar_widget.dart'; import '../../desktop/widgets/tabbar_widget.dart';
import '../../mobile/pages/chat_page.dart'; import '../../mobile/pages/chat_page.dart';
import '../../models/chat_model.dart'; import '../../models/chat_model.dart';
import '../../models/model.dart';
class DraggableChatWindow extends StatelessWidget { class DraggableChatWindow extends StatelessWidget {
const DraggableChatWindow( const DraggableChatWindow(
@ -311,3 +313,48 @@ class _DraggableState extends State<Draggable> {
child: widget.builder(context, onPanUpdate)); child: widget.builder(context, onPanUpdate));
} }
} }
class QualityMonitor extends StatelessWidget {
static const textStyle = TextStyle(color: MyTheme.grayBg);
final QualityMonitorModel qualityMonitorModel;
QualityMonitor(this.qualityMonitorModel);
@override
Widget build(BuildContext context) => ChangeNotifierProvider.value(
value: qualityMonitorModel,
child: Consumer<QualityMonitorModel>(
builder: (context, qualityMonitorModel, child) => Positioned(
top: 10,
right: 10,
child: qualityMonitorModel.show
? Container(
padding: const EdgeInsets.all(8),
color: MyTheme.canvasColor.withAlpha(120),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Speed: ${qualityMonitorModel.data.speed ?? ''}",
style: textStyle,
),
Text(
"FPS: ${qualityMonitorModel.data.fps ?? ''}",
style: textStyle,
),
Text(
"Delay: ${qualityMonitorModel.data.delay ?? ''} ms",
style: textStyle,
),
Text(
"Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb",
style: textStyle,
),
Text(
"Codec: ${qualityMonitorModel.data.codecFormat ?? ''}",
style: textStyle,
),
],
),
)
: const SizedBox.shrink())));
}

View File

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../models/input_model.dart';
class RawKeyFocusScope extends StatelessWidget {
final FocusNode? focusNode;
final ValueChanged<bool>? onFocusChange;
final InputModel inputModel;
final Widget child;
RawKeyFocusScope(
{this.focusNode,
this.onFocusChange,
required this.inputModel,
required this.child});
@override
Widget build(BuildContext context) {
return FocusScope(
autofocus: true,
child: Focus(
autofocus: true,
canRequestFocus: true,
focusNode: focusNode,
onFocusChange: onFocusChange,
onKey: inputModel.handleRawKeyEvent,
child: child));
}
}
class RawPointerMouseRegion extends StatelessWidget {
final InputModel inputModel;
final Widget child;
final MouseCursor? cursor;
final PointerEnterEventListener? onEnter;
final PointerExitEventListener? onExit;
RawPointerMouseRegion(
{this.onEnter,
this.onExit,
this.cursor,
required this.inputModel,
required this.child});
@override
Widget build(BuildContext context) {
return Listener(
onPointerHover: inputModel.onPointHoverImage,
onPointerDown: inputModel.onPointDownImage,
onPointerUp: inputModel.onPointUpImage,
onPointerMove: inputModel.onPointMoveImage,
onPointerSignal: inputModel.onPointerSignalImage,
child: MouseRegion(
cursor: cursor ?? MouseCursor.defer,
onEnter: onEnter,
onExit: onExit,
child: child));
}
}

View File

@ -24,9 +24,9 @@ const Size kConnectionManagerWindowSize = Size(300, 400);
/// [kDefaultScrollAmountMultiplier] indicates how many rows can be scrolled after a minimum scroll action of mouse /// [kDefaultScrollAmountMultiplier] indicates how many rows can be scrolled after a minimum scroll action of mouse
const kDefaultScrollAmountMultiplier = 5.0; const kDefaultScrollAmountMultiplier = 5.0;
const kDefaultScrollDuration = Duration(milliseconds: 50); const kDefaultScrollDuration = Duration(milliseconds: 50);
const kDefaultMouseWhellThrottleDuration = Duration(milliseconds: 50); const kDefaultMouseWheelThrottleDuration = Duration(milliseconds: 50);
const kFullScreenEdgeSize = 0.0; const kFullScreenEdgeSize = 0.0;
const kWindowEdgeSize = 1.0; const kWindowEdgeSize = 5.0;
const kInvalidValueStr = "InvalidValueStr"; const kInvalidValueStr = "InvalidValueStr";

View File

@ -58,7 +58,7 @@ class _FileManagerPageState extends State<FileManagerPage>
void initState() { void initState() {
super.initState(); super.initState();
_ffi = FFI(); _ffi = FFI();
_ffi.connect(widget.id, isFileTransfer: true); _ffi.start(widget.id, isFileTransfer: true);
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_ffi.dialogManager _ffi.dialogManager
.showLoading(translate('Connecting...'), onCancel: closeConnection); .showLoading(translate('Connecting...'), onCancel: closeConnection);

View File

@ -47,7 +47,7 @@ class _PortForwardPageState extends State<PortForwardPage>
void initState() { void initState() {
super.initState(); super.initState();
_ffi = FFI(); _ffi = FFI();
_ffi.connect(widget.id, isPortForward: true); _ffi.start(widget.id, isPortForward: true);
Get.put(_ffi, tag: 'pf_${widget.id}'); Get.put(_ffi, tag: 'pf_${widget.id}');
if (!Platform.isLinux) { if (!Platform.isLinux) {
Wakelock.enable(); Wakelock.enable();

View File

@ -5,12 +5,13 @@ import 'dart:ui' as ui;
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/models/input_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:wakelock/wakelock.dart'; import 'package:wakelock/wakelock.dart';
import 'package:flutter_custom_cursor/flutter_custom_cursor.dart'; import 'package:flutter_custom_cursor/flutter_custom_cursor.dart';
import '../../common/widgets/overlay.dart';
import '../../common/widgets/remote_input.dart';
import '../widgets/remote_menubar.dart'; import '../widgets/remote_menubar.dart';
import '../../common.dart'; import '../../common.dart';
import '../../mobile/widgets/dialog.dart'; import '../../mobile/widgets/dialog.dart';
@ -49,12 +50,9 @@ class _RemotePageState extends State<RemotePage>
Function(bool)? _onEnterOrLeaveImage4Menubar; Function(bool)? _onEnterOrLeaveImage4Menubar;
late FFI _ffi; late FFI _ffi;
late Keyboard _keyboard_input;
late Mouse _mouse_input;
void _updateTabBarHeight() { void _updateTabBarHeight() {
_ffi.canvasModel.tabBarHeight = widget.tabBarHeight; _ffi.canvasModel.tabBarHeight = widget.tabBarHeight;
_mouse_input.tabBarHeight = widget.tabBarHeight;
} }
void _initStates(String id) { void _initStates(String id) {
@ -84,12 +82,10 @@ class _RemotePageState extends State<RemotePage>
_initStates(widget.id); _initStates(widget.id);
_ffi = FFI(); _ffi = FFI();
_keyboard_input = Keyboard(_ffi, widget.id);
_mouse_input = Mouse(_ffi, widget.id, widget.tabBarHeight);
_updateTabBarHeight(); _updateTabBarHeight();
Get.put(_ffi, tag: widget.id); Get.put(_ffi, tag: widget.id);
_ffi.connect(widget.id, tabBarHeight: super.widget.tabBarHeight); _ffi.start(widget.id, tabBarHeight: super.widget.tabBarHeight);
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
_ffi.dialogManager _ffi.dialogManager
@ -131,10 +127,6 @@ class _RemotePageState extends State<RemotePage>
_removeStates(widget.id); _removeStates(widget.id);
} }
void resetTool() {
_ffi.resetModifiers();
}
Widget buildBody(BuildContext context) { Widget buildBody(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Theme.of(context).backgroundColor, backgroundColor: Theme.of(context).backgroundColor,
@ -145,7 +137,13 @@ class _RemotePageState extends State<RemotePage>
_ffi.dialogManager.setOverlayState(Overlay.of(context)); _ffi.dialogManager.setOverlayState(Overlay.of(context));
return Container( return Container(
color: Colors.black, color: Colors.black,
child: getRawPointerAndKeyBody(getBodyForDesktop(context))); child: RawKeyFocusScope(
focusNode: _rawKeyFocusNode,
onFocusChange: (bool v) {
_imageFocused = v;
},
inputModel: _ffi.inputModel,
child: getBodyForDesktop(context)));
}) })
], ],
)); ));
@ -169,20 +167,6 @@ class _RemotePageState extends State<RemotePage>
], child: buildBody(context))); ], child: buildBody(context)));
} }
Widget getRawPointerAndKeyBody(Widget child) {
return FocusScope(
autofocus: true,
child: Focus(
autofocus: true,
canRequestFocus: true,
focusNode: _rawKeyFocusNode,
onFocusChange: (bool v) {
_imageFocused = v;
},
onKey: _keyboard_input.handleRawKeyEvent,
child: child));
}
void enterView(PointerEnterEvent evt) { void enterView(PointerEnterEvent evt) {
if (!_imageFocused) { if (!_imageFocused) {
_rawKeyFocusNode.requestFocus(); _rawKeyFocusNode.requestFocus();
@ -195,7 +179,7 @@ class _RemotePageState extends State<RemotePage>
// //
} }
} }
_ffi.enterOrLeave(true); _ffi.inputModel.enterOrLeave(true);
} }
void leaveView(PointerExitEvent evt) { void leaveView(PointerExitEvent evt) {
@ -207,18 +191,7 @@ class _RemotePageState extends State<RemotePage>
// //
} }
} }
_ffi.enterOrLeave(false); _ffi.inputModel.enterOrLeave(false);
}
Widget _buildImageListener(Widget child) {
return Listener(
onPointerHover: _mouse_input.onPointHoverImage,
onPointerDown: _mouse_input.onPointDownImage,
onPointerUp: _mouse_input.onPointUpImage,
onPointerMove: _mouse_input.onPointMoveImage,
onPointerSignal: _mouse_input.onPointerSignalImage,
child:
MouseRegion(onEnter: enterView, onExit: leaveView, child: child));
} }
Widget getBodyForDesktop(BuildContext context) { Widget getBodyForDesktop(BuildContext context) {
@ -236,7 +209,12 @@ class _RemotePageState extends State<RemotePage>
cursorOverImage: _cursorOverImage, cursorOverImage: _cursorOverImage,
keyboardEnabled: _keyboardEnabled, keyboardEnabled: _keyboardEnabled,
remoteCursorMoved: _remoteCursorMoved, remoteCursorMoved: _remoteCursorMoved,
listenerBuilder: _buildImageListener, listenerBuilder: (child) => RawPointerMouseRegion(
onEnter: enterView,
onExit: leaveView,
inputModel: _ffi.inputModel,
child: child,
),
); );
})) }))
]; ];
@ -441,48 +419,3 @@ class ImagePainter extends CustomPainter {
return oldDelegate != this; return oldDelegate != this;
} }
} }
class QualityMonitor extends StatelessWidget {
static const textStyle = TextStyle(color: MyTheme.grayBg);
final QualityMonitorModel qualityMonitorModel;
QualityMonitor(this.qualityMonitorModel);
@override
Widget build(BuildContext context) => ChangeNotifierProvider.value(
value: qualityMonitorModel,
child: Consumer<QualityMonitorModel>(
builder: (context, qualityMonitorModel, child) => Positioned(
top: 10,
right: 10,
child: qualityMonitorModel.show
? Container(
padding: const EdgeInsets.all(8),
color: MyTheme.canvasColor.withAlpha(120),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Speed: ${qualityMonitorModel.data.speed ?? ''}",
style: textStyle,
),
Text(
"FPS: ${qualityMonitorModel.data.fps ?? ''}",
style: textStyle,
),
Text(
"Delay: ${qualityMonitorModel.data.delay ?? ''} ms",
style: textStyle,
),
Text(
"Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb",
style: textStyle,
),
Text(
"Codec: ${qualityMonitorModel.data.codecFormat ?? ''}",
style: textStyle,
),
],
),
)
: const SizedBox.shrink())));
}

View File

@ -18,7 +18,7 @@ class DesktopScrollWrapper extends StatelessWidget {
scrollDuration: kDefaultScrollDuration, scrollDuration: kDefaultScrollDuration,
scrollCurve: Curves.linearToEaseOut, scrollCurve: Curves.linearToEaseOut,
mouseWheelTurnsThrottleTimeMs: mouseWheelTurnsThrottleTimeMs:
kDefaultMouseWhellThrottleDuration.inMilliseconds, kDefaultMouseWheelThrottleDuration.inMilliseconds,
scrollAmountMultiplier: kDefaultScrollAmountMultiplier), scrollAmountMultiplier: kDefaultScrollAmountMultiplier),
child: child, child: child,
); );

View File

@ -26,7 +26,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
gFFI.connect(widget.id, isFileTransfer: true); gFFI.start(widget.id, isFileTransfer: true);
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
gFFI.dialogManager gFFI.dialogManager
.showLoading(translate('Connecting...'), onCancel: closeConnection); .showLoading(translate('Connecting...'), onCancel: closeConnection);

View File

@ -2,16 +2,18 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/mobile/widgets/gesture_help.dart'; import 'package:flutter_hbb/mobile/widgets/gesture_help.dart';
import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/chat_model.dart';
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:wakelock/wakelock.dart'; import 'package:wakelock/wakelock.dart';
import '../../common.dart'; import '../../common.dart';
import '../../consts.dart'; import '../../common/widgets/overlay.dart';
import '../../common/widgets/remote_input.dart';
import '../../models/input_model.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
import '../widgets/dialog.dart'; import '../widgets/dialog.dart';
@ -25,7 +27,7 @@ class RemotePage extends StatefulWidget {
final String id; final String id;
@override @override
_RemotePageState createState() => _RemotePageState(); State<RemotePage> createState() => _RemotePageState();
} }
class _RemotePageState extends State<RemotePage> { class _RemotePageState extends State<RemotePage> {
@ -43,12 +45,13 @@ class _RemotePageState extends State<RemotePage> {
final FocusNode _mobileFocusNode = FocusNode(); final FocusNode _mobileFocusNode = FocusNode();
final FocusNode _physicalFocusNode = FocusNode(); final FocusNode _physicalFocusNode = FocusNode();
var _showEdit = false; // use soft keyboard var _showEdit = false; // use soft keyboard
var _isPhysicalMouse = false;
InputModel get inputModel => gFFI.inputModel;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
gFFI.connect(widget.id); gFFI.start(widget.id);
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
gFFI.dialogManager gFFI.dialogManager
@ -59,14 +62,14 @@ class _RemotePageState extends State<RemotePage> {
Wakelock.enable(); Wakelock.enable();
_physicalFocusNode.requestFocus(); _physicalFocusNode.requestFocus();
gFFI.ffiModel.updateEventListener(widget.id); gFFI.ffiModel.updateEventListener(widget.id);
gFFI.listenToMouse(true); gFFI.inputModel.listenToMouse(true);
gFFI.qualityMonitorModel.checkShowQualityMonitor(widget.id); gFFI.qualityMonitorModel.checkShowQualityMonitor(widget.id);
} }
@override @override
void dispose() { void dispose() {
gFFI.dialogManager.hideMobileActionsOverlay(); gFFI.dialogManager.hideMobileActionsOverlay();
gFFI.listenToMouse(false); gFFI.inputModel.listenToMouse(false);
gFFI.invokeMethod("enable_soft_keyboard", true); gFFI.invokeMethod("enable_soft_keyboard", true);
_mobileFocusNode.dispose(); _mobileFocusNode.dispose();
_physicalFocusNode.dispose(); _physicalFocusNode.dispose();
@ -81,7 +84,7 @@ class _RemotePageState extends State<RemotePage> {
} }
void resetTool() { void resetTool() {
gFFI.resetModifiers(); inputModel.resetModifiers();
} }
bool isKeyboardShown() { bool isKeyboardShown() {
@ -115,7 +118,7 @@ class _RemotePageState extends State<RemotePage> {
} }
// handle mobile virtual keyboard // handle mobile virtual keyboard
void handleInput(String newValue) { void handleSoftKeyboardInput(String newValue) {
var oldValue = _value; var oldValue = _value;
_value = newValue; _value = newValue;
if (isIOS) { if (isIOS) {
@ -133,7 +136,7 @@ class _RemotePageState extends State<RemotePage> {
newValue[common] == oldValue[common]; newValue[common] == oldValue[common];
++common) {} ++common) {}
for (i = 0; i < oldValue.length - common; ++i) { for (i = 0; i < oldValue.length - common; ++i) {
gFFI.inputKey('VK_BACK'); inputModel.inputKey('VK_BACK');
} }
if (newValue.length > common) { if (newValue.length > common) {
var s = newValue.substring(common); var s = newValue.substring(common);
@ -145,8 +148,8 @@ class _RemotePageState extends State<RemotePage> {
} }
return; return;
} }
if (oldValue.length > 0 && if (oldValue.isNotEmpty &&
newValue.length > 0 && newValue.isNotEmpty &&
oldValue[0] == '\1' && oldValue[0] == '\1' &&
newValue[0] != '\1') { newValue[0] != '\1') {
// clipboard // clipboard
@ -156,7 +159,7 @@ class _RemotePageState extends State<RemotePage> {
// ? // ?
} else if (newValue.length < oldValue.length) { } else if (newValue.length < oldValue.length) {
final char = 'VK_BACK'; final char = 'VK_BACK';
gFFI.inputKey(char); inputModel.inputKey(char);
} else { } else {
final content = newValue.substring(oldValue.length); final content = newValue.substring(oldValue.length);
if (content.length > 1) { if (content.length > 1) {
@ -189,7 +192,7 @@ class _RemotePageState extends State<RemotePage> {
} else if (char == ' ') { } else if (char == ' ') {
char = 'VK_SPACE'; char = 'VK_SPACE';
} }
gFFI.inputKey(char); inputModel.inputKey(char);
} }
void openKeyboard() { void openKeyboard() {
@ -211,14 +214,6 @@ class _RemotePageState extends State<RemotePage> {
}); });
} }
void sendRawKey(RawKeyEvent e, {bool? down, bool? press}) {
// for maximum compatibility
final label = logicalKeyMap[e.logicalKey.keyId] ??
physicalKeyMap[e.physicalKey.usbHidUsage] ??
e.logicalKey.keyLabel;
gFFI.inputKey(label, down: down, press: press ?? false);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final pi = Provider.of<FfiModel>(context).pi; final pi = Provider.of<FfiModel>(context).pi;
@ -231,159 +226,68 @@ class _RemotePageState extends State<RemotePage> {
clientClose(gFFI.dialogManager); clientClose(gFFI.dialogManager);
return false; return false;
}, },
child: getRawPointerAndKeyBody( child: getRawPointerAndKeyBody(Scaffold(
keyboard, // resizeToAvoidBottomInset: true,
Scaffold( floatingActionButton: !showActionButton
// resizeToAvoidBottomInset: true, ? null
floatingActionButton: !showActionButton : FloatingActionButton(
? null mini: !hideKeyboard,
: FloatingActionButton( child: Icon(
mini: !hideKeyboard, hideKeyboard ? Icons.expand_more : Icons.expand_less),
child: Icon( backgroundColor: MyTheme.accent,
hideKeyboard ? Icons.expand_more : Icons.expand_less), onPressed: () {
backgroundColor: MyTheme.accent, setState(() {
onPressed: () { if (hideKeyboard) {
setState(() { _showEdit = false;
if (hideKeyboard) { gFFI.invokeMethod("enable_soft_keyboard", false);
_showEdit = false; _mobileFocusNode.unfocus();
gFFI.invokeMethod("enable_soft_keyboard", false); _physicalFocusNode.requestFocus();
_mobileFocusNode.unfocus(); } else {
_physicalFocusNode.requestFocus(); _showBar = !_showBar;
} else { }
_showBar = !_showBar; });
} }),
}); bottomNavigationBar: _showBar && pi.displays.isNotEmpty
}), ? getBottomAppBar(keyboard)
bottomNavigationBar: _showBar && pi.displays.length > 0 : null,
? getBottomAppBar(keyboard) body: Overlay(
: null, initialEntries: [
body: Overlay( OverlayEntry(builder: (context) {
initialEntries: [ return Container(
OverlayEntry(builder: (context) { color: Colors.black,
return Container( child: isWebDesktop
color: Colors.black, ? getBodyForDesktopWithListener(keyboard)
child: isWebDesktop : SafeArea(child:
? getBodyForDesktopWithListener(keyboard) OrientationBuilder(builder: (ctx, orientation) {
: SafeArea(child: if (_currentOrientation != orientation) {
OrientationBuilder(builder: (ctx, orientation) { Timer(const Duration(milliseconds: 200), () {
if (_currentOrientation != orientation) { gFFI.dialogManager
Timer(const Duration(milliseconds: 200), () { .resetMobileActionsOverlay(ffi: gFFI);
gFFI.dialogManager _currentOrientation = orientation;
.resetMobileActionsOverlay(ffi: gFFI); gFFI.canvasModel.updateViewStyle();
_currentOrientation = orientation; });
gFFI.canvasModel.updateViewStyle(); }
}); return Obx(() => Container(
} color: MyTheme.canvasColor,
return Container( child: inputModel.isPhysicalMouse.value
color: MyTheme.canvasColor, ? getBodyForMobile()
child: _isPhysicalMouse : getBodyForMobileWithGesture()));
? getBodyForMobile() })));
: getBodyForMobileWithGesture()); })
}))); ],
}) ))),
],
))),
); );
} }
Widget getRawPointerAndKeyBody(bool keyboard, Widget child) { Widget getRawPointerAndKeyBody(Widget child) {
return Listener( final keyboard = gFFI.ffiModel.permissions['keyboard'] != false;
onPointerHover: (e) { return RawPointerMouseRegion(
if (e.kind != ui.PointerDeviceKind.mouse) return; cursor: keyboard ? SystemMouseCursors.none : MouseCursor.defer,
if (!_isPhysicalMouse) { inputModel: inputModel,
setState(() { child: RawKeyFocusScope(
_isPhysicalMouse = true; focusNode: _physicalFocusNode,
}); inputModel: inputModel,
} child: child));
if (_isPhysicalMouse) {
gFFI.handleMouse(getEvent(e, 'mousemove'));
}
},
onPointerDown: (e) {
if (e.kind != ui.PointerDeviceKind.mouse) {
if (_isPhysicalMouse) {
setState(() {
_isPhysicalMouse = false;
});
}
}
if (_isPhysicalMouse) {
gFFI.handleMouse(getEvent(e, 'mousedown'));
}
},
onPointerUp: (e) {
if (e.kind != ui.PointerDeviceKind.mouse) return;
if (_isPhysicalMouse) {
gFFI.handleMouse(getEvent(e, 'mouseup'));
}
},
onPointerMove: (e) {
if (e.kind != ui.PointerDeviceKind.mouse) return;
if (_isPhysicalMouse) {
gFFI.handleMouse(getEvent(e, 'mousemove'));
}
},
onPointerSignal: (e) {
if (e is PointerScrollEvent) {
var dy = 0;
if (e.scrollDelta.dy > 0) {
dy = -1;
} else if (e.scrollDelta.dy < 0) {
dy = 1;
}
gFFI.scroll(dy);
}
},
child: MouseRegion(
cursor: keyboard ? SystemMouseCursors.none : MouseCursor.defer,
child: FocusScope(
autofocus: true,
child: Focus(
autofocus: true,
canRequestFocus: true,
focusNode: _physicalFocusNode,
onKey: (data, e) {
final key = e.logicalKey;
if (e is RawKeyDownEvent) {
if (e.repeat &&
!e.isAltPressed &&
!e.isControlPressed &&
!e.isShiftPressed &&
!e.isMetaPressed) {
sendRawKey(e, press: true);
} else {
sendRawKey(e, down: true);
if (e.isAltPressed && !gFFI.alt) {
gFFI.alt = true;
} else if (e.isControlPressed && !gFFI.ctrl) {
gFFI.ctrl = true;
} else if (e.isShiftPressed && !gFFI.shift) {
gFFI.shift = true;
} else if (e.isMetaPressed && !gFFI.command) {
gFFI.command = true;
}
}
}
// [!_showEdit] workaround for soft-keyboard's control_key like Backspace / Enter
if (!_showEdit && e is RawKeyUpEvent) {
if (key == LogicalKeyboardKey.altLeft ||
key == LogicalKeyboardKey.altRight) {
gFFI.alt = false;
} else if (key == LogicalKeyboardKey.controlLeft ||
key == LogicalKeyboardKey.controlRight) {
gFFI.ctrl = false;
} else if (key == LogicalKeyboardKey.shiftRight ||
key == LogicalKeyboardKey.shiftLeft) {
gFFI.shift = false;
} else if (key == LogicalKeyboardKey.metaLeft ||
key == LogicalKeyboardKey.metaRight) {
gFFI.command = false;
}
sendRawKey(e);
}
return KeyEventResult.handled;
},
child: child))));
} }
Widget getBottomAppBar(bool keyboard) { Widget getBottomAppBar(bool keyboard) {
@ -489,10 +393,10 @@ class _RemotePageState extends State<RemotePage> {
child: getBodyForMobile(), child: getBodyForMobile(),
onTapUp: (d) { onTapUp: (d) {
if (touchMode) { if (touchMode) {
gFFI.cursorModel.touch( gFFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
d.localPosition.dx, d.localPosition.dy, MouseButtons.left); inputModel.tap(MouseButtons.left);
} else { } else {
gFFI.tap(MouseButtons.left); inputModel.tap(MouseButtons.left);
} }
}, },
onDoubleTapDown: (d) { onDoubleTapDown: (d) {
@ -501,8 +405,8 @@ class _RemotePageState extends State<RemotePage> {
} }
}, },
onDoubleTap: () { onDoubleTap: () {
gFFI.tap(MouseButtons.left); inputModel.tap(MouseButtons.left);
gFFI.tap(MouseButtons.left); inputModel.tap(MouseButtons.left);
}, },
onLongPressDown: (d) { onLongPressDown: (d) {
if (touchMode) { if (touchMode) {
@ -515,16 +419,16 @@ class _RemotePageState extends State<RemotePage> {
gFFI.cursorModel gFFI.cursorModel
.move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy); .move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy);
} }
gFFI.tap(MouseButtons.right); inputModel.tap(MouseButtons.right);
}, },
onDoubleFinerTap: (d) { onDoubleFinerTap: (d) {
if (!touchMode) { if (!touchMode) {
gFFI.tap(MouseButtons.right); inputModel.tap(MouseButtons.right);
} }
}, },
onHoldDragStart: (d) { onHoldDragStart: (d) {
if (!touchMode) { if (!touchMode) {
gFFI.sendMouse('down', MouseButtons.left); inputModel.sendMouse('down', MouseButtons.left);
} }
}, },
onHoldDragUpdate: (d) { onHoldDragUpdate: (d) {
@ -534,13 +438,13 @@ class _RemotePageState extends State<RemotePage> {
}, },
onHoldDragEnd: (_) { onHoldDragEnd: (_) {
if (!touchMode) { if (!touchMode) {
gFFI.sendMouse('up', MouseButtons.left); inputModel.sendMouse('up', MouseButtons.left);
} }
}, },
onOneFingerPanStart: (d) { onOneFingerPanStart: (d) {
if (touchMode) { if (touchMode) {
gFFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy); gFFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
gFFI.sendMouse('down', MouseButtons.left); inputModel.sendMouse('down', MouseButtons.left);
} else { } else {
final cursorX = gFFI.cursorModel.x; final cursorX = gFFI.cursorModel.x;
final cursorY = gFFI.cursorModel.y; final cursorY = gFFI.cursorModel.y;
@ -557,7 +461,7 @@ class _RemotePageState extends State<RemotePage> {
}, },
onOneFingerPanEnd: (d) { onOneFingerPanEnd: (d) {
if (touchMode) { if (touchMode) {
gFFI.sendMouse('up', MouseButtons.left); inputModel.sendMouse('up', MouseButtons.left);
} }
}, },
// scale + pan event // scale + pan event
@ -576,10 +480,10 @@ class _RemotePageState extends State<RemotePage> {
: (d) { : (d) {
_mouseScrollIntegral += d.delta.dy / 4; _mouseScrollIntegral += d.delta.dy / 4;
if (_mouseScrollIntegral > 1) { if (_mouseScrollIntegral > 1) {
gFFI.scroll(1); inputModel.scroll(1);
_mouseScrollIntegral = 0; _mouseScrollIntegral = 0;
} else if (_mouseScrollIntegral < -1) { } else if (_mouseScrollIntegral < -1) {
gFFI.scroll(-1); inputModel.scroll(-1);
_mouseScrollIntegral = 0; _mouseScrollIntegral = 0;
} }
}); });
@ -591,7 +495,7 @@ class _RemotePageState extends State<RemotePage> {
child: Stack(children: [ child: Stack(children: [
ImagePaint(), ImagePaint(),
CursorPaint(), CursorPaint(),
QualityMonitor(), QualityMonitor(gFFI.qualityMonitorModel),
getHelpTools(), getHelpTools(),
SizedBox( SizedBox(
width: 0, width: 0,
@ -608,7 +512,7 @@ class _RemotePageState extends State<RemotePage> {
initialValue: _value, initialValue: _value,
// trick way to make backspace work always // trick way to make backspace work always
keyboardType: TextInputType.multiline, keyboardType: TextInputType.multiline,
onChanged: handleInput, onChanged: handleSoftKeyboardInput,
), ),
), ),
])); ]));
@ -625,27 +529,6 @@ class _RemotePageState extends State<RemotePage> {
color: MyTheme.canvasColor, child: Stack(children: paints)); color: MyTheme.canvasColor, child: Stack(children: paints));
} }
int lastMouseDownButtons = 0;
Map<String, dynamic> getEvent(PointerEvent evt, String type) {
final Map<String, dynamic> out = {};
out['type'] = type;
out['x'] = evt.position.dx;
out['y'] = evt.position.dy;
if (gFFI.alt) out['alt'] = 'true';
if (gFFI.shift) out['shift'] = 'true';
if (gFFI.ctrl) out['ctrl'] = 'true';
if (gFFI.command) out['command'] = 'true';
out['buttons'] = evt
.buttons; // left button: 1, right button: 2, middle button: 4, 1 | 2 = 3 (left + right)
if (evt.buttons != 0) {
lastMouseDownButtons = evt.buttons;
} else {
out['buttons'] = lastMouseDownButtons;
}
return out;
}
void showActions(String id) async { void showActions(String id) async {
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
final x = 120.0; final x = 120.0;
@ -679,9 +562,12 @@ class _RemotePageState extends State<RemotePage> {
child: Text(translate('Reset canvas')), value: 'reset_canvas')); child: Text(translate('Reset canvas')), value: 'reset_canvas'));
} }
if (perms['keyboard'] != false) { if (perms['keyboard'] != false) {
more.add(PopupMenuItem<String>(
child: Text(translate('Physical Keyboard Input Mode')),
value: 'input-mode'));
if (pi.platform == 'Linux' || pi.sasEnabled) { if (pi.platform == 'Linux' || pi.sasEnabled) {
more.add(PopupMenuItem<String>( more.add(PopupMenuItem<String>(
child: Text(translate('Insert') + ' Ctrl + Alt + Del'), child: Text('${translate('Insert')} Ctrl + Alt + Del'),
value: 'cad')); value: 'cad'));
} }
more.add(PopupMenuItem<String>( more.add(PopupMenuItem<String>(
@ -690,8 +576,8 @@ class _RemotePageState extends State<RemotePage> {
await bind.sessionGetToggleOption(id: id, arg: 'privacy-mode') != await bind.sessionGetToggleOption(id: id, arg: 'privacy-mode') !=
true) { true) {
more.add(PopupMenuItem<String>( more.add(PopupMenuItem<String>(
child: Text(translate((gFFI.ffiModel.inputBlocked ? 'Unb' : 'B') + child: Text(translate(
'lock user input')), '${gFFI.ffiModel.inputBlocked ? 'Unb' : 'B'}lock user input')),
value: 'block-input')); value: 'block-input'));
} }
} }
@ -711,12 +597,14 @@ class _RemotePageState extends State<RemotePage> {
); );
if (value == 'cad') { if (value == 'cad') {
bind.sessionCtrlAltDel(id: widget.id); bind.sessionCtrlAltDel(id: widget.id);
} else if (value == 'input-mode') {
changePhysicalKeyboardInputMode();
} else if (value == 'lock') { } else if (value == 'lock') {
bind.sessionLockScreen(id: widget.id); bind.sessionLockScreen(id: widget.id);
} else if (value == 'block-input') { } else if (value == 'block-input') {
bind.sessionToggleOption( bind.sessionToggleOption(
id: widget.id, id: widget.id,
value: (gFFI.ffiModel.inputBlocked ? 'un' : '') + 'block-input'); value: '${gFFI.ffiModel.inputBlocked ? 'un' : ''}block-input');
gFFI.ffiModel.inputBlocked = !gFFI.ffiModel.inputBlocked; gFFI.ffiModel.inputBlocked = !gFFI.ffiModel.inputBlocked;
} else if (value == 'refresh') { } else if (value == 'refresh') {
bind.sessionRefresh(id: widget.id); bind.sessionRefresh(id: widget.id);
@ -770,13 +658,33 @@ class _RemotePageState extends State<RemotePage> {
})); }));
} }
void changePhysicalKeyboardInputMode() async {
var current = await bind.sessionGetKeyboardName(id: widget.id);
gFFI.dialogManager.show((setState, close) {
void setMode(String? v) async {
await bind.sessionSetKeyboardMode(id: widget.id, keyboardMode: v ?? '');
setState(() => current = v ?? '');
Future.delayed(Duration(milliseconds: 300), close);
}
return CustomAlertDialog(
title: Text(translate('Physical Keyboard Input Mode')),
content: Column(mainAxisSize: MainAxisSize.min, children: [
getRadio('Legacy mode', 'legacy', current, setMode,
contentPadding: EdgeInsets.zero),
getRadio('Map mode', 'map', current, setMode,
contentPadding: EdgeInsets.zero),
]));
}, clickMaskDismiss: true);
}
Widget getHelpTools() { Widget getHelpTools() {
final keyboard = isKeyboardShown(); final keyboard = isKeyboardShown();
if (!keyboard) { if (!keyboard) {
return SizedBox(); return SizedBox();
} }
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
var wrap = (String text, void Function() onPressed, wrap(String text, void Function() onPressed,
[bool? active, IconData? icon]) { [bool? active, IconData? icon]) {
return TextButton( return TextButton(
style: TextButton.styleFrom( style: TextButton.styleFrom(
@ -795,22 +703,23 @@ class _RemotePageState extends State<RemotePage> {
: Text(translate(text), : Text(translate(text),
style: TextStyle(color: Colors.white, fontSize: 11)), style: TextStyle(color: Colors.white, fontSize: 11)),
onPressed: onPressed); onPressed: onPressed);
}; }
final pi = gFFI.ffiModel.pi; final pi = gFFI.ffiModel.pi;
final isMac = pi.platform == "Mac OS"; final isMac = pi.platform == "Mac OS";
final modifiers = <Widget>[ final modifiers = <Widget>[
wrap('Ctrl ', () { wrap('Ctrl ', () {
setState(() => gFFI.ctrl = !gFFI.ctrl); setState(() => inputModel.ctrl = !inputModel.ctrl);
}, gFFI.ctrl), }, inputModel.ctrl),
wrap(' Alt ', () { wrap(' Alt ', () {
setState(() => gFFI.alt = !gFFI.alt); setState(() => inputModel.alt = !inputModel.alt);
}, gFFI.alt), }, inputModel.alt),
wrap('Shift', () { wrap('Shift', () {
setState(() => gFFI.shift = !gFFI.shift); setState(() => inputModel.shift = !inputModel.shift);
}, gFFI.shift), }, inputModel.shift),
wrap(isMac ? ' Cmd ' : ' Win ', () { wrap(isMac ? ' Cmd ' : ' Win ', () {
setState(() => gFFI.command = !gFFI.command); setState(() => inputModel.command = !inputModel.command);
}, gFFI.command), }, inputModel.command),
]; ];
final keys = <Widget>[ final keys = <Widget>[
wrap( wrap(
@ -840,46 +749,46 @@ class _RemotePageState extends State<RemotePage> {
SizedBox(width: 9999), SizedBox(width: 9999),
]; ];
for (var i = 1; i <= 12; ++i) { for (var i = 1; i <= 12; ++i) {
final name = 'F' + i.toString(); final name = 'F$i';
fn.add(wrap(name, () { fn.add(wrap(name, () {
gFFI.inputKey('VK_' + name); inputModel.inputKey('VK_$name');
})); }));
} }
final more = <Widget>[ final more = <Widget>[
SizedBox(width: 9999), SizedBox(width: 9999),
wrap('Esc', () { wrap('Esc', () {
gFFI.inputKey('VK_ESCAPE'); inputModel.inputKey('VK_ESCAPE');
}), }),
wrap('Tab', () { wrap('Tab', () {
gFFI.inputKey('VK_TAB'); inputModel.inputKey('VK_TAB');
}), }),
wrap('Home', () { wrap('Home', () {
gFFI.inputKey('VK_HOME'); inputModel.inputKey('VK_HOME');
}), }),
wrap('End', () { wrap('End', () {
gFFI.inputKey('VK_END'); inputModel.inputKey('VK_END');
}), }),
wrap('Del', () { wrap('Del', () {
gFFI.inputKey('VK_DELETE'); inputModel.inputKey('VK_DELETE');
}), }),
wrap('PgUp', () { wrap('PgUp', () {
gFFI.inputKey('VK_PRIOR'); inputModel.inputKey('VK_PRIOR');
}), }),
wrap('PgDn', () { wrap('PgDn', () {
gFFI.inputKey('VK_NEXT'); inputModel.inputKey('VK_NEXT');
}), }),
SizedBox(width: 9999), SizedBox(width: 9999),
wrap('', () { wrap('', () {
gFFI.inputKey('VK_LEFT'); inputModel.inputKey('VK_LEFT');
}, false, Icons.keyboard_arrow_left), }, false, Icons.keyboard_arrow_left),
wrap('', () { wrap('', () {
gFFI.inputKey('VK_UP'); inputModel.inputKey('VK_UP');
}, false, Icons.keyboard_arrow_up), }, false, Icons.keyboard_arrow_up),
wrap('', () { wrap('', () {
gFFI.inputKey('VK_DOWN'); inputModel.inputKey('VK_DOWN');
}, false, Icons.keyboard_arrow_down), }, false, Icons.keyboard_arrow_down),
wrap('', () { wrap('', () {
gFFI.inputKey('VK_RIGHT'); inputModel.inputKey('VK_RIGHT');
}, false, Icons.keyboard_arrow_right), }, false, Icons.keyboard_arrow_right),
wrap(isMac ? 'Cmd+C' : 'Ctrl+C', () { wrap(isMac ? 'Cmd+C' : 'Ctrl+C', () {
sendPrompt(isMac, 'VK_C'); sendPrompt(isMac, 'VK_C');
@ -915,7 +824,7 @@ class ImagePaint extends StatelessWidget {
final adjust = gFFI.cursorModel.adjustForKeyboard(); final adjust = gFFI.cursorModel.adjustForKeyboard();
var s = c.scale; var s = c.scale;
return CustomPaint( return CustomPaint(
painter: new ImagePainter( painter: ImagePainter(
image: m.image, x: c.x / s, y: (c.y - adjust) / s, scale: s), image: m.image, x: c.x / s, y: (c.y - adjust) / s, scale: s),
); );
} }
@ -929,7 +838,7 @@ class CursorPaint extends StatelessWidget {
final adjust = gFFI.cursorModel.adjustForKeyboard(); final adjust = gFFI.cursorModel.adjustForKeyboard();
var s = c.scale; var s = c.scale;
return CustomPaint( return CustomPaint(
painter: new ImagePainter( painter: ImagePainter(
image: m.image, image: m.image,
x: m.x * s - m.hotx + c.x, x: m.x * s - m.hotx + c.x,
y: m.y * s - m.hoty + c.y - adjust, y: m.y * s - m.hoty + c.y - adjust,
@ -955,7 +864,7 @@ class ImagePainter extends CustomPainter {
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
if (image == null) return; if (image == null) return;
canvas.scale(scale, scale); canvas.scale(scale, scale);
canvas.drawImage(image!, new Offset(x, y), new Paint()); canvas.drawImage(image!, Offset(x, y), Paint());
} }
@override @override
@ -964,49 +873,6 @@ class ImagePainter extends CustomPainter {
} }
} }
// TODO global widget
class QualityMonitor extends StatelessWidget {
static final textColor = Colors.grey.shade200;
@override
Widget build(BuildContext context) => ChangeNotifierProvider.value(
value: gFFI.qualityMonitorModel,
child: Consumer<QualityMonitorModel>(
builder: (context, qualityMonitorModel, child) => Positioned(
top: 10,
right: 10,
child: qualityMonitorModel.show
? Container(
padding: EdgeInsets.all(8),
color: MyTheme.canvasColor.withAlpha(120),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Speed: ${qualityMonitorModel.data.speed ?? ''}",
style: TextStyle(color: textColor),
),
Text(
"FPS: ${qualityMonitorModel.data.fps ?? ''}",
style: TextStyle(color: textColor),
),
Text(
"Delay: ${qualityMonitorModel.data.delay ?? ''} ms",
style: TextStyle(color: textColor),
),
Text(
"Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb",
style: TextStyle(color: textColor),
),
Text(
"Codec: ${qualityMonitorModel.data.codecFormat ?? ''}",
style: TextStyle(color: textColor),
),
],
),
)
: SizedBox.shrink())));
}
void showOptions(String id, OverlayDialogManager dialogManager) async { void showOptions(String id, OverlayDialogManager dialogManager) async {
String quality = await bind.sessionGetImageQuality(id: id) ?? 'balanced'; String quality = await bind.sessionGetImageQuality(id: id) ?? 'balanced';
if (quality == '') quality = 'balanced'; if (quality == '') quality = 'balanced';
@ -1202,16 +1068,16 @@ void showSetOSPassword(
} }
void sendPrompt(bool isMac, String key) { void sendPrompt(bool isMac, String key) {
final old = isMac ? gFFI.command : gFFI.ctrl; final old = isMac ? gFFI.inputModel.command : gFFI.inputModel.ctrl;
if (isMac) { if (isMac) {
gFFI.command = true; gFFI.inputModel.command = true;
} else { } else {
gFFI.ctrl = true; gFFI.inputModel.ctrl = true;
} }
gFFI.inputKey(key); gFFI.inputModel.inputKey(key);
if (isMac) { if (isMac) {
gFFI.command = old; gFFI.inputModel.command = old;
} else { } else {
gFFI.ctrl = old; gFFI.inputModel.ctrl = old;
} }
} }

View File

@ -6,6 +6,8 @@ import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import '../common.dart';
class AbModel with ChangeNotifier { class AbModel with ChangeNotifier {
var abLoading = false; var abLoading = false;
var abError = ""; var abError = "";
@ -27,7 +29,7 @@ class AbModel with ChangeNotifier {
final api = "${await getApiServer()}/api/ab/get"; final api = "${await getApiServer()}/api/ab/get";
try { try {
final resp = final resp =
await http.post(Uri.parse(api), headers: await _getHeaders()); await http.post(Uri.parse(api), headers: await getHttpHeaders());
if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") { if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") {
Map<String, dynamic> json = jsonDecode(resp.body); Map<String, dynamic> json = jsonDecode(resp.body);
if (json.containsKey('error')) { if (json.containsKey('error')) {
@ -61,10 +63,6 @@ class AbModel with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
Future<Map<String, String>>? _getHeaders() {
return _ffi?.getHttpHeaders();
}
void addId(String id) async { void addId(String id) async {
if (idContainBy(id)) { if (idContainBy(id)) {
return; return;
@ -93,7 +91,7 @@ class AbModel with ChangeNotifier {
abLoading = true; abLoading = true;
notifyListeners(); notifyListeners();
final api = "${await getApiServer()}/api/ab"; final api = "${await getApiServer()}/api/ab";
var authHeaders = await _getHeaders() ?? Map<String, String>(); var authHeaders = await getHttpHeaders();
authHeaders['Content-Type'] = "application/json"; authHeaders['Content-Type'] = "application/json";
final body = jsonEncode({ final body = jsonEncode({
"data": jsonEncode({"tags": tags, "peers": peers}) "data": jsonEncode({"tags": tags, "peers": peers})

View File

@ -50,10 +50,9 @@ class ChatModel with ChangeNotifier {
bool get isShowChatPage => _isShowChatPage; bool get isShowChatPage => _isShowChatPage;
WeakReference<FFI> _ffi; final WeakReference<FFI> parent;
/// Constructor ChatModel(this.parent);
ChatModel(this._ffi);
ChatUser get currentUser { ChatUser get currentUser {
final user = messages[currentID]?.chatUser; final user = messages[currentID]?.chatUser;
@ -182,7 +181,7 @@ class ChatModel with ChangeNotifier {
_currentID = id; _currentID = id;
notifyListeners(); notifyListeners();
} else { } else {
final client = _ffi.target?.serverModel.clients final client = parent.target?.serverModel.clients
.firstWhere((client) => client.id == id); .firstWhere((client) => client.id == id);
if (client == null) { if (client == null) {
return debugPrint( return debugPrint(
@ -208,23 +207,23 @@ class ChatModel with ChangeNotifier {
if (!_isShowChatPage) { if (!_isShowChatPage) {
toggleCMChatPage(id); toggleCMChatPage(id);
} }
_ffi.target?.serverModel.jumpTo(id); parent.target?.serverModel.jumpTo(id);
late final chatUser; late final chatUser;
if (id == clientModeID) { if (id == clientModeID) {
chatUser = ChatUser( chatUser = ChatUser(
firstName: _ffi.target?.ffiModel.pi.username, firstName: parent.target?.ffiModel.pi.username,
id: await bind.mainGetLastRemoteId(), id: await bind.mainGetLastRemoteId(),
); );
} else { } else {
final client = _ffi.target?.serverModel.clients final client = parent.target?.serverModel.clients
.firstWhere((client) => client.id == id); .firstWhere((client) => client.id == id);
if (client == null) { if (client == null) {
return debugPrint("Failed to receive msg,user doesn't exist"); return debugPrint("Failed to receive msg,user doesn't exist");
} }
if (isDesktop) { if (isDesktop) {
window_on_top(null); window_on_top(null);
var index = _ffi.target?.serverModel.clients var index = parent.target?.serverModel.clients
.indexWhere((client) => client.id == id); .indexWhere((client) => client.id == id);
if (index != null && index >= 0) { if (index != null && index >= 0) {
gFFI.serverModel.tabController.jumpTo(index); gFFI.serverModel.tabController.jumpTo(index);
@ -246,8 +245,8 @@ class ChatModel with ChangeNotifier {
if (message.text.isNotEmpty) { if (message.text.isNotEmpty) {
_messages[_currentID]?.insert(message); _messages[_currentID]?.insert(message);
if (_currentID == clientModeID) { if (_currentID == clientModeID) {
if (_ffi.target != null) { if (parent.target != null) {
bind.sessionSendChat(id: _ffi.target!.id, text: message.text); bind.sessionSendChat(id: parent.target!.id, text: message.text);
} }
} else { } else {
bind.cmSendChat(connId: _currentID, msg: message.text); bind.cmSendChat(connId: _currentID, msg: message.text);

View File

@ -1,55 +1,84 @@
import 'dart:convert';
import 'dart:math';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:get/get.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
import '../common.dart';
import '../consts.dart'; import '../consts.dart';
import 'dart:ui' as ui; import 'dart:ui' as ui;
class Keyboard { /// Mouse button enum.
late FFI _ffi; enum MouseButtons { left, right, wheel }
late String _id;
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 InputModel {
final WeakReference<FFI> parent;
String keyboardMode = "legacy"; String keyboardMode = "legacy";
Keyboard(FFI ffi, String id) { // keyboard
_ffi = ffi; var shift = false;
_id = id; var ctrl = false;
} var alt = false;
var command = false;
// mouse
final isPhysicalMouse = false.obs;
int _lastMouseDownButtons = 0;
get id => parent.target?.id ?? "";
InputModel(this.parent);
KeyEventResult handleRawKeyEvent(FocusNode data, RawKeyEvent e) { KeyEventResult handleRawKeyEvent(FocusNode data, RawKeyEvent e) {
bind.sessionGetKeyboardName(id: _id).then((result) { bind.sessionGetKeyboardName(id: id).then((result) {
keyboardMode = result.toString(); keyboardMode = result.toString();
}); });
final key = e.logicalKey; final key = e.logicalKey;
if (e is RawKeyDownEvent) { if (e is RawKeyDownEvent) {
if (!e.repeat){ if (!e.repeat) {
if (e.isAltPressed && !_ffi.alt) { if (e.isAltPressed && !alt) {
_ffi.alt = true; alt = true;
} else if (e.isControlPressed && !_ffi.ctrl) { } else if (e.isControlPressed && !ctrl) {
_ffi.ctrl = true; ctrl = true;
} else if (e.isShiftPressed && !_ffi.shift) { } else if (e.isShiftPressed && !shift) {
_ffi.shift = true; shift = true;
} else if (e.isMetaPressed && !_ffi.command) { } else if (e.isMetaPressed && !command) {
_ffi.command = true; command = true;
} }
} }
} }
if (e is RawKeyUpEvent) { if (e is RawKeyUpEvent) {
if (key == LogicalKeyboardKey.altLeft || if (key == LogicalKeyboardKey.altLeft ||
key == LogicalKeyboardKey.altRight) { key == LogicalKeyboardKey.altRight) {
_ffi.alt = false; alt = false;
} else if (key == LogicalKeyboardKey.controlLeft || } else if (key == LogicalKeyboardKey.controlLeft ||
key == LogicalKeyboardKey.controlRight) { key == LogicalKeyboardKey.controlRight) {
_ffi.ctrl = false; ctrl = false;
} else if (key == LogicalKeyboardKey.shiftRight || } else if (key == LogicalKeyboardKey.shiftRight ||
key == LogicalKeyboardKey.shiftLeft) { key == LogicalKeyboardKey.shiftLeft) {
_ffi.shift = false; shift = false;
} else if (key == LogicalKeyboardKey.metaLeft || } else if (key == LogicalKeyboardKey.metaLeft ||
key == LogicalKeyboardKey.metaRight || key == LogicalKeyboardKey.metaRight ||
key == LogicalKeyboardKey.superKey) { key == LogicalKeyboardKey.superKey) {
_ffi.command = false; command = false;
} }
} }
@ -91,12 +120,20 @@ class Keyboard {
} else { } else {
down = false; down = false;
} }
inputRawKey(e.character ?? "", keyCode, scanCode, down);
}
_ffi.inputRawKey(e.character ?? "", keyCode, scanCode, down); /// Send raw Key Event
void inputRawKey(String name, int keyCode, int scanCode, bool down) {
bind.sessionHandleFlutterKeyEvent(
id: id,
name: name,
keycode: keyCode,
scancode: scanCode,
downOrUp: down);
} }
void legacyKeyboardMode(RawKeyEvent e) { void legacyKeyboardMode(RawKeyEvent e) {
final key = e.logicalKey;
if (e is RawKeyDownEvent) { if (e is RawKeyDownEvent) {
if (e.repeat) { if (e.repeat) {
sendRawKey(e, press: true); sendRawKey(e, press: true);
@ -114,22 +151,23 @@ class Keyboard {
final label = physicalKeyMap[e.physicalKey.usbHidUsage] ?? final label = physicalKeyMap[e.physicalKey.usbHidUsage] ??
logicalKeyMap[e.logicalKey.keyId] ?? logicalKeyMap[e.logicalKey.keyId] ??
e.logicalKey.keyLabel; e.logicalKey.keyLabel;
_ffi.inputKey(label, down: down, press: press ?? false); inputKey(label, down: down, press: press ?? false);
} }
}
class Mouse { /// Send key stroke event.
var _isPhysicalMouse = false; /// [down] indicates the key's state(down or up).
int _lastMouseDownButtons = 0; /// [press] indicates a click event(down and up).
void inputKey(String name, {bool? down, bool? press}) {
late FFI _ffi; if (!parent.target!.ffiModel.keyboard()) return;
late String _id; bind.sessionInputKey(
late double tabBarHeight; id: id,
name: name,
Mouse(FFI ffi, String id, double tabBarHeight_) { down: down ?? false,
_ffi = ffi; press: press ?? true,
_id = id; alt: alt,
tabBarHeight = tabBarHeight_; ctrl: ctrl,
shift: shift,
command: command);
} }
Map<String, dynamic> getEvent(PointerEvent evt, String type) { Map<String, dynamic> getEvent(PointerEvent evt, String type) {
@ -137,10 +175,10 @@ class Mouse {
out['type'] = type; out['type'] = type;
out['x'] = evt.position.dx; out['x'] = evt.position.dx;
out['y'] = evt.position.dy; out['y'] = evt.position.dy;
if (_ffi.alt) out['alt'] = 'true'; if (alt) out['alt'] = 'true';
if (_ffi.shift) out['shift'] = 'true'; if (shift) out['shift'] = 'true';
if (_ffi.ctrl) out['ctrl'] = 'true'; if (ctrl) out['ctrl'] = 'true';
if (_ffi.command) out['command'] = 'true'; if (command) out['command'] = 'true';
out['buttons'] = evt out['buttons'] = evt
.buttons; // left button: 1, right button: 2, middle button: 4, 1 | 2 = 3 (left + right) .buttons; // left button: 1, right button: 2, middle button: 4, 1 | 2 = 3 (left + right)
if (evt.buttons != 0) { if (evt.buttons != 0) {
@ -151,38 +189,92 @@ class Mouse {
return out; return out;
} }
/// Send a mouse tap event(down and up).
void tap(MouseButtons button) {
sendMouse('down', button);
sendMouse('up', button);
}
/// Send scroll event with scroll distance [y].
void scroll(int y) {
bind.sessionSendMouse(
id: id,
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, String> modify(Map<String, String> 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 (!parent.target!.ffiModel.keyboard()) return;
bind.sessionSendMouse(
id: id,
msg: json.encode(modify({'type': type, 'buttons': button.value})));
}
void enterOrLeave(bool enter) {
// Fix status
if (!enter) {
resetModifiers();
}
bind.sessionEnterOrLeave(id: id, enter: enter);
}
/// Send mouse movement event with distance in [x] and [y].
void moveMouse(double x, double y) {
if (!parent.target!.ffiModel.keyboard()) return;
var x2 = x.toInt();
var y2 = y.toInt();
bind.sessionSendMouse(
id: id, msg: json.encode(modify({'x': '$x2', 'y': '$y2'})));
}
void onPointHoverImage(PointerHoverEvent e) { void onPointHoverImage(PointerHoverEvent e) {
if (e.kind != ui.PointerDeviceKind.mouse) return; if (e.kind != ui.PointerDeviceKind.mouse) return;
if (!_isPhysicalMouse) { if (!isPhysicalMouse.value) {
_isPhysicalMouse = true; isPhysicalMouse.value = true;
} }
if (_isPhysicalMouse) { if (isPhysicalMouse.value) {
_ffi.handleMouse(getEvent(e, 'mousemove'), tabBarHeight: tabBarHeight); handleMouse(getEvent(e, 'mousemove'));
} }
} }
void onPointDownImage(PointerDownEvent e) { void onPointDownImage(PointerDownEvent e) {
debugPrint("onPointDownImage");
if (e.kind != ui.PointerDeviceKind.mouse) { if (e.kind != ui.PointerDeviceKind.mouse) {
if (_isPhysicalMouse) { if (isPhysicalMouse.value) {
_isPhysicalMouse = false; isPhysicalMouse.value = false;
} }
} }
if (_isPhysicalMouse) { if (isPhysicalMouse.value) {
_ffi.handleMouse(getEvent(e, 'mousedown'), tabBarHeight: tabBarHeight); handleMouse(getEvent(e, 'mousedown'));
} }
} }
void onPointUpImage(PointerUpEvent e) { void onPointUpImage(PointerUpEvent e) {
if (e.kind != ui.PointerDeviceKind.mouse) return; if (e.kind != ui.PointerDeviceKind.mouse) return;
if (_isPhysicalMouse) { if (isPhysicalMouse.value) {
_ffi.handleMouse(getEvent(e, 'mouseup'), tabBarHeight: tabBarHeight); handleMouse(getEvent(e, 'mouseup'));
} }
} }
void onPointMoveImage(PointerMoveEvent e) { void onPointMoveImage(PointerMoveEvent e) {
if (e.kind != ui.PointerDeviceKind.mouse) return; if (e.kind != ui.PointerDeviceKind.mouse) return;
if (_isPhysicalMouse) { if (isPhysicalMouse.value) {
_ffi.handleMouse(getEvent(e, 'mousemove'), tabBarHeight: tabBarHeight); handleMouse(getEvent(e, 'mousemove'));
} }
} }
@ -201,7 +293,93 @@ class Mouse {
dy = 1; dy = 1;
} }
bind.sessionSendMouse( bind.sessionSendMouse(
id: _id, msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}'); id: id, msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}');
}
}
void handleMouse(Map<String, dynamic> evt) {
var type = '';
var isMove = false;
switch (evt['type']) {
case 'mousedown':
type = 'down';
break;
case 'mouseup':
type = 'up';
break;
case 'mousemove':
isMove = true;
break;
default:
return;
}
evt['type'] = type;
double x = evt['x'];
double y = max(0.0, evt['y']);
if (isDesktop) {
final RxBool fullscreen = Get.find(tag: 'fullscreen');
final tabBarHeight = fullscreen.isTrue ? 0 : kDesktopRemoteTabBarHeight;
y = y - tabBarHeight;
}
final canvasModel = parent.target!.canvasModel;
final ffiModel = parent.target!.ffiModel;
if (isMove) {
canvasModel.moveDesktopMouse(x, y);
}
final d = ffiModel.display;
if (canvasModel.scrollStyle == ScrollStyle.scrollbar) {
final imageWidth = d.width * canvasModel.scale;
final imageHeight = d.height * canvasModel.scale;
x += imageWidth * canvasModel.scrollX;
y += imageHeight * canvasModel.scrollY;
// boxed size is a center widget
if (canvasModel.size.width > imageWidth) {
x -= ((canvasModel.size.width - imageWidth) / 2);
}
if (canvasModel.size.height > imageHeight) {
y -= ((canvasModel.size.height - imageHeight) / 2);
}
} else {
x -= canvasModel.x;
y -= canvasModel.y;
}
x /= canvasModel.scale;
y /= canvasModel.scale;
x += d.x;
y += d.y;
if (type != '') {
x = 0;
y = 0;
}
// fix mouse out of bounds
x = min(max(0.0, x), d.width.toDouble());
y = min(max(0.0, y), d.height.toDouble());
evt['x'] = '${x.round()}';
evt['y'] = '${y.round()}';
var buttons = '';
switch (evt['buttons']) {
case 1:
buttons = 'left';
break;
case 2:
buttons = 'right';
break;
case 4:
buttons = 'wheel';
break;
}
evt['buttons'] = buttons;
bind.sessionSendMouse(id: id, msg: json.encode(evt));
}
/// Web only
void listenToMouse(bool yesOrNo) {
if (yesOrNo) {
platformFFI.startDesktopWebListener();
} else {
platformFFI.stopDesktopWebListener();
} }
} }
} }

View File

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui' as ui; import 'dart:ui' as ui;
@ -23,6 +22,7 @@ import '../common.dart';
import '../common/shared_state.dart'; import '../common/shared_state.dart';
import '../utils/image.dart' as img; import '../utils/image.dart' as img;
import '../mobile/widgets/dialog.dart'; import '../mobile/widgets/dialog.dart';
import 'input_model.dart';
import 'platform_model.dart'; import 'platform_model.dart';
typedef HandleMsgBox = Function(Map<String, dynamic> evt, String id); typedef HandleMsgBox = Function(Map<String, dynamic> evt, String id);
@ -725,15 +725,9 @@ class CursorModel with ChangeNotifier {
return h - thresh; return h - thresh;
} }
touch(double x, double y, MouseButtons button) {
moveLocal(x, y);
parent.target?.moveMouse(_x, _y);
parent.target?.tap(button);
}
move(double x, double y) { move(double x, double y) {
moveLocal(x, y); moveLocal(x, y);
parent.target?.moveMouse(_x, _y); parent.target?.inputModel.moveMouse(_x, _y);
} }
moveLocal(double x, double y) { moveLocal(double x, double y) {
@ -748,7 +742,7 @@ class CursorModel with ChangeNotifier {
reset() { reset() {
_x = _displayOriginX; _x = _displayOriginX;
_y = _displayOriginY; _y = _displayOriginY;
parent.target?.moveMouse(_x, _y); parent.target?.inputModel.moveMouse(_x, _y);
parent.target?.canvasModel.clear(true); parent.target?.canvasModel.clear(true);
notifyListeners(); notifyListeners();
} }
@ -759,7 +753,7 @@ class CursorModel with ChangeNotifier {
final scale = parent.target?.canvasModel.scale ?? 1.0; final scale = parent.target?.canvasModel.scale ?? 1.0;
_x += dx / scale; _x += dx / scale;
_y += dy / scale; _y += dy / scale;
parent.target?.moveMouse(_x, _y); parent.target?.inputModel.moveMouse(_x, _y);
notifyListeners(); notifyListeners();
return; return;
} }
@ -824,7 +818,7 @@ class CursorModel with ChangeNotifier {
parent.target?.canvasModel.panY(-dy); parent.target?.canvasModel.panY(-dy);
} }
parent.target?.moveMouse(_x, _y); parent.target?.inputModel.moveMouse(_x, _y);
notifyListeners(); notifyListeners();
} }
@ -894,7 +888,7 @@ class CursorModel with ChangeNotifier {
_displayOriginY = y; _displayOriginY = y;
_x = x + 1; _x = x + 1;
_y = y + 1; _y = y + 1;
parent.target?.moveMouse(x, y); parent.target?.inputModel.moveMouse(x, y);
parent.target?.canvasModel.resetOffset(); parent.target?.canvasModel.resetOffset();
notifyListeners(); notifyListeners();
} }
@ -905,7 +899,7 @@ class CursorModel with ChangeNotifier {
_displayOriginY = y; _displayOriginY = y;
_x = xCursor; _x = xCursor;
_y = yCursor; _y = yCursor;
parent.target?.moveMouse(x, y); parent.target?.inputModel.moveMouse(x, y);
notifyListeners(); notifyListeners();
} }
@ -1011,31 +1005,11 @@ class RecordingModel with ChangeNotifier {
} }
} }
/// Mouse button enum.
enum MouseButtons { left, right, wheel }
extension ToString on MouseButtons {
String get value {
switch (this) {
case MouseButtons.left:
return 'left';
case MouseButtons.right:
return 'right';
case MouseButtons.wheel:
return 'wheel';
}
}
}
enum ConnType { defaultConn, fileTransfer, portForward, rdp } enum ConnType { defaultConn, fileTransfer, portForward, rdp }
/// Flutter state manager and data communication with the Rust core. /// Flutter state manager and data communication with the Rust core.
class FFI { class FFI {
var id = ''; var id = '';
var shift = false;
var ctrl = false;
var alt = false;
var command = false;
var version = ''; var version = '';
var connType = ConnType.defaultConn; var connType = ConnType.defaultConn;
@ -1053,6 +1027,7 @@ class FFI {
late final UserModel userModel; // global late final UserModel userModel; // global
late final QualityMonitorModel qualityMonitorModel; // session late final QualityMonitorModel qualityMonitorModel; // session
late final RecordingModel recordingModel; // recording late final RecordingModel recordingModel; // recording
late final InputModel inputModel; // session
FFI() { FFI() {
imageModel = ImageModel(WeakReference(this)); imageModel = ImageModel(WeakReference(this));
@ -1066,89 +1041,11 @@ class FFI {
userModel = UserModel(WeakReference(this)); userModel = UserModel(WeakReference(this));
qualityMonitorModel = QualityMonitorModel(WeakReference(this)); qualityMonitorModel = QualityMonitorModel(WeakReference(this));
recordingModel = RecordingModel(WeakReference(this)); recordingModel = RecordingModel(WeakReference(this));
inputModel = InputModel(WeakReference(this));
} }
/// Send a mouse tap event(down and up). /// Start with the given [id]. Only transfer file if [isFileTransfer], only port forward if [isPortForward].
tap(MouseButtons button) { void start(String id,
sendMouse('down', button);
sendMouse('up', button);
}
/// Send scroll event with scroll distance [y].
scroll(int y) {
bind.sessionSendMouse(
id: id,
msg: json
.encode(modify({'id': id, 'type': 'wheel', 'y': y.toString()})));
}
/// Reset key modifiers to false, including [shift], [ctrl], [alt] and [command].
resetModifiers() {
shift = ctrl = alt = command = false;
}
/// Modify the given modifier map [evt] based on current modifier key status.
Map<String, String> modify(Map<String, String> 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.
sendMouse(String type, MouseButtons button) {
if (!ffiModel.keyboard()) return;
bind.sessionSendMouse(
id: id,
msg: json.encode(modify({'type': type, 'buttons': button.value})));
}
/// Send raw Key Event
inputRawKey(String name, int keyCode, int scanCode, bool down) {
bind.sessionHandleFlutterKeyEvent(
id: id,
name: name,
keycode: keyCode,
scancode: scanCode,
downOrUp: down);
}
enterOrLeave(bool enter) {
// Fix status
if (!enter) {
resetModifiers();
}
bind.sessionEnterOrLeave(id: id, enter: enter);
}
/// Send key stroke event.
/// [down] indicates the key's state(down or up).
/// [press] indicates a click event(down and up).
inputKey(String name, {bool? down, bool? press}) {
if (!ffiModel.keyboard()) return;
bind.sessionInputKey(
id: id,
name: name,
down: down ?? false,
press: press ?? true,
alt: alt,
ctrl: ctrl,
shift: shift,
command: command);
}
/// Send mouse movement event with distance in [x] and [y].
moveMouse(double x, double y) {
if (!ffiModel.keyboard()) return;
var x2 = x.toInt();
var y2 = y.toInt();
bind.sessionSendMouse(
id: id, msg: json.encode(modify({'x': '$x2', 'y': '$y2'})));
}
/// Connect with the given [id]. Only transfer file if [isFileTransfer], only port forward if [isPortForward].
connect(String id,
{bool isFileTransfer = false, {bool isFileTransfer = false,
bool isPortForward = false, bool isPortForward = false,
double tabBarHeight = 0.0}) { double tabBarHeight = 0.0}) {
@ -1192,7 +1089,7 @@ class FFI {
} }
/// Login with [password], choose if the client should [remember] it. /// Login with [password], choose if the client should [remember] it.
login(String id, String password, bool remember) { void login(String id, String password, bool remember) {
bind.sessionLogin(id: id, password: password, remember: remember); bind.sessionLogin(id: id, password: password, remember: remember);
} }
@ -1209,118 +1106,17 @@ class FFI {
cursorModel.clear(); cursorModel.clear();
ffiModel.clear(); ffiModel.clear();
canvasModel.clear(); canvasModel.clear();
resetModifiers(); inputModel.resetModifiers();
debugPrint('model $id closed'); debugPrint('model $id closed');
} }
handleMouse(Map<String, dynamic> evt, {double tabBarHeight = 0.0}) { void setMethodCallHandler(FMethod callback) {
var type = '';
var isMove = false;
switch (evt['type']) {
case 'mousedown':
type = 'down';
break;
case 'mouseup':
type = 'up';
break;
case 'mousemove':
isMove = true;
break;
default:
return;
}
evt['type'] = type;
double x = evt['x'];
double y = max(0.0, (evt['y'] as double) - tabBarHeight);
if (isMove) {
canvasModel.moveDesktopMouse(x, y);
}
final d = ffiModel.display;
if (canvasModel.scrollStyle == ScrollStyle.scrollbar) {
final imageWidth = d.width * canvasModel.scale;
final imageHeight = d.height * canvasModel.scale;
x += imageWidth * canvasModel.scrollX;
y += imageHeight * canvasModel.scrollY;
// boxed size is a center widget
if (canvasModel.size.width > imageWidth) {
x -= ((canvasModel.size.width - imageWidth) / 2);
}
if (canvasModel.size.height > imageHeight) {
y -= ((canvasModel.size.height - imageHeight) / 2);
}
} else {
x -= canvasModel.x;
y -= canvasModel.y;
}
x /= canvasModel.scale;
y /= canvasModel.scale;
x += d.x;
y += d.y;
if (type != '') {
x = 0;
y = 0;
}
// fix mouse out of bounds
x = min(max(0.0, x), d.width.toDouble());
y = min(max(0.0, y), d.height.toDouble());
evt['x'] = '${x.round()}';
evt['y'] = '${y.round()}';
var buttons = '';
switch (evt['buttons']) {
case 1:
buttons = 'left';
break;
case 2:
buttons = 'right';
break;
case 4:
buttons = 'wheel';
break;
}
evt['buttons'] = buttons;
bind.sessionSendMouse(id: id, msg: json.encode(evt));
}
listenToMouse(bool yesOrNo) {
if (yesOrNo) {
platformFFI.startDesktopWebListener();
} else {
platformFFI.stopDesktopWebListener();
}
}
setMethodCallHandler(FMethod callback) {
platformFFI.setMethodCallHandler(callback); platformFFI.setMethodCallHandler(callback);
} }
Future<bool> invokeMethod(String method, [dynamic arguments]) async { Future<bool> invokeMethod(String method, [dynamic arguments]) async {
return await platformFFI.invokeMethod(method, arguments); return await platformFFI.invokeMethod(method, arguments);
} }
Future<List<String>> getAudioInputs() async {
return await bind.mainGetSoundInputs();
}
Future<String> getDefaultAudioInput() async {
final input = await bind.mainGetOption(key: 'audio-input');
if (input.isEmpty && Platform.isWindows) {
return 'System Sound';
}
return input;
}
setDefaultAudioInput(String input) {
bind.mainSetOption(key: 'audio-input', value: input);
}
Future<Map<String, String>> getHttpHeaders() async {
return {
'Authorization':
'Bearer ${await bind.mainGetLocalOption(key: 'access_token')}'
};
}
} }
class Display { class Display {
@ -1349,8 +1145,8 @@ class PeerInfo {
List<Display> displays = []; List<Display> displays = [];
} }
savePreference(String id, double xCursor, double yCursor, double xCanvas, Future<void> savePreference(String id, double xCursor, double yCursor,
double yCanvas, double scale, int currentDisplay) async { double xCanvas, double yCanvas, double scale, int currentDisplay) async {
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
final p = <String, dynamic>{}; final p = <String, dynamic>{};
p['xCursor'] = xCursor; p['xCursor'] = xCursor;
@ -1371,12 +1167,12 @@ Future<Map<String, dynamic>?> getPreference(String id) async {
return m; return m;
} }
removePreference(String id) async { void removePreference(String id) async {
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.remove('peer$id'); prefs.remove('peer$id');
} }
initializeCursorAndCanvas(FFI ffi) async { Future<void> initializeCursorAndCanvas(FFI ffi) async {
var p = await getPreference(ffi.id); var p = await getPreference(ffi.id);
int currentDisplay = 0; int currentDisplay = 0;
if (p != null) { if (p != null) {
@ -1396,16 +1192,3 @@ initializeCursorAndCanvas(FFI ffi) async {
ffi.ffiModel.display.x, ffi.ffiModel.display.y, xCursor, yCursor); ffi.ffiModel.display.x, ffi.ffiModel.display.y, xCursor, yCursor);
ffi.canvasModel.update(xCanvas, yCanvas, scale); ffi.canvasModel.update(xCanvas, yCanvas, scale);
} }
/// Translate text based on the pre-defined dictionary.
/// note: params [FFI?] can be used to replace global FFI implementation
/// for example: during global initialization, gFFI not exists yet.
// String translate(String name, {FFI? ffi}) {
// if (name.startsWith('Failed to') && name.contains(': ')) {
// return name.split(': ').map((x) => translate(x)).join(': ');
// }
// var a = 'translate';
// var b = '{"locale": "$localeName", "text": "$name"}';
//
// return (ffi ?? gFFI).getByName(a, b);
// }

View File

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import '../common.dart';
import 'model.dart'; import 'model.dart';
import 'platform_model.dart'; import 'platform_model.dart';
@ -35,7 +36,7 @@ class UserModel extends ChangeNotifier {
"id": await bind.mainGetMyId(), "id": await bind.mainGetMyId(),
"uuid": await bind.mainGetUuid(), "uuid": await bind.mainGetUuid(),
}, },
headers: await _getHeaders()); headers: await getHttpHeaders());
await Future.wait([ await Future.wait([
bind.mainSetLocalOption(key: 'access_token', value: ''), bind.mainSetLocalOption(key: 'access_token', value: ''),
bind.mainSetLocalOption(key: 'user_info', value: ''), bind.mainSetLocalOption(key: 'user_info', value: ''),
@ -46,10 +47,6 @@ class UserModel extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
Future<Map<String, String>>? _getHeaders() {
return parent.target?.getHttpHeaders();
}
Future<Map<String, dynamic>> login(String userName, String pass) async { Future<Map<String, dynamic>> login(String userName, String pass) async {
final url = await bind.mainGetApiServer(); final url = await bind.mainGetApiServer();
try { try {