diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 9daa69865..68752cf7d 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -13,6 +13,7 @@ import 'package:flutter_hbb/desktop/screen/desktop_port_forward_screen.dart'; import 'package:flutter_hbb/desktop/screen/desktop_remote_screen.dart'; import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.dart'; import 'package:flutter_hbb/models/state_model.dart'; +import 'package:flutter_hbb/plugin/handlers.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:get/get.dart'; @@ -428,4 +429,10 @@ _registerEventHandler() { reloadAllWindows(); }); } + // Register native handlers. + if (isDesktop) { + platformFFI.registerEventHandler('native_ui', 'native_ui', (evt) async { + NativeUiHandler.instance.onEvent(evt); + }); + } } diff --git a/flutter/lib/plugin/handlers.dart b/flutter/lib/plugin/handlers.dart new file mode 100644 index 000000000..99327d242 --- /dev/null +++ b/flutter/lib/plugin/handlers.dart @@ -0,0 +1,43 @@ +import 'dart:ffi'; + +abstract class NativeHandler { + bool onEvent(Map evt); +} + +typedef OnSelectPeersCallback = Bool Function(Int returnCode, + Pointer data, Uint64 dataLength, Pointer userData); +typedef OnSelectPeersCallbackDart = bool Function( + int returnCode, Pointer data, int dataLength, Pointer userData); + +class NativeUiHandler extends NativeHandler { + NativeUiHandler._(); + + static NativeUiHandler instance = NativeUiHandler._(); + + @override + bool onEvent(Map evt) { + final name = evt['name']; + final action = evt['action']; + if (name != "native_ui") { + return false; + } + switch (action) { + case "select_peers": + int cb = evt['cb']; + int userData = evt['user_data'] ?? 0; + final cbFuncNative = Pointer.fromAddress(cb) + .cast>(); + final cbFuncDart = cbFuncNative.asFunction(); + onSelectPeers(cbFuncDart, userData); + break; + default: + return false; + } + return true; + } + + void onSelectPeers(OnSelectPeersCallbackDart cb, int userData) async { + // TODO: design a UI interface to pick peers. + cb(0, Pointer.fromAddress(0), 0, Pointer.fromAddress(userData)); + } +} diff --git a/src/plugin/native_handlers/mod.rs b/src/plugin/native_handlers/mod.rs index 32b6627e2..7d590ab1e 100644 --- a/src/plugin/native_handlers/mod.rs +++ b/src/plugin/native_handlers/mod.rs @@ -10,12 +10,13 @@ use serde_json::Map; use crate::return_if_not_method; -use self::session::PluginNativeSessionHandler; +use self::{session::PluginNativeSessionHandler, ui::PluginNativeUIHandler}; use super::cstr_to_string; mod macros; pub mod session; +pub mod ui; pub type NR = super::native::NativeReturnValue; pub type PluginNativeHandlerRegistrar = NativeHandlerRegistrar>; @@ -33,10 +34,11 @@ pub struct NativeHandlerRegistrar { impl Default for PluginNativeHandlerRegistrar { fn default() -> Self { Self { - handlers: Arc::new(RwLock::new(vec![Box::new( + handlers: Arc::new(RwLock::new(vec![ // Add prebuilt native handlers here. - PluginNativeSessionHandler::default(), - )])), + Box::new(PluginNativeSessionHandler::default()), + Box::new(PluginNativeUIHandler::default()), + ])), } } } diff --git a/src/plugin/native_handlers/ui.rs b/src/plugin/native_handlers/ui.rs new file mode 100644 index 000000000..a4754fc08 --- /dev/null +++ b/src/plugin/native_handlers/ui.rs @@ -0,0 +1,100 @@ +use std::{collections::HashMap, ffi::c_void, os::raw::c_int}; + +use serde_json::json; + +use crate::{ + define_method_prefix, + flutter::{APP_TYPE_MAIN}, +}; + +use super::PluginNativeHandler; + +#[derive(Default)] +pub struct PluginNativeUIHandler; + +/// Callback for UI interface. +/// +/// [Note] +/// We will transfer the native callback to u64 and post it to flutter. +/// The flutter thread will directly call this method. +/// +/// an example of `data` is: +/// ``` +/// { +/// "cb": 0x1234567890 +/// } +/// ``` +/// [Safety] +/// Please make sure the callback u provided is VALID, or memory or calling issues may occur to cause the program crash! +pub type OnUIReturnCallback = extern "C" fn(return_code: c_int, data: *const c_void, data_len: u64, user_data: *const c_void); + +impl PluginNativeHandler for PluginNativeUIHandler { + define_method_prefix!("ui_"); + + fn on_message( + &self, + method: &str, + data: &serde_json::Map, + ) -> Option { + match method { + "select_peers_async" => { + if let Some(cb) = data.get("cb") { + if let Some(cb) = cb.as_u64() { + let user_data = match data.get("user_data") { + Some(user_data) => { + user_data.as_u64().unwrap_or(0) + }, + None => 0, + }; + self.select_peers_async(cb, user_data); + return Some(super::NR { + return_type: 0, + data: std::ptr::null(), + }); + } + } + return Some(super::NR { + return_type: -1, + data: "missing cb field message".as_ptr() as _, + }); + } + _ => {} + } + None + } + + fn on_message_raw( + &self, + method: &str, + data: &serde_json::Map, + raw: *const std::ffi::c_void, + _raw_len: usize, + ) -> Option { + None + } +} + +impl PluginNativeUIHandler { + /// Call with method `select_peers_async` and the following json: + /// ```json + /// { + /// "cb": 0, // The function address + /// "user_data": 0 // An opaque pointer value passed to the callback. + /// } + /// ``` + /// + /// [Arguments] + /// @param cb: the function address with type [OnUIReturnCallback]. + /// @param user_data: the function will be called with this value. + fn select_peers_async(&self, cb: u64, user_data: u64) { + let mut param = HashMap::new(); + param.insert("name", json!("native_ui")); + param.insert("action", json!("select_peers")); + param.insert("cb", json!(cb)); + param.insert("user_data", json!(user_data)); + crate::flutter::push_global_event( + APP_TYPE_MAIN, + serde_json::to_string(¶m).unwrap_or("".to_string()), + ); + } +}