mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-06-07 18:02:48 +08:00
Merge pull request #1633 from Heap-Hop/refactor_input_model
Refactor input model for mobile and desktop
This commit is contained in:
commit
215f0575a7
@ -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')}'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -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())));
|
||||||
|
}
|
||||||
|
60
flutter/lib/common/widgets/remote_input.dart
Normal file
60
flutter/lib/common/widgets/remote_input.dart
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
@ -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";
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
|
@ -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())));
|
|
||||||
}
|
|
||||||
|
@ -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,
|
||||||
);
|
);
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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})
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
// }
|
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user