diff --git a/flutter/lib/cm_main.dart b/flutter/lib/cm_main.dart index bf72849e8..422e7aa26 100644 --- a/flutter/lib/cm_main.dart +++ b/flutter/lib/cm_main.dart @@ -24,7 +24,5 @@ void main(List args) async { gFFI.serverModel.clients .add(Client(3, false, false, "UserD", "441123123", true, false, false)); runApp(GetMaterialApp( - debugShowCheckedModeBanner: false, - theme: getCurrentTheme(), - home: DesktopServerPage())); + debugShowCheckedModeBanner: false, home: DesktopServerPage())); } diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 309ae9892..92ae17f9a 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -27,6 +27,7 @@ var isWeb = false; var isWebDesktop = false; var version = ""; int androidVersion = 0; +late final DesktopType? desktopType; typedef F = String Function(String); typedef FMethod = String Function(String, dynamic); @@ -42,6 +43,15 @@ late final iconFile = MemoryImage(Uint8List.fromList(base64Decode( late final iconRestart = MemoryImage(Uint8List.fromList(base64Decode( 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAB7BAAAewQHDaVRTAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAbhJREFUWIXVlrFqFGEUhb+7UYxaWCQKlrKKxaZSQVGDJih2tj6MD2DnMwiWvoAIRnENIpZiYxEro6IooiS7SPwsMgNLkk3mjmYmnmb45/73nMNwz/x/qH3gMu2gH6rAU+Blw+Lngau4jpmGxVF7qp1iPWjaQKnZ2WnXbuP/NqAeUPc3ZkA9XDwvqc+BVWCgPlJ7tRwUKThZce819b46VH+pfXVRXVO/q2cSul3VOgZUl0ejq86r39TXI8mqZKDuDEwCw3IREQvAbWAGmMsQZQ0sAl3gHPB1Q+0e8BuYzRDuy2yOiFVgaUxtRf0ETGc4syk4rc6PqU0Cx9j8Zf6dAeAK8Fi9sUXtFjABvEgxJlNwRP2svlNPjbw/q35U36oTFbnyMSwabxb/gB/qA3VBHagrauV7RW0DRfP1IvMlXqkXkhz1DYyQTKtHa/Z2VVMx3IiI+PI3/bCHjuOpFrSnAMpL6QfgTcMGesDx0kBr2BMzsNyi/vtQu8CJlgwsRbZDnWP90NkKaxHxJMOXMqAeAn5u0ydwMCKGY+qbkB3C2W3EKWoXk5zVoHbUZ+6Mh7tl4G4F8RJ3qvL+AfV3r5Vdpj70AAAAAElFTkSuQmCC'))); +enum DesktopType { + main, + remote, + fileTransfer, + cm, + portForward, + rdp, +} + class IconFont { static const _family1 = 'Tabbar'; static const _family2 = 'PeerSearchbar'; @@ -182,6 +192,32 @@ class MyTheme { ], ); + static changeTo(bool dark) { + if (Get.isDarkMode != dark) { + Get.find().setString("darkTheme", dark ? "Y" : ""); + Get.changeThemeMode(dark ? ThemeMode.dark : ThemeMode.light); + if (desktopType == DesktopType.main) { + bind.mainChangeTheme(dark: dark); + } + } + } + + static bool _themeInitialed = false; + + static ThemeMode initialThemeMode({bool mainPage = false}) { + bool dark; + // Brightnesss is always light on windows, Flutter 3.0.5 + if (_themeInitialed || !mainPage || Platform.isWindows) { + dark = "Y" == Get.find().getString("darkTheme"); + } else { + dark = WidgetsBinding.instance.platformDispatcher.platformBrightness == + Brightness.dark; + Get.find().setString("darkTheme", dark ? "Y" : ""); + } + _themeInitialed = true; + return dark ? ThemeMode.dark : ThemeMode.light; + } + static ColorThemeExtension color(BuildContext context) { return Theme.of(context).extension()!; } @@ -192,8 +228,7 @@ class MyTheme { } bool isDarkTheme() { - final isDark = "Y" == Get.find().getString("darkTheme"); - return isDark; + return Get.isDarkMode; } final ButtonStyle flatButtonStyle = TextButton.styleFrom( @@ -478,7 +513,9 @@ void msgBox( submit() { dialogManager.dismissAll(); // https://github.com/fufesou/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263 - if (!type.contains("custom")) { + if (!type.contains("custom") && + !(desktopType == DesktopType.portForward || + desktopType == DesktopType.rdp)) { closeConnection(); } } diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 95a6faaa2..e48c85b0d 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -5,6 +5,8 @@ const String kAppTypeMain = "main"; const String kAppTypeDesktopRemote = "remote"; const String kAppTypeDesktopFileTransfer = "file transfer"; const String kAppTypeDesktopPortForward = "port forward"; +const String kAppTypeDesktopRDP = "rdp"; + const String kTabLabelHomePage = "Home"; const String kTabLabelSettingPage = "Settings"; diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 48fc0a5e7..823a32fb6 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -218,27 +218,25 @@ class _UserInterfaceState extends State<_UserInterface> onChanged: (key) async { await bind.mainSetLocalOption(key: "lang", value: key); Get.forceAppUpdate(); + bind.mainChangeLanguage(lang: key); }, ).marginOnly(left: _kContentHMargin); }); } Widget theme() { - var change = () { - bool dark = !isDarkTheme(); - Get.changeTheme(dark ? MyTheme.darkTheme : MyTheme.lightTheme); - Get.find().setString("darkTheme", dark ? "Y" : ""); - Get.forceAppUpdate(); - }; + change() { + MyTheme.changeTo(!isDarkTheme()); + } return GestureDetector( + onTap: change, child: Row( children: [ Checkbox(value: isDarkTheme(), onChanged: (_) => change()), Expanded(child: Text(translate('Dark Theme'))), ], ).marginOnly(left: _kCheckBoxLeftMargin), - onTap: change, ); } } diff --git a/flutter/lib/desktop/pages/desktop_tab_page.dart b/flutter/lib/desktop/pages/desktop_tab_page.dart index 87082284b..fde543a89 100644 --- a/flutter/lib/desktop/pages/desktop_tab_page.dart +++ b/flutter/lib/desktop/pages/desktop_tab_page.dart @@ -33,7 +33,6 @@ class _DesktopTabPageState extends State { @override Widget build(BuildContext context) { - final dark = isDarkTheme(); RxBool fullscreen = false.obs; Get.put(fullscreen, tag: 'fullscreen'); return Obx(() => DragToResizeArea( diff --git a/flutter/lib/desktop/pages/port_forward_page.dart b/flutter/lib/desktop/pages/port_forward_page.dart index 28ee0d70e..6cfd0cdb2 100644 --- a/flutter/lib/desktop/pages/port_forward_page.dart +++ b/flutter/lib/desktop/pages/port_forward_page.dart @@ -70,45 +70,38 @@ class _PortForwardPageState extends State @override Widget build(BuildContext context) { super.build(context); - return Overlay(initialEntries: [ - OverlayEntry(builder: (context) { - _ffi.dialogManager.setOverlayState(Overlay.of(context)); - return Scaffold( - backgroundColor: MyTheme.color(context).grayBg, - body: FutureBuilder(future: () async { - if (!isRdp) { - refreshTunnelConfig(); - } - }(), builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - return Container( - decoration: BoxDecoration( - border: Border.all( - width: 20, color: MyTheme.color(context).grayBg!)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - buildPrompt(context), - Flexible( - child: Container( - decoration: BoxDecoration( - color: MyTheme.color(context).bg, - border: - Border.all(width: 1, color: MyTheme.border)), - child: widget.isRDP - ? buildRdp(context) - : buildTunnel(context), - ), - ), - ], + return Scaffold( + backgroundColor: MyTheme.color(context).grayBg, + body: FutureBuilder(future: () async { + if (!isRdp) { + refreshTunnelConfig(); + } + }(), builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + return Container( + decoration: BoxDecoration( + border: Border.all( + width: 20, color: MyTheme.color(context).grayBg!)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + buildPrompt(context), + Flexible( + child: Container( + decoration: BoxDecoration( + color: MyTheme.color(context).bg, + border: Border.all(width: 1, color: MyTheme.border)), + child: + widget.isRDP ? buildRdp(context) : buildTunnel(context), + ), ), - ); - } - return const Offstage(); - }), - ); - }) - ]); + ], + ), + ); + } + return const Offstage(); + }), + ); } buildPrompt(BuildContext context) { diff --git a/flutter/lib/desktop/pages/server_page.dart b/flutter/lib/desktop/pages/server_page.dart index 08f3e5836..be14a8537 100644 --- a/flutter/lib/desktop/pages/server_page.dart +++ b/flutter/lib/desktop/pages/server_page.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/mobile/pages/chat_page.dart'; import 'package:flutter_hbb/models/chat_model.dart'; @@ -63,20 +64,15 @@ class _DesktopServerPageState extends State border: Border.all(color: MyTheme.color(context).border!)), child: Scaffold( backgroundColor: MyTheme.color(context).bg, - body: Overlay(initialEntries: [ - OverlayEntry(builder: (context) { - gFFI.dialogManager.setOverlayState(Overlay.of(context)); - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Expanded(child: ConnectionManager()), - SizedBox.fromSize(size: Size(0, 15.0)), - ], - ), - ); - }) - ]), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded(child: ConnectionManager()), + SizedBox.fromSize(size: Size(0, 15.0)), + ], + ), + ), )))); } @@ -111,7 +107,7 @@ class ConnectionManagerState extends State { return serverModel.clients.isEmpty ? Column( children: [ - buildTitleBar(Offstage()), + buildTitleBar(), Expanded( child: Center( child: Text(translate("Waiting")), @@ -134,20 +130,27 @@ class ConnectionManagerState extends State { ])); } - Widget buildTitleBar(Widget middle) { - return GestureDetector( - onPanDown: (d) { - windowManager.startDragging(); - }, + Widget buildTitleBar() { + return SizedBox( + height: kDesktopRemoteTabBarHeight, child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - _AppIcon(), - Expanded(child: middle), + const _AppIcon(), + Expanded( + child: GestureDetector( + onPanStart: (d) { + windowManager.startDragging(); + }, + child: Container( + color: MyTheme.color(context).bg, + ), + ), + ), const SizedBox( width: 4.0, ), - _CloseButton() + const _CloseButton() ], ), ); @@ -209,15 +212,16 @@ class _CloseButton extends StatelessWidget { @override Widget build(BuildContext context) { - return Ink( - child: InkWell( - onTap: () { - windowManager.close(); - }, - child: Icon( - Icons.close, - size: 30, - )), + return IconButton( + onPressed: () { + windowManager.close(); + }, + icon: const Icon( + IconFont.close, + size: 18, + ), + splashColor: Colors.transparent, + hoverColor: Colors.transparent, ); } } diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 2f1d0680f..8efc7c6ad 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -43,12 +43,16 @@ Future main(List args) async { WindowType wType = type.windowType; switch (wType) { case WindowType.RemoteDesktop: + desktopType = DesktopType.remote; runRemoteScreen(argument); break; case WindowType.FileTransfer: + desktopType = DesktopType.fileTransfer; runFileTransferScreen(argument); break; case WindowType.PortForward: + desktopType = + argument['isRDP'] ? DesktopType.rdp : DesktopType.portForward; runPortForwardScreen(argument); break; default: @@ -56,19 +60,17 @@ Future main(List args) async { } } else if (args.isNotEmpty && args.first == '--cm') { print("--cm started"); + desktopType = DesktopType.cm; await windowManager.ensureInitialized(); runConnectionManagerScreen(); } else { + desktopType = DesktopType.main; await windowManager.ensureInitialized(); windowManager.setPreventClose(true); runMainApp(true); } } -ThemeData getCurrentTheme() { - return isDarkTheme() ? MyTheme.darkTheme : MyTheme.lightTheme; -} - Future initEnv(String appType) async { await platformFFI.init(appType); // global FFI, use this **ONLY** for global configuration @@ -77,6 +79,7 @@ Future initEnv(String appType) async { await initGlobalFFI(); // await Firebase.initializeApp(); refreshCurrentUser(); + _registerEventHandler(); } void runMainApp(bool startService) async { @@ -111,7 +114,9 @@ void runRemoteScreen(Map argument) async { navigatorKey: globalKey, debugShowCheckedModeBanner: false, title: 'RustDesk - Remote Desktop', - theme: getCurrentTheme(), + theme: MyTheme.lightTheme, + darkTheme: MyTheme.darkTheme, + themeMode: MyTheme.initialThemeMode(), home: DesktopRemoteScreen( params: argument, ), @@ -129,7 +134,9 @@ void runFileTransferScreen(Map argument) async { navigatorKey: globalKey, debugShowCheckedModeBanner: false, title: 'RustDesk - File Transfer', - theme: getCurrentTheme(), + theme: MyTheme.lightTheme, + darkTheme: MyTheme.darkTheme, + themeMode: MyTheme.initialThemeMode(), home: DesktopFileTransferScreen(params: argument), navigatorObservers: [ // FirebaseAnalyticsObserver(analytics: analytics), @@ -146,7 +153,9 @@ void runPortForwardScreen(Map argument) async { navigatorKey: globalKey, debugShowCheckedModeBanner: false, title: 'RustDesk - Port Forward', - theme: getCurrentTheme(), + theme: MyTheme.lightTheme, + darkTheme: MyTheme.darkTheme, + themeMode: MyTheme.initialThemeMode(), home: DesktopPortForwardScreen(params: argument), navigatorObservers: [ // FirebaseAnalyticsObserver(analytics: analytics), @@ -170,7 +179,9 @@ void runConnectionManagerScreen() async { ]); runApp(GetMaterialApp( debugShowCheckedModeBanner: false, - theme: getCurrentTheme(), + theme: MyTheme.lightTheme, + darkTheme: MyTheme.darkTheme, + themeMode: MyTheme.initialThemeMode(), home: DesktopServerPage(), builder: _keepScaleBuilder())); } @@ -185,7 +196,26 @@ WindowOptions getHiddenTitleBarWindowOptions(Size size) { ); } -class App extends StatelessWidget { +class App extends StatefulWidget { + @override + State createState() => _AppState(); +} + +class _AppState extends State { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.window.onPlatformBrightnessChanged = () { + WidgetsBinding.instance.handlePlatformBrightnessChanged(); + var system = + WidgetsBinding.instance.platformDispatcher.platformBrightness; + var current = isDarkTheme() ? Brightness.dark : Brightness.light; + if (current != system) { + MyTheme.changeTo(system == Brightness.dark); + } + }; + } + @override Widget build(BuildContext context) { // final analytics = FirebaseAnalytics.instance; @@ -204,7 +234,9 @@ class App extends StatelessWidget { navigatorKey: globalKey, debugShowCheckedModeBanner: false, title: 'RustDesk', - theme: getCurrentTheme(), + theme: MyTheme.lightTheme, + darkTheme: MyTheme.darkTheme, + themeMode: MyTheme.initialThemeMode(mainPage: true), home: isDesktop ? const DesktopTabPage() : !isAndroid @@ -238,3 +270,17 @@ _keepScaleBuilder() { ); }; } + +_registerEventHandler() { + if (desktopType != DesktopType.main) { + platformFFI.registerEventHandler('theme', 'theme', (evt) { + String? dark = evt['dark']; + if (dark != null) { + MyTheme.changeTo(dark == 'true'); + } + }); + platformFFI.registerEventHandler('language', 'language', (_) { + Get.forceAppUpdate(); + }); + } +} diff --git a/flutter/linux/main.cc b/flutter/linux/main.cc index e2ad70957..d409bfd2b 100644 --- a/flutter/linux/main.cc +++ b/flutter/linux/main.cc @@ -1,7 +1,7 @@ #include #include "my_application.h" -#define RUSTDESK_LIB_PATH "ibrustdesk.so" +#define RUSTDESK_LIB_PATH "librustdesk.so" // #define RUSTDESK_LIB_PATH "/usr/lib/rustdesk/librustdesk.so" typedef bool (*RustDeskCoreMain)(); diff --git a/src/flutter.rs b/src/flutter.rs index 4be998027..b22c0da83 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -17,6 +17,8 @@ use crate::{client::*, flutter_ffi::EventToUI}; pub(super) const APP_TYPE_MAIN: &str = "main"; pub(super) const APP_TYPE_DESKTOP_REMOTE: &str = "remote"; pub(super) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer"; +pub(super) const APP_TYPE_DESKTOP_PORT_FORWARD: &str = "port forward"; +pub(super) const APP_TYPE_DESKTOP_RDP: &str = "rdp"; lazy_static::lazy_static! { pub static ref SESSIONS: RwLock>> = Default::default(); @@ -376,6 +378,14 @@ pub mod connection_manager { vec![("id", &id.to_string()), ("text", &text)], ); } + + fn change_theme(&self, dark: bool) { + self.push_event("theme", vec![("dark", &dark.to_string())]); + } + + fn change_language(&self) { + self.push_event("language", vec![]); + } } impl FlutterHandler { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 977c03197..56d69f4c4 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -23,7 +23,7 @@ use crate::ui_interface::{ get_app_name, get_async_job_status, get_connect_status, get_fav, get_id, get_lan_peers, get_langs, get_license, get_local_option, get_mouse_time, get_option, get_options, get_peer, get_peer_option, get_socks, get_sound_inputs, get_uuid, get_version, has_hwcodec, - has_rendezvous_service, post_request, set_local_option, set_option, set_options, + has_rendezvous_service, post_request, send_to_cm, set_local_option, set_option, set_options, set_peer_option, set_permanent_password, set_socks, store_fav, test_if_valid_server, update_temporary_password, using_public_server, }; @@ -659,6 +659,34 @@ pub fn main_load_lan_peers() { }; } +fn main_broadcast_message(data: &HashMap<&str, &str>) { + let apps = vec![ + flutter::APP_TYPE_DESKTOP_REMOTE, + flutter::APP_TYPE_DESKTOP_FILE_TRANSFER, + flutter::APP_TYPE_DESKTOP_PORT_FORWARD, + flutter::APP_TYPE_DESKTOP_RDP, + ]; + + for app in apps { + if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(app) { + s.add(serde_json::ser::to_string(data).unwrap_or("".to_owned())); + }; + } +} + +pub fn main_change_theme(dark: bool) { + main_broadcast_message(&HashMap::from([ + ("name", "theme"), + ("dark", &dark.to_string()), + ])); + send_to_cm(&crate::ipc::Data::Theme(dark)); +} + +pub fn main_change_language(lang: String) { + main_broadcast_message(&HashMap::from([("name", "language"), ("lang", &lang)])); + send_to_cm(&crate::ipc::Data::Language(lang)); +} + pub fn session_add_port_forward( id: String, local_port: i32, diff --git a/src/ipc.rs b/src/ipc.rs index 0bdc3f43b..2d841755b 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -182,6 +182,8 @@ pub enum Data { #[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))] Mouse(DataMouse), Control(DataControl), + Theme(bool), + Language(String), Empty, } diff --git a/src/ui/cm.rs b/src/ui/cm.rs index 2b1e3e791..c5f64699a 100644 --- a/src/ui/cm.rs +++ b/src/ui/cm.rs @@ -47,6 +47,14 @@ impl InvokeUiCM for SciterHandler { fn new_message(&self, id: i32, text: String) { self.call("newMessage", &make_args!(id, text)); } + + fn change_theme(&self, _dark: bool) { + // TODO + } + + fn change_language(&self) { + // TODO + } } impl SciterHandler { diff --git a/src/ui/msgbox.tis b/src/ui/msgbox.tis index a622a45b8..b7df30717 100644 --- a/src/ui/msgbox.tis +++ b/src/ui/msgbox.tis @@ -63,7 +63,7 @@ class MsgboxComponent: Reactor.Component { var ts = this.auto_login ? { checked: true } : {}; return
-
{translate('Auto Login')}
+
{translate('Auto Login')}
; } return this.content; diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index d416fdd63..a7082e9ee 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -60,6 +60,10 @@ pub trait InvokeUiCM: Send + Clone + 'static + Sized { fn remove_connection(&self, id: i32); fn new_message(&self, id: i32, text: String); + + fn change_theme(&self, dark: bool); + + fn change_language(&self); } impl Deref for ConnectionManager { @@ -198,6 +202,8 @@ pub enum ClipboardFileData { #[cfg(not(any(target_os = "android", target_os = "ios")))] #[tokio::main(flavor = "current_thread")] pub async fn start_ipc(cm: ConnectionManager) { + use hbb_common::config::LocalConfig; + let (tx_file, _rx_file) = mpsc::unbounded_channel::(); #[cfg(windows)] let cm_clip = cm.clone(); @@ -280,6 +286,13 @@ pub async fn start_ipc(cm: ConnectionManager) { .send(ClipboardFileData::Enable((conn_id, enabled))) .ok(); } + Data::Theme(dark) => { + cm.change_theme(dark); + } + Data::Language(lang) => { + LocalConfig::set_option("lang".to_owned(), lang); + cm.change_language(); + } _ => { } diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 186381ce4..c7b743ccb 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -373,7 +373,8 @@ pub fn get_mouse_time() -> f64 { } pub fn check_mouse_time() { - #[cfg(not(any(target_os = "android", target_os = "ios")))]{ + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { let sender = SENDER.lock().unwrap(); allow_err!(sender.send(ipc::Data::MouseMoveTime(0))); } @@ -779,6 +780,13 @@ pub(crate) async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedRe } } +#[tokio::main(flavor = "current_thread")] +pub(crate) async fn send_to_cm(data: &ipc::Data) { + if let Ok(mut c) = ipc::connect(1000, "_cm").await { + c.send(data).await.ok(); + } +} + const INVALID_FORMAT: &'static str = "Invalid format"; const UNKNOWN_ERROR: &'static str = "Unknown error";