diff --git a/flutter/lib/common/shared_state.dart b/flutter/lib/common/shared_state.dart index 7232cb6ad..67752d888 100644 --- a/flutter/lib/common/shared_state.dart +++ b/flutter/lib/common/shared_state.dart @@ -1,16 +1,26 @@ import 'package:get/get.dart'; import '../consts.dart'; +import '../models/platform_model.dart'; class PrivacyModeState { static String tag(String id) => 'privacy_mode_$id'; static void init(String id) { - final RxBool state = false.obs; - Get.put(state, tag: tag(id)); + final key = tag(id); + if (!Get.isRegistered(tag: key)) { + final RxBool state = false.obs; + Get.put(state, tag: key); + } + } + + static void delete(String id) { + final key = tag(id); + if (Get.isRegistered(tag: key)) { + Get.delete(tag: key); + } } - static void delete(String id) => Get.delete(tag: tag(id)); static RxBool find(String id) => Get.find(tag: tag(id)); } @@ -18,11 +28,20 @@ class BlockInputState { static String tag(String id) => 'block_input_$id'; static void init(String id) { - final RxBool state = false.obs; - Get.put(state, tag: tag(id)); + final key = tag(id); + if (!Get.isRegistered(tag: key)) { + final RxBool state = false.obs; + Get.put(state, tag: key); + } + } + + static void delete(String id) { + final key = tag(id); + if (Get.isRegistered(tag: key)) { + Get.delete(tag: key); + } } - static void delete(String id) => Get.delete(tag: tag(id)); static RxBool find(String id) => Get.find(tag: tag(id)); } @@ -30,11 +49,20 @@ class CurrentDisplayState { static String tag(String id) => 'current_display_$id'; static void init(String id) { - final RxInt state = RxInt(0); - Get.put(state, tag: tag(id)); + final key = tag(id); + if (!Get.isRegistered(tag: key)) { + final RxInt state = RxInt(0); + Get.put(state, tag: key); + } + } + + static void delete(String id) { + final key = tag(id); + if (Get.isRegistered(tag: key)) { + Get.delete(tag: key); + } } - static void delete(String id) => Get.delete(tag: tag(id)); static RxInt find(String id) => Get.find(tag: tag(id)); } @@ -85,3 +113,46 @@ class ConnectionTypeState { static ConnectionType find(String id) => Get.find(tag: tag(id)); } + +class ShowRemoteCursorState { + static String tag(String id) => 'show_remote_cursor_$id'; + + static void init(String id) { + final key = tag(id); + if (!Get.isRegistered(tag: key)) { + final RxBool state = false.obs; + Get.put(state, tag: key); + } + } + + static void delete(String id) { + final key = tag(id); + if (Get.isRegistered(tag: key)) { + Get.delete(tag: key); + } + } + + static RxBool find(String id) => Get.find(tag: tag(id)); +} + +class KeyboardEnabledState { + static String tag(String id) => 'keyboard_enabled_$id'; + + static void init(String id) { + final key = tag(id); + if (!Get.isRegistered(tag: key)) { + // Server side, default true + final RxBool state = true.obs; + Get.put(state, tag: key); + } + } + + static void delete(String id) { + final key = tag(id); + if (Get.isRegistered(tag: key)) { + Get.delete(tag: key); + } + } + + static RxBool find(String id) => Get.find(tag: tag(id)); +} diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index a245f1f12..23e4e4900 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -40,6 +40,8 @@ class _RemotePageState extends State Timer? _timer; String _value = ''; final _cursorOverImage = false.obs; + late RxBool _showRemoteCursor; + late RxBool _keyboardEnabled; final FocusNode _mobileFocusNode = FocusNode(); final FocusNode _physicalFocusNode = FocusNode(); @@ -56,17 +58,24 @@ class _RemotePageState extends State PrivacyModeState.init(id); BlockInputState.init(id); CurrentDisplayState.init(id); + KeyboardEnabledState.init(id); + ShowRemoteCursorState.init(id); + _showRemoteCursor = ShowRemoteCursorState.find(id); + _keyboardEnabled = KeyboardEnabledState.find(id); } void _removeStates(String id) { PrivacyModeState.delete(id); BlockInputState.delete(id); CurrentDisplayState.delete(id); + ShowRemoteCursorState.delete(id); + KeyboardEnabledState.delete(id); } @override void initState() { super.initState(); + _initStates(widget.id); _ffi = FFI(); _updateTabBarHeight(); Get.put(_ffi, tag: widget.id); @@ -84,7 +93,8 @@ class _RemotePageState extends State _ffi.listenToMouse(true); _ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id); // WindowManager.instance.addListener(this); - _initStates(widget.id); + _showRemoteCursor.value = bind.sessionGetToggleOptionSync( + id: widget.id, arg: 'show-remote-cursor'); } @override @@ -197,8 +207,7 @@ class _RemotePageState extends State _ffi.inputKey(label, down: down, press: press ?? false); } - Widget buildBody(BuildContext context, FfiModel ffiModel) { - final keyboard = ffiModel.permissions['keyboard'] != false; + Widget buildBody(BuildContext context) { return Scaffold( backgroundColor: MyTheme.color(context).bg, body: Overlay( @@ -208,8 +217,7 @@ class _RemotePageState extends State _ffi.dialogManager.setOverlayState(Overlay.of(context)); return Container( color: Colors.black, - child: getRawPointerAndKeyBody( - getBodyForDesktop(context, keyboard))); + child: getRawPointerAndKeyBody(getBodyForDesktop(context))); }) ], )); @@ -224,70 +232,61 @@ class _RemotePageState extends State clientClose(_ffi.dialogManager); return false; }, - child: MultiProvider( - providers: [ - ChangeNotifierProvider.value(value: _ffi.ffiModel), - ChangeNotifierProvider.value(value: _ffi.imageModel), - ChangeNotifierProvider.value(value: _ffi.cursorModel), - ChangeNotifierProvider.value(value: _ffi.canvasModel), - ], - child: Consumer( - builder: (context, ffiModel, child) => - buildBody(context, ffiModel)))); + child: MultiProvider(providers: [ + ChangeNotifierProvider.value(value: _ffi.ffiModel), + ChangeNotifierProvider.value(value: _ffi.imageModel), + ChangeNotifierProvider.value(value: _ffi.cursorModel), + ChangeNotifierProvider.value(value: _ffi.canvasModel), + ], child: buildBody(context))); } Widget getRawPointerAndKeyBody(Widget child) { - return Consumer( - builder: (context, FfiModel, _child) => MouseRegion( - cursor: FfiModel.permissions['keyboard'] != false - ? SystemMouseCursors.none - : MouseCursor.defer, - child: FocusScope( - autofocus: true, - child: Focus( - autofocus: true, - canRequestFocus: true, - focusNode: _physicalFocusNode, - onFocusChange: (bool v) { - _imageFocused = v; - }, - onKey: (data, e) { - final key = e.logicalKey; - if (e is RawKeyDownEvent) { - if (e.repeat) { - sendRawKey(e, press: true); - } else { - if (e.isAltPressed && !_ffi.alt) { - _ffi.alt = true; - } else if (e.isControlPressed && !_ffi.ctrl) { - _ffi.ctrl = true; - } else if (e.isShiftPressed && !_ffi.shift) { - _ffi.shift = true; - } else if (e.isMetaPressed && !_ffi.command) { - _ffi.command = true; - } - sendRawKey(e, down: true); - } - } - if (e is RawKeyUpEvent) { - if (key == LogicalKeyboardKey.altLeft || - key == LogicalKeyboardKey.altRight) { - _ffi.alt = false; - } else if (key == LogicalKeyboardKey.controlLeft || - key == LogicalKeyboardKey.controlRight) { - _ffi.ctrl = false; - } else if (key == LogicalKeyboardKey.shiftRight || - key == LogicalKeyboardKey.shiftLeft) { - _ffi.shift = false; - } else if (key == LogicalKeyboardKey.metaLeft || - key == LogicalKeyboardKey.metaRight) { - _ffi.command = false; - } - sendRawKey(e); - } - return KeyEventResult.handled; - }, - child: child)))); + return FocusScope( + autofocus: true, + child: Focus( + autofocus: true, + canRequestFocus: true, + focusNode: _physicalFocusNode, + onFocusChange: (bool v) { + _imageFocused = v; + }, + onKey: (data, e) { + final key = e.logicalKey; + if (e is RawKeyDownEvent) { + if (e.repeat) { + sendRawKey(e, press: true); + } else { + if (e.isAltPressed && !_ffi.alt) { + _ffi.alt = true; + } else if (e.isControlPressed && !_ffi.ctrl) { + _ffi.ctrl = true; + } else if (e.isShiftPressed && !_ffi.shift) { + _ffi.shift = true; + } else if (e.isMetaPressed && !_ffi.command) { + _ffi.command = true; + } + sendRawKey(e, down: true); + } + } + if (e is RawKeyUpEvent) { + if (key == LogicalKeyboardKey.altLeft || + key == LogicalKeyboardKey.altRight) { + _ffi.alt = false; + } else if (key == LogicalKeyboardKey.controlLeft || + key == LogicalKeyboardKey.controlRight) { + _ffi.ctrl = false; + } else if (key == LogicalKeyboardKey.shiftRight || + key == LogicalKeyboardKey.shiftLeft) { + _ffi.shift = false; + } else if (key == LogicalKeyboardKey.metaLeft || + key == LogicalKeyboardKey.metaRight) { + _ffi.command = false; + } + sendRawKey(e); + } + return KeyEventResult.handled; + }, + child: child)); } /// touchMode only: @@ -382,32 +381,30 @@ class _RemotePageState extends State child: child)); } - Widget getBodyForDesktop(BuildContext context, bool keyboard) { + Widget getBodyForDesktop(BuildContext context) { var paints = [ MouseRegion(onEnter: (evt) { bind.hostStopSystemKeyPropagate(stopped: false); }, onExit: (evt) { bind.hostStopSystemKeyPropagate(stopped: true); - }, child: Container( - child: LayoutBuilder(builder: (context, constraints) { - Future.delayed(Duration.zero, () { - Provider.of(context, listen: false).updateViewStyle(); - }); - return ImagePaint( - id: widget.id, - cursorOverImage: _cursorOverImage, - listenerBuilder: _buildImageListener, - ); - }), - )) + }, child: LayoutBuilder(builder: (context, constraints) { + Future.delayed(Duration.zero, () { + Provider.of(context, listen: false).updateViewStyle(); + }); + return ImagePaint( + id: widget.id, + cursorOverImage: _cursorOverImage, + keyboardEnabled: _keyboardEnabled, + listenerBuilder: _buildImageListener, + ); + })) ]; - final cursor = bind.sessionGetToggleOptionSync( - id: widget.id, arg: 'show-remote-cursor'); - if (keyboard || cursor) { - paints.add(CursorPaint( - id: widget.id, - )); - } + + paints.add(Obx(() => Visibility( + visible: _keyboardEnabled.isTrue || _showRemoteCursor.isTrue, + child: CursorPaint( + id: widget.id, + )))); paints.add(QualityMonitor(_ffi.qualityMonitorModel)); paints.add(RemoteMenubar( id: widget.id, @@ -447,7 +444,7 @@ class _RemotePageState extends State _ffi.canvasModel.updateViewStyle(); break; case 'maximize': - Future.delayed(Duration(milliseconds: 100), () { + Future.delayed(const Duration(milliseconds: 100), () { _ffi.canvasModel.updateViewStyle(); }); break; @@ -461,6 +458,7 @@ class _RemotePageState extends State class ImagePaint extends StatelessWidget { final String id; final Rx cursorOverImage; + final Rx keyboardEnabled; final Widget Function(Widget)? listenerBuilder; final ScrollController _horizontal = ScrollController(); final ScrollController _vertical = ScrollController(); @@ -469,6 +467,7 @@ class ImagePaint extends StatelessWidget { {Key? key, required this.id, required this.cursorOverImage, + required this.keyboardEnabled, this.listenerBuilder}) : super(key: key); @@ -485,25 +484,26 @@ class ImagePaint extends StatelessWidget { painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s), )); return Center( - child: NotificationListener( - onNotification: (notification) { - final percentX = _horizontal.position.extentBefore / - (_horizontal.position.extentBefore + - _horizontal.position.extentInside + - _horizontal.position.extentAfter); - final percentY = _vertical.position.extentBefore / - (_vertical.position.extentBefore + - _vertical.position.extentInside + - _vertical.position.extentAfter); - c.setScrollPercent(percentX, percentY); - return false; - }, - child: Obx(() => MouseRegion( - cursor: cursorOverImage.value - ? SystemMouseCursors.none - : SystemMouseCursors.basic, - child: _buildCrossScrollbar(_buildListener(imageWidget)))), - )); + child: NotificationListener( + onNotification: (notification) { + final percentX = _horizontal.position.extentBefore / + (_horizontal.position.extentBefore + + _horizontal.position.extentInside + + _horizontal.position.extentAfter); + final percentY = _vertical.position.extentBefore / + (_vertical.position.extentBefore + + _vertical.position.extentInside + + _vertical.position.extentAfter); + c.setScrollPercent(percentX, percentY); + return false; + }, + child: Obx(() => MouseRegion( + cursor: (keyboardEnabled.isTrue && cursorOverImage.isTrue) + ? SystemMouseCursors.none + : MouseCursor.defer, + child: _buildCrossScrollbar(_buildListener(imageWidget)))), + ), + ); } else { final imageWidget = SizedBox( width: c.size.width, @@ -562,13 +562,12 @@ class CursorPaint extends StatelessWidget { final m = Provider.of(context); final c = Provider.of(context); // final adjust = m.adjustForKeyboard(); - var s = c.scale; return CustomPaint( painter: ImagePainter( image: m.image, - x: m.x * s - m.hotx + c.x, - y: m.y * s - m.hoty + c.y, - scale: 1), + x: m.x - m.hotx + c.x / c.scale, + y: m.y - m.hoty + c.y / c.scale, + scale: c.scale), ); } } @@ -620,30 +619,30 @@ class QualityMonitor extends StatelessWidget { right: 10, child: qualityMonitorModel.show ? Container( - padding: EdgeInsets.all(8), + padding: const EdgeInsets.all(8), color: MyTheme.canvasColor.withAlpha(120), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Speed: ${qualityMonitorModel.data.speed ?? ''}", - style: TextStyle(color: MyTheme.grayBg), + style: const TextStyle(color: MyTheme.grayBg), ), Text( "FPS: ${qualityMonitorModel.data.fps ?? ''}", - style: TextStyle(color: MyTheme.grayBg), + style: const TextStyle(color: MyTheme.grayBg), ), Text( "Delay: ${qualityMonitorModel.data.delay ?? ''} ms", - style: TextStyle(color: MyTheme.grayBg), + style: const TextStyle(color: MyTheme.grayBg), ), Text( "Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb", - style: TextStyle(color: MyTheme.grayBg), + style: const TextStyle(color: MyTheme.grayBg), ), Text( "Codec: ${qualityMonitorModel.data.codecFormat ?? ''}", - style: TextStyle(color: MyTheme.grayBg), + style: const TextStyle(color: MyTheme.grayBg), ), ], ), diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index dbe7592e6..dc3c249f0 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -522,16 +522,19 @@ class _RemoteMenubarState extends State { } }), MenuEntryDivider(), - MenuEntrySwitch( - text: translate('Show remote cursor'), - getter: () async { - return bind.sessionGetToggleOptionSync( - id: widget.id, arg: 'show-remote-cursor'); - }, - setter: (bool v) async { - await bind.sessionToggleOption( - id: widget.id, value: 'show-remote-cursor'); - }), + () { + final state = ShowRemoteCursorState.find(widget.id); + return MenuEntrySwitch2( + text: translate('Show remote cursor'), + getter: () { + return state; + }, + setter: (bool v) async { + state.value = v; + await bind.sessionToggleOption( + id: widget.id, value: 'show-remote-cursor'); + }); + }(), MenuEntrySwitch( text: translate('Show quality monitor'), getter: () async { @@ -560,12 +563,12 @@ class _RemoteMenubarState extends State { 'Lock after session end', 'lock-after-session-end')); if (pi.platform == 'Windows') { displayMenu.add(MenuEntrySwitch2( + dismissOnClicked: true, text: translate('Privacy mode'), getter: () { return PrivacyModeState.find(widget.id); }, setter: (bool v) async { - Navigator.pop(context); await bind.sessionToggleOption( id: widget.id, value: 'privacy-mode'); })); diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 887bf7d35..384d7692a 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -32,7 +32,7 @@ class FfiModel with ChangeNotifier { Display _display = Display(); var _inputBlocked = false; - final _permissions = Map(); + final _permissions = {}; bool? _secure; bool? _direct; bool _touchMode = false; @@ -71,12 +71,13 @@ class FfiModel with ChangeNotifier { } } - void updatePermission(Map evt) { + void updatePermission(Map evt, String id) { evt.forEach((k, v) { if (k == 'name' || k.isEmpty) return; _permissions[k] = v == 'true'; }); - print('$_permissions'); + KeyboardEnabledState.find(id).value = _permissions['keyboard'] != false; + debugPrint('$_permissions'); notifyListeners(); } @@ -146,7 +147,7 @@ class FfiModel with ChangeNotifier { } else if (name == 'clipboard') { Clipboard.setData(ClipboardData(text: evt['content'])); } else if (name == 'permission') { - parent.target?.ffiModel.updatePermission(evt); + parent.target?.ffiModel.updatePermission(evt, peerId); } else if (name == 'chat_client_mode') { parent.target?.chatModel .receive(ChatModel.clientModeID, evt['text'] ?? ""); @@ -185,7 +186,7 @@ class FfiModel with ChangeNotifier { /// Bind the event listener to receive events from the Rust core. void updateEventListener(String peerId) { - final void Function(Map) cb = (evt) { + cb(evt) { var name = evt['name']; if (name == 'msgbox') { handleMsgBox(evt, peerId); @@ -205,7 +206,7 @@ class FfiModel with ChangeNotifier { } else if (name == 'clipboard') { Clipboard.setData(ClipboardData(text: evt['content'])); } else if (name == 'permission') { - parent.target?.ffiModel.updatePermission(evt); + parent.target?.ffiModel.updatePermission(evt, peerId); } else if (name == 'chat_client_mode') { parent.target?.chatModel .receive(ChatModel.clientModeID, evt['text'] ?? ""); @@ -239,7 +240,8 @@ class FfiModel with ChangeNotifier { } else if (name == 'update_privacy_mode') { updatePrivacyMode(evt, peerId); } - }; + } + platformFFI.setEventCallback(cb); } @@ -321,7 +323,7 @@ class FfiModel with ChangeNotifier { if (isPeerAndroid) { _touchMode = true; if (parent.target?.ffiModel.permissions['keyboard'] != false) { - Timer(Duration(milliseconds: 100), showMobileActionsOverlay); + Timer(const Duration(milliseconds: 100), showMobileActionsOverlay); } } else { _touchMode = @@ -464,15 +466,20 @@ enum ScrollStyle { } class CanvasModel with ChangeNotifier { + // image offset of canvas + double _x = 0; + // image offset of canvas + double _y = 0; + // image scale + double _scale = 1.0; + // the tabbar over the image + double tabBarHeight = 0.0; + // TODO multi canvas model + String id = ""; // scroll offset x percent double _scrollX = 0.0; // scroll offset y percent double _scrollY = 0.0; - double _x = 0; - double _y = 0; - double _scale = 1.0; - double _tabBarHeight = 0.0; - String id = ""; // TODO multi canvas model ScrollStyle _scrollStyle = ScrollStyle.scrollauto; WeakReference parent; @@ -492,9 +499,6 @@ class CanvasModel with ChangeNotifier { double get scrollX => _scrollX; double get scrollY => _scrollY; - set tabBarHeight(double h) => _tabBarHeight = h; - double get tabBarHeight => _tabBarHeight; - void updateViewStyle() async { final style = await bind.sessionGetOption(id: id, arg: 'view-style'); if (style == null) { @@ -548,12 +552,11 @@ class CanvasModel with ChangeNotifier { Size get size { final size = MediaQueryData.fromWindow(ui.window).size; - return Size(size.width, size.height - _tabBarHeight); + return Size(size.width, size.height - tabBarHeight); } void moveDesktopMouse(double x, double y) { // On mobile platforms, move the canvas with the cursor. - //if (!isDesktop) { final dw = getDisplayWidth() * _scale; final dh = getDisplayHeight() * _scale; var dxOffset = 0; @@ -579,8 +582,13 @@ class CanvasModel with ChangeNotifier { if (dxOffset != 0 || dyOffset != 0) { notifyListeners(); } - //} - parent.target?.cursorModel.moveLocal(x, y); + + // If keyboard is not permitted, do not move cursor when mouse is moving. + if (parent.target != null) { + if (parent.target!.ffiModel.keyboard()) { + parent.target!.cursorModel.moveLocal(x, y); + } + } } set scale(v) { @@ -597,11 +605,8 @@ class CanvasModel with ChangeNotifier { if (isWebDesktop) { updateViewStyle(); } else { - final size = MediaQueryData.fromWindow(ui.window).size; - final canvasWidth = size.width; - final canvasHeight = size.height - _tabBarHeight; - _x = (canvasWidth - getDisplayWidth() * _scale) / 2; - _y = (canvasHeight - getDisplayHeight() * _scale) / 2; + _x = (size.width - getDisplayWidth() * _scale) / 2; + _y = (size.height - getDisplayHeight() * _scale) / 2; } notifyListeners(); } @@ -613,7 +618,7 @@ class CanvasModel with ChangeNotifier { void updateScale(double v) { if (parent.target?.imageModel.image == null) return; - final offset = parent.target?.cursorModel.offset ?? Offset(0, 0); + final offset = parent.target?.cursorModel.offset ?? const Offset(0, 0); var r = parent.target?.cursorModel.getVisibleRect() ?? Rect.zero; final px0 = (offset.dx - r.left) * _scale; final py0 = (offset.dy - r.top) * _scale; @@ -640,7 +645,7 @@ class CanvasModel with ChangeNotifier { class CursorModel with ChangeNotifier { ui.Image? _image; - final _images = Map>(); + final _images = >{}; double _x = -10000; double _y = -10000; double _hotx = 0; @@ -807,7 +812,7 @@ class CursorModel with ChangeNotifier { // my throw exception, because the listener maybe already dispose notifyListeners(); } catch (e) { - print('notify cursor: $e'); + debugPrint('notify cursor: $e'); } }); }