From 9a08e0bed4ccc9f40afc802567b4c6f01b981be2 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 20 Apr 2023 20:57:47 +0800 Subject: [PATCH] add ui event Signed-off-by: fufesou --- .../lib/desktop/widgets/remote_toolbar.dart | 43 ++++----- flutter/lib/plugin/common.dart | 42 ++++++++ flutter/lib/plugin/desc.dart | 96 ++++++++++++------- flutter/lib/plugin/model.dart | 2 + flutter/lib/plugin/widget.dart | 3 - .../widgets/remote/toolbar/display.dart | 85 +++++++++++++++- src/flutter_ffi.rs | 48 ++++++++++ src/plugin/config.rs | 4 +- src/plugin/mod.rs | 2 + 9 files changed, 262 insertions(+), 63 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index f8a38c830..f44f62514 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:io'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; @@ -641,7 +640,7 @@ class _ControlMenu extends StatelessWidget { if (e.divider) { return Divider(); } else { - return _MenuItemButton( + return MenuButton( child: e.child, onPressed: e.onPressed, ffi: ffi, @@ -711,7 +710,7 @@ class _DisplayMenuState extends State<_DisplayMenu> { if (!visible) return Offstage(); return Column( children: [ - _MenuItemButton( + MenuButton( child: Text(translate('Adjust Window')), onPressed: _doAdjustWindow, ffi: widget.ffi), @@ -828,7 +827,7 @@ class _DisplayMenuState extends State<_DisplayMenu> { final v = data as List>; return Column(children: [ ...v - .map((e) => _RadioMenuButton( + .map((e) => RdoMenuButton( value: e.value, groupValue: e.groupValue, onChanged: e.onChanged, @@ -858,14 +857,14 @@ class _DisplayMenuState extends State<_DisplayMenu> { final enabled = widget.ffi.canvasModel.imageOverflow.value; return Column(children: [ - _RadioMenuButton( + RdoMenuButton( child: Text(translate('ScrollAuto')), value: kRemoteScrollStyleAuto, groupValue: groupValue, onChanged: enabled ? (value) => onChange(value) : null, ffi: widget.ffi, ), - _RadioMenuButton( + RdoMenuButton( child: Text(translate('Scrollbar')), value: kRemoteScrollStyleBar, groupValue: groupValue, @@ -886,7 +885,7 @@ class _DisplayMenuState extends State<_DisplayMenu> { ffi: widget.ffi, child: Text(translate('Image Quality')), menuChildren: v - .map((e) => _RadioMenuButton( + .map((e) => RdoMenuButton( value: e.value, groupValue: e.groupValue, onChanged: e.onChanged, @@ -908,7 +907,7 @@ class _DisplayMenuState extends State<_DisplayMenu> { ffi: widget.ffi, child: Text(translate('Codec')), menuChildren: v - .map((e) => _RadioMenuButton( + .map((e) => RdoMenuButton( value: e.value, groupValue: e.groupValue, onChanged: e.onChanged, @@ -948,7 +947,7 @@ class _DisplayMenuState extends State<_DisplayMenu> { return _SubmenuButton( ffi: widget.ffi, menuChildren: resolutions - .map((e) => _RadioMenuButton( + .map((e) => RdoMenuButton( value: '${e.width}x${e.height}', groupValue: groupValue, onChanged: onChanged, @@ -966,7 +965,7 @@ class _DisplayMenuState extends State<_DisplayMenu> { if (v.isEmpty) return Offstage(); return Column( children: v - .map((e) => _CheckboxMenuButton( + .map((e) => CkbMenuButton( value: e.value, onChanged: e.onChanged, child: e.child, @@ -1026,7 +1025,7 @@ class _KeyboardMenu extends StatelessWidget { KeyboardModeMenu(key: _kKeyMapMode, menu: 'Map mode'), KeyboardModeMenu(key: _kKeyTranslateMode, menu: 'Translate mode'), ]; - List<_RadioMenuButton> list = []; + List list = []; final enabled = !ffi.ffiModel.viewOnly; onChanged(String? value) async { if (value == null) return; @@ -1049,7 +1048,7 @@ class _KeyboardMenu extends StatelessWidget { if (mode.key == _kKeyTranslateMode) { text = '$text beta'; } - list.add(_RadioMenuButton( + list.add(RdoMenuButton( child: Text(text), value: mode.key, groupValue: groupValue, @@ -1069,7 +1068,7 @@ class _KeyboardMenu extends StatelessWidget { return Column( children: [ Divider(), - _MenuItemButton( + MenuButton( child: Text( '${translate('Local keyboard type')}: ${KBLayoutType.value}'), trailingIcon: const Icon(Icons.settings), @@ -1085,7 +1084,7 @@ class _KeyboardMenu extends StatelessWidget { view_mode() { final ffiModel = ffi.ffiModel; final enabled = version_cmp(pi.version, '1.2.0') >= 0 && ffiModel.keyboard; - return _CheckboxMenuButton( + return CkbMenuButton( value: ffiModel.viewOnly, onChanged: enabled ? (value) async { @@ -1129,7 +1128,7 @@ class _ChatMenuState extends State<_ChatMenu> { } textChat() { - return _MenuItemButton( + return MenuButton( child: Text(translate('Text chat')), ffi: widget.ffi, onPressed: () { @@ -1148,7 +1147,7 @@ class _ChatMenuState extends State<_ChatMenu> { } voiceCall() { - return _MenuItemButton( + return MenuButton( child: Text(translate('Voice call')), ffi: widget.ffi, onPressed: () => bind.sessionRequestVoiceCall(id: widget.id), @@ -1403,12 +1402,12 @@ class _SubmenuButton extends StatelessWidget { } } -class _MenuItemButton extends StatelessWidget { +class MenuButton extends StatelessWidget { final VoidCallback? onPressed; final Widget? trailingIcon; final Widget? child; final FFI ffi; - _MenuItemButton( + MenuButton( {Key? key, this.onPressed, this.trailingIcon, @@ -1431,12 +1430,12 @@ class _MenuItemButton extends StatelessWidget { } } -class _CheckboxMenuButton extends StatelessWidget { +class CkbMenuButton extends StatelessWidget { final bool? value; final ValueChanged? onChanged; final Widget? child; final FFI ffi; - const _CheckboxMenuButton( + const CkbMenuButton( {Key? key, required this.value, required this.onChanged, @@ -1460,13 +1459,13 @@ class _CheckboxMenuButton extends StatelessWidget { } } -class _RadioMenuButton extends StatelessWidget { +class RdoMenuButton extends StatelessWidget { final T value; final T? groupValue; final ValueChanged? onChanged; final Widget? child; final FFI ffi; - const _RadioMenuButton( + const RdoMenuButton( {Key? key, required this.value, required this.groupValue, diff --git a/flutter/lib/plugin/common.dart b/flutter/lib/plugin/common.dart index 5cbc0f871..8395e2a81 100644 --- a/flutter/lib/plugin/common.dart +++ b/flutter/lib/plugin/common.dart @@ -1 +1,43 @@ +import 'dart:convert'; + typedef PluginId = String; + +class MsgFromUi { + String remotePeerId; + String localPeerId; + String id; + String name; + String location; + String key; + String value; + String action; + + MsgFromUi({ + required this.remotePeerId, + required this.localPeerId, + required this.id, + required this.name, + required this.location, + required this.key, + required this.value, + required this.action, + }); + + Map toJson() { + return { + 'remote_peer_id': remotePeerId, + 'local_peer_id': localPeerId, + 'id': id, + 'name': name, + 'location': location, + 'key': key, + 'value': value, + 'action': action, + }; + } + + @override + String toString() { + return jsonEncode(toJson()); + } +} diff --git a/flutter/lib/plugin/desc.dart b/flutter/lib/plugin/desc.dart index 6be712697..8c0de4625 100644 --- a/flutter/lib/plugin/desc.dart +++ b/flutter/lib/plugin/desc.dart @@ -1,45 +1,58 @@ import 'dart:collection'; -class UiButton { - String key; - String text; - String icon; - String tooltip; - String action; - - UiButton(this.key, this.text, this.icon, this.tooltip, this.action); - UiButton.fromJson(Map json) - : key = json['key'] ?? '', - text = json['text'] ?? '', - icon = json['icon'] ?? '', - tooltip = json['tooltip'] ?? '', - action = json['action'] ?? ''; -} - -class UiCheckbox { - String key; - String text; - String tooltip; - String action; - - UiCheckbox(this.key, this.text, this.tooltip, this.action); - UiCheckbox.fromJson(Map json) - : key = json['key'] ?? '', - text = json['text'] ?? '', - tooltip = json['tooltip'] ?? '', - action = json['action'] ?? ''; -} +const String kValueTrue = '1'; +const String kValueFalse = '0'; class UiType { - UiButton? button; - UiCheckbox? checkbox; + String key; + String text; + String tooltip; + String action; + + UiType(this.key, this.text, this.tooltip, this.action); UiType.fromJson(Map json) - : button = json['t'] == 'Button' ? UiButton.fromJson(json['c']) : null, - checkbox = - json['t'] != 'Checkbox' ? UiCheckbox.fromJson(json['c']) : null; + : key = json['key'] ?? '', + text = json['text'] ?? '', + tooltip = json['tooltip'] ?? '', + action = json['action'] ?? ''; - bool get isValid => button != null || checkbox != null; + static UiType? create(Map json) { + if (json['t'] == 'Button') { + return UiButton.fromJson(json['c']); + } else if (json['t'] == 'Checkbox') { + return UiCheckbox.fromJson(json['c']); + } else { + return null; + } + } +} + +class UiButton extends UiType { + String icon; + + UiButton( + {required String key, + required String text, + required this.icon, + required String tooltip, + required String action}) + : super(key, text, tooltip, action); + + UiButton.fromJson(Map json) + : icon = json['icon'] ?? '', + super.fromJson(json); +} + +class UiCheckbox extends UiType { + UiCheckbox( + {required String key, + required String text, + required String tooltip, + required String action}) + : super(key, text, tooltip, action); + + UiCheckbox.fromJson(Map json) : super.fromJson(json); } class Location { @@ -49,6 +62,14 @@ class Location { HashMap ui; Location(this.ui); + Location.fromJson(Map json) : ui = HashMap() { + json.forEach((key, value) { + var ui = UiType.create(value); + if (ui != null) { + this.ui[ui.key] = ui; + } + }); + } } class ConfigItem { @@ -63,6 +84,11 @@ class ConfigItem { value = json['value'] ?? '', description = json['description'] ?? '', defaultValue = json['default'] ?? ''; + + static String get trueValue => kValueTrue; + static String get falseValue => kValueFalse; + static bool isTrue(String value) => value == kValueTrue; + static bool isFalse(String value) => value == kValueFalse; } class Config { diff --git a/flutter/lib/plugin/model.dart b/flutter/lib/plugin/model.dart index 824992e20..ac1b74828 100644 --- a/flutter/lib/plugin/model.dart +++ b/flutter/lib/plugin/model.dart @@ -15,6 +15,8 @@ class LocationModel with ChangeNotifier { uiList.add(ui); notifyListeners(); } + + bool get isEmpty => uiList.isEmpty; } void addLocation(PluginId id, String location, UiType ui) { diff --git a/flutter/lib/plugin/widget.dart b/flutter/lib/plugin/widget.dart index a99e25e4e..fc110077c 100644 --- a/flutter/lib/plugin/widget.dart +++ b/flutter/lib/plugin/widget.dart @@ -40,8 +40,5 @@ void handleReloading(Map evt, String peer) { return; } final ui = UiType.fromJson(evt); - if (!ui.isValid) { - return; - } addLocation(evt['id']!, evt['location']!, ui); } diff --git a/flutter/lib/plugin/widgets/remote/toolbar/display.dart b/flutter/lib/plugin/widgets/remote/toolbar/display.dart index 1f7cc359e..c5fa13e67 100644 --- a/flutter/lib/plugin/widgets/remote/toolbar/display.dart +++ b/flutter/lib/plugin/widgets/remote/toolbar/display.dart @@ -1,27 +1,110 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hbb/models/model.dart'; import 'package:provider/provider.dart'; +import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart'; +import 'package:flutter_hbb/models/platform_model.dart'; +import '../../../desc.dart'; import '../../../model.dart'; +import '../../../common.dart'; class Display extends StatelessWidget { + final PluginId pluginId; final String peerId; + final FFI ffi; + final String location; final LocationModel locationModel; Display({ Key? key, + required this.pluginId, required this.peerId, + required this.ffi, + required this.location, required this.locationModel, }) : super(key: key); + bool get isEmpty => locationModel.isEmpty; + @override Widget build(BuildContext context) { return ChangeNotifierProvider.value( value: locationModel, child: Consumer(builder: (context, model, child) { return Column( - children: [], + children: locationModel.uiList.map((ui) => _buildItem(ui)).toList(), ); }), ); } + + Widget _buildItem(UiType ui) { + switch (ui.runtimeType) { + case UiButton: + return _buildMenuButton(ui as UiButton); + case UiCheckbox: + return _buildCheckboxMenuButton(ui as UiCheckbox); + default: + return Container(); + } + } + + Uint8List _makeEvent( + String localPeerId, + String key, { + bool? v, + }) { + final event = MsgFromUi( + remotePeerId: peerId, + localPeerId: localPeerId, + id: pluginId, + name: getDesc(pluginId)?.name ?? '', + location: location, + key: key, + value: + v != null ? (v ? ConfigItem.trueValue : ConfigItem.falseValue) : '', + action: '', + ); + return Uint8List.fromList(event.toString().codeUnits); + } + + Widget _buildMenuButton(UiButton ui) { + return MenuButton( + onPressed: () { + () async { + final localPeerId = await bind.mainGetMyId(); + bind.pluginEvent( + id: pluginId, + event: _makeEvent(localPeerId, ui.key), + ); + }(); + }, + // to-do: rustdesk translate or plugin translate ? + child: Text(ui.text), + ffi: ffi, + ); + } + + Widget _buildCheckboxMenuButton(UiCheckbox ui) { + final v = + bind.pluginGetSessionOption(id: pluginId, peer: peerId, key: ui.key); + return CkbMenuButton( + value: ConfigItem.isTrue(v), + onChanged: (v) { + if (v != null) { + () async { + final localPeerId = await bind.mainGetMyId(); + bind.pluginEvent( + id: pluginId, + event: _makeEvent(localPeerId, ui.key, v: v), + ); + }(); + } + }, + // to-do: rustdesk translate or plugin translate ? + child: Text(ui.text), + ffi: ffi, + ); + } } diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 5f72431ef..62db17151 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1405,6 +1405,54 @@ pub fn plugin_event(_id: String, _event: Vec) { } } +#[inline] +pub fn plugin_get_session_option(_id: String, _peer: String, _key: String) -> SyncReturn { + #[cfg(feature = "plugin_framework")] + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + return SyncReturn(crate::plugin::PeerConfig::get(&_id, &_peer, &_key)); + } + #[cfg(any( + not(feature = "plugin_framework"), + target_os = "android", + target_os = "ios" + ))] + return SyncReturn("".to_owned()); +} + +#[inline] +pub fn plugin_set_session_option(_id: String, _peer: String, _key: String, _value: String) { + #[cfg(feature = "plugin_framework")] + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + crate::plugin::PeerConfig::set(&_id, &_peer, &_key, &_value); + } +} + +#[inline] +pub fn plugin_get_local_option(_id: String, _key: String) -> SyncReturn { + #[cfg(feature = "plugin_framework")] + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + allow_err!(crate::plugin::LocalConfig::get(&_id, &key)); + } + #[cfg(any( + not(feature = "plugin_framework"), + target_os = "android", + target_os = "ios" + ))] + return SyncReturn("".to_owned()); +} + +#[inline] +pub fn plugin_set_local_option(_id: String, _key: String, _value: String) { + #[cfg(feature = "plugin_framework")] + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + crate::plugin::LocalConfig::set(&_id, &_key, &_value); + } +} + #[cfg(target_os = "android")] pub mod server_side { use hbb_common::{config, log}; diff --git a/src/plugin/config.rs b/src/plugin/config.rs index 7a9228425..d86053649 100644 --- a/src/plugin/config.rs +++ b/src/plugin/config.rs @@ -15,9 +15,9 @@ lazy_static::lazy_static! { } #[derive(Debug, Default, Serialize, Deserialize)] -struct LocalConfig(HashMap); +pub struct LocalConfig(HashMap); #[derive(Debug, Default, Serialize, Deserialize)] -struct PeerConfig(HashMap); +pub struct PeerConfig(HashMap); type PeersConfig = HashMap; #[inline] diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index 2f16f0325..c7ff2c4af 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -12,6 +12,8 @@ pub use plugins::{ reload_plugin, unload_plugin, }; +pub use config::{LocalConfig, PeerConfig}; + #[inline] fn cstr_to_string(cstr: *const c_char) -> ResultType { Ok(String::from_utf8(unsafe {