diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 6ee57ef50..cf7de0fa2 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -205,6 +205,9 @@ class MyTheme { splashColor: Colors.transparent, highlightColor: Colors.transparent, splashFactory: isDesktop ? NoSplash.splashFactory : null, + outlinedButtonTheme: OutlinedButtonThemeData( + style: + OutlinedButton.styleFrom(side: BorderSide(color: Colors.white38))), textButtonTheme: isDesktop ? TextButtonThemeData( style: ButtonStyle(splashFactory: NoSplash.splashFactory), @@ -613,6 +616,7 @@ class CustomAlertDialog extends StatelessWidget { Future.delayed(Duration.zero, () { if (!scopeNode.hasFocus) scopeNode.requestFocus(); }); + const double padding = 16; return FocusScope( node: scopeNode, autofocus: true, @@ -637,17 +641,18 @@ class CustomAlertDialog extends StatelessWidget { child: AlertDialog( scrollable: true, title: title, - contentPadding: EdgeInsets.symmetric( - horizontal: contentPadding ?? 25, vertical: 10), + contentPadding: EdgeInsets.fromLTRB( + contentPadding ?? padding, 25, contentPadding ?? padding, 10), content: ConstrainedBox( - constraints: contentBoxConstraints, - child: Theme( - data: ThemeData( + constraints: contentBoxConstraints, + child: Theme( + data: Theme.of(context).copyWith( inputDecorationTheme: InputDecorationTheme( - isDense: true, contentPadding: EdgeInsets.all(15)), - ), - child: content)), + isDense: true, contentPadding: EdgeInsets.all(15))), + child: content), + ), actions: actions, + actionsPadding: EdgeInsets.fromLTRB(0, 0, padding, padding), ), ); } @@ -701,9 +706,8 @@ void msgBox(String id, String type, String title, String text, String link, } dialogManager.show( (setState, close) => CustomAlertDialog( - title: _msgBoxTitle(title), - content: - SelectableText(translate(text), style: const TextStyle(fontSize: 15)), + title: null, + content: msgboxContent(type, title, text), actions: buttons, onSubmit: hasOk ? submit : null, onCancel: hasCancel == true ? cancel : null, @@ -712,30 +716,74 @@ void msgBox(String id, String type, String title, String text, String link, ); } -Widget msgBoxButton(String text, void Function() onPressed) { - return ButtonTheme( - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - //limits the touch area to the button area - minWidth: 0, - //wraps child's width - height: 0, - child: TextButton( - style: flatButtonStyle, - onPressed: onPressed, - child: - Text(translate(text), style: TextStyle(color: MyTheme.accent)))); +Color? _msgboxColor(String type) { + if (type == "input-password" || type == "custom-os-password") { + return Color(0xFFAD448E); + } + if (type.contains("success")) { + return Color(0xFF32bea6); + } + if (type.contains("error") || type == "re-input-password") { + return Color(0xFFE04F5F); + } + return Color(0xFF2C8CFF); } -Widget _msgBoxTitle(String title) => - Text(translate(title), style: TextStyle(fontSize: 21)); +Widget msgboxIcon(String type) { + IconData? iconData; + if (type.contains("error") || type == "re-input-password") { + iconData = Icons.cancel; + } + if (type.contains("success")) { + iconData = Icons.check_circle; + } + if (type == "wait-uac" || type == "wait-remote-accept-nook") { + iconData = Icons.hourglass_top; + } + if (type == 'on-uac' || type == 'on-foreground-elevated') { + iconData = Icons.admin_panel_settings; + } + if (type == "info") { + iconData = Icons.info; + } + if (iconData != null) { + return Icon(iconData, size: 50, color: _msgboxColor(type)) + .marginOnly(right: 16); + } + + return Offstage(); +} + +// title should be null +Widget msgboxContent(String type, String title, String text) { + return Row( + children: [ + msgboxIcon(type), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + translate(title), + style: TextStyle(fontSize: 21), + ).marginOnly(bottom: 10), + Text(translate(text), style: const TextStyle(fontSize: 15)), + ], + ), + ), + ], + ); +} void msgBoxCommon(OverlayDialogManager dialogManager, String title, Widget content, List buttons, {bool hasCancel = true}) { dialogManager.dismissAll(); dialogManager.show((setState, close) => CustomAlertDialog( - title: _msgBoxTitle(title), + title: Text( + translate(title), + style: TextStyle(fontSize: 21), + ), content: content, actions: buttons, onCancel: hasCancel ? close : null, @@ -1589,7 +1637,8 @@ class ServerConfig { Widget dialogButton(String text, {required VoidCallback? onPressed, bool isOutline = false, - TextStyle? style}) { + TextStyle? style, + ButtonStyle? buttonStyle}) { if (isDesktop) { if (isOutline) { return OutlinedButton( @@ -1598,7 +1647,7 @@ Widget dialogButton(String text, ); } else { return ElevatedButton( - style: ElevatedButton.styleFrom(elevation: 0), + style: ElevatedButton.styleFrom(elevation: 0).merge(buttonStyle), onPressed: onPressed, child: Text(translate(text), style: style), ); diff --git a/flutter/lib/common/widgets/overlay.dart b/flutter/lib/common/widgets/overlay.dart index d9684bace..aaf52fb07 100644 --- a/flutter/lib/common/widgets/overlay.dart +++ b/flutter/lib/common/widgets/overlay.dart @@ -1,3 +1,4 @@ +import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common.dart'; import 'package:provider/provider.dart'; @@ -316,44 +317,49 @@ class _DraggableState extends State { } class QualityMonitor extends StatelessWidget { - static const textStyle = TextStyle(color: MyTheme.grayBg); final QualityMonitorModel qualityMonitorModel; QualityMonitor(this.qualityMonitorModel); + Widget _row(String info, String? value) { + return Row( + children: [ + Expanded( + flex: 8, + child: AutoSizeText(info, + style: TextStyle(color: MyTheme.grayBg), + textAlign: TextAlign.right, + maxLines: 1)), + Spacer(flex: 1), + Expanded( + flex: 8, + child: AutoSizeText(value ?? '', + style: TextStyle(color: MyTheme.grayBg), maxLines: 1)), + ], + ); + } + @override Widget build(BuildContext context) => ChangeNotifierProvider.value( value: qualityMonitorModel, child: Consumer( - builder: (context, qualityMonitorModel, 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())); + builder: (context, qualityMonitorModel, child) => qualityMonitorModel + .show + ? Container( + constraints: BoxConstraints(maxWidth: 200), + padding: const EdgeInsets.all(8), + color: MyTheme.canvasColor.withAlpha(120), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _row("Speed", qualityMonitorModel.data.speed ?? ''), + _row("FPS", qualityMonitorModel.data.fps ?? ''), + _row( + "Delay", "${qualityMonitorModel.data.delay ?? ''}ms"), + _row("Target Bitrate", + "${qualityMonitorModel.data.targetBitrate ?? ''}kb"), + _row("Codec", qualityMonitorModel.data.codecFormat ?? ''), + ], + ), + ) + : const SizedBox.shrink())); } diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 7695bc51c..517dc9750 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1432,12 +1432,8 @@ void showConfirmSwitchSidesDialog( } return CustomAlertDialog( - title: Text(translate('Switch Sides')), - content: Column( - children: [ - Text(translate('Please confirm if you want to share your desktop?')), - ], - ), + content: msgboxContent('info', 'Switch Sides', + 'Please confirm if you want to share your desktop?'), actions: [ dialogButton('Cancel', onPressed: close, isOutline: true), dialogButton('OK', onPressed: submit), diff --git a/flutter/lib/mobile/widgets/dialog.dart b/flutter/lib/mobile/widgets/dialog.dart index 0eb403833..bded6d069 100644 --- a/flutter/lib/mobile/widgets/dialog.dart +++ b/flutter/lib/mobile/widgets/dialog.dart @@ -9,7 +9,7 @@ import '../../models/model.dart'; import '../../models/platform_model.dart'; void clientClose(String id, OverlayDialogManager dialogManager) { - msgBox(id, '', 'Close', 'Are you sure to close the connection?', '', + msgBox(id, 'info', 'Close', 'Are you sure to close the connection?', '', dialogManager); } @@ -33,8 +33,10 @@ void showRestartRemoteDevice( ]), content: Text( "${translate('Are you sure you want to restart')} \n${pi.username}@${pi.hostname}($id) ?"), + onCancel: close, + onSubmit: () => close(true), actions: [ - dialogButton("Cancel", onPressed: () => close(), isOutline: true), + dialogButton("Cancel", onPressed: close, isOutline: true), dialogButton("OK", onPressed: () => close(true)), ], )); @@ -48,6 +50,18 @@ void setPermanentPasswordDialog(OverlayDialogManager dialogManager) async { var validateLength = false; var validateSame = false; dialogManager.show((setState, close) { + submit() async { + close(); + dialogManager.showLoading(translate("Waiting")); + if (await gFFI.serverModel.setPermanentPassword(p0.text)) { + dialogManager.dismissAll(); + showSuccess(); + } else { + dialogManager.dismissAll(); + showError(); + } + } + return CustomAlertDialog( title: Text(translate('Set your own password')), content: Form( @@ -94,29 +108,17 @@ void setPermanentPasswordDialog(OverlayDialogManager dialogManager) async { }, ), ])), + onCancel: close, + onSubmit: (validateLength && validateSame) ? submit : null, actions: [ dialogButton( 'Cancel', - onPressed: () { - close(); - }, + onPressed: close, isOutline: true, ), dialogButton( 'OK', - onPressed: (validateLength && validateSame) - ? () async { - close(); - dialogManager.showLoading(translate("Waiting")); - if (await gFFI.serverModel.setPermanentPassword(p0.text)) { - dialogManager.dismissAll(); - showSuccess(); - } else { - dialogManager.dismissAll(); - showError(); - } - } - : null, + onPressed: (validateLength && validateSame) ? submit : null, ), ], ); @@ -205,26 +207,36 @@ void enterPasswordDialog(String id, OverlayDialogManager dialogManager) async { }); } -void wrongPasswordDialog(String id, OverlayDialogManager dialogManager) { - dialogManager.show((setState, close) => CustomAlertDialog( - title: Text(translate('Wrong Password')), - content: Text(translate('Do you want to enter again?')), - actions: [ - dialogButton( - 'Cancel', - onPressed: () { - close(); - closeConnection(); - }, - isOutline: true, - ), - dialogButton( - 'Retry', - onPressed: () { - enterPasswordDialog(id, dialogManager); - }, - ), - ])); +void wrongPasswordDialog( + String id, OverlayDialogManager dialogManager, type, title, text) { + dialogManager.dismissAll(); + dialogManager.show((setState, close) { + cancel() { + close(); + closeConnection(); + } + + submit() { + enterPasswordDialog(id, dialogManager); + } + + return CustomAlertDialog( + title: null, + content: msgboxContent(type, title, text), + onSubmit: submit, + onCancel: cancel, + actions: [ + dialogButton( + 'Cancel', + onPressed: cancel, + isOutline: true, + ), + dialogButton( + 'Retry', + onPressed: submit, + ), + ]); + }); } void showServerSettingsWithValue( @@ -352,13 +364,15 @@ void showServerSettingsWithValue( }); } -void showWaitUacDialog(String id, OverlayDialogManager dialogManager) { +void showWaitUacDialog( + String id, OverlayDialogManager dialogManager, String type) { dialogManager.dismissAll(); dialogManager.show( tag: '$id-wait-uac', (setState, close) => CustomAlertDialog( - title: Text(translate('Wait')), - content: Text(translate('wait_accept_uac_tip')).marginAll(10), + title: null, + content: msgboxContent(type, 'Wait', 'wait_accept_uac_tip') + .marginOnly(bottom: 10), )); } @@ -516,16 +530,6 @@ void showOnBlockDialog( dialogManager.existing('$id-request-elevation')) { return; } - var content = Column(children: [ - Align( - alignment: Alignment.centerLeft, - child: Text( - "${translate(text)}${type.contains('uac') ? '\n' : '\n\n'}${translate('request_elevation_tip')}", - textAlign: TextAlign.left, - style: TextStyle(fontWeight: FontWeight.w400), - ).marginSymmetric(vertical: 15), - ), - ]); dialogManager.show(tag: '$id-$type', (setState, close) { void submit() { close(); @@ -533,12 +537,11 @@ void showOnBlockDialog( } return CustomAlertDialog( - title: Text(translate(title)), - content: content, + title: null, + content: msgboxContent(type, title, + "${translate(text)}${type.contains('uac') ? '\n' : '\n\n'}${translate('request_elevation_tip')}"), actions: [ - dialogButton('Wait', onPressed: () { - close(); - }, isOutline: true), + dialogButton('Wait', onPressed: close, isOutline: true), dialogButton('Request Elevation', onPressed: submit), ], onSubmit: submit, @@ -556,8 +559,8 @@ void showElevationError(String id, String type, String title, String text, } return CustomAlertDialog( - title: Text(translate(title)), - content: Text(translate(text)), + title: null, + content: msgboxContent(type, title, text), actions: [ dialogButton('Cancel', onPressed: () { close(); @@ -570,6 +573,25 @@ void showElevationError(String id, String type, String title, String text, }); } +void showWaitAcceptDialog(String id, String type, String title, String text, + OverlayDialogManager dialogManager) { + dialogManager.dismissAll(); + dialogManager.show((setState, close) { + onCancel() { + closeConnection(); + } + + return CustomAlertDialog( + title: null, + content: msgboxContent(type, title, text), + actions: [ + dialogButton('Cancel', onPressed: onCancel, isOutline: true), + ], + onCancel: onCancel, + ); + }); +} + Future validateAsync(String value) async { value = value.trim(); if (value.isEmpty) { diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 1f4fbb8f0..1eac1be39 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -264,19 +264,18 @@ class FfiModel with ChangeNotifier { final text = evt['text']; final link = evt['link']; if (type == 're-input-password') { - wrongPasswordDialog(id, dialogManager); + wrongPasswordDialog(id, dialogManager, type, title, text); } else if (type == 'input-password') { enterPasswordDialog(id, dialogManager); } else if (type == 'restarting') { showMsgBox(id, type, title, text, link, false, dialogManager, hasCancel: false); } else if (type == 'wait-remote-accept-nook') { - msgBoxCommon(dialogManager, title, Text(translate(text)), - [dialogButton("Cancel", onPressed: closeConnection)]); + showWaitAcceptDialog(id, type, title, text, dialogManager); } else if (type == 'on-uac' || type == 'on-foreground-elevated') { showOnBlockDialog(id, type, title, text, dialogManager); } else if (type == 'wait-uac') { - showWaitUacDialog(id, dialogManager); + showWaitUacDialog(id, dialogManager, type); } else if (type == 'elevation-error') { showElevationError(id, type, title, text, dialogManager); } else { diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index ff6d6c004..f4ecbded5 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1104,7 +1104,7 @@ impl Remote { Some(misc::Union::PortableServiceRunning(b)) => { if b { self.handler.msgbox( - "custom-nocancel", + "custom-nocancel-success", "Successful", "Elevate successfully", "", diff --git a/src/platform/windows.rs b/src/platform/windows.rs index b778283a5..2e0d56eab 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -1745,3 +1745,13 @@ pub fn create_process_with_logon(user: &str, pwd: &str, exe: &str, arg: &str) -> } return Ok(()); } + +pub fn set_path_permission(dir: &PathBuf, permission: &str) -> ResultType<()> { + std::process::Command::new("icacls") + .arg(dir.as_os_str()) + .arg("/grant") + .arg(format!("Everyone:(OI)(CI){}", permission)) + .arg("/T") + .spawn()?; + Ok(()) +} diff --git a/src/server/portable_service.rs b/src/server/portable_service.rs index 748cb39e4..a2f6fb829 100644 --- a/src/server/portable_service.rs +++ b/src/server/portable_service.rs @@ -2,9 +2,7 @@ use core::slice; use hbb_common::{ allow_err, anyhow::anyhow, - bail, - config::Config, - log, + bail, log, message_proto::{KeyEvent, MouseEvent}, protobuf::Message, tokio::{self, sync::mpsc}, @@ -15,6 +13,7 @@ use shared_memory::*; use std::{ mem::size_of, ops::{Deref, DerefMut}, + path::PathBuf, sync::{Arc, Mutex}, time::Duration, }; @@ -25,6 +24,7 @@ use winapi::{ use crate::{ ipc::{self, new_listener, Connection, Data, DataPortableService}, + platform::set_path_permission, video_service::get_current_display, }; @@ -72,7 +72,7 @@ impl DerefMut for SharedMemory { impl SharedMemory { pub fn create(name: &str, size: usize) -> ResultType { - let flink = Self::flink(name.to_string()); + let flink = Self::flink(name.to_string())?; let shmem = match ShmemConf::new() .size(size) .flink(&flink) @@ -91,12 +91,12 @@ impl SharedMemory { } }; log::info!("Create shared memory, size:{}, flink:{}", size, flink); - Self::set_all_perm(&flink); + set_path_permission(&PathBuf::from(flink), "F").ok(); Ok(SharedMemory { inner: shmem }) } pub fn open_existing(name: &str) -> ResultType { - let flink = Self::flink(name.to_string()); + let flink = Self::flink(name.to_string())?; let shmem = match ShmemConf::new().flink(&flink).allow_raw(true).open() { Ok(m) => m, Err(e) => { @@ -116,30 +116,29 @@ impl SharedMemory { } } - fn flink(name: String) -> String { - let mut shmem_flink = format!("shared_memory{}", name); - if cfg!(windows) { - let df = "C:\\ProgramData"; - let df = if std::path::Path::new(df).exists() { - df.to_owned() - } else { - std::env::var("TEMP").unwrap_or("C:\\Windows\\TEMP".to_owned()) - }; - let df = format!("{}\\{}", df, *hbb_common::config::APP_NAME.read().unwrap()); - std::fs::create_dir(&df).ok(); - shmem_flink = format!("{}\\{}", df, shmem_flink); + fn flink(name: String) -> ResultType { + let disk = std::env::var("SystemDrive").unwrap_or("C:".to_string()); + let mut dir = PathBuf::from(disk); + let dir1 = dir.join("ProgramData"); + let dir2 = std::env::var("TEMP") + .map(|d| PathBuf::from(d)) + .unwrap_or(dir.join("Windows").join("Temp")); + if dir1.exists() { + dir = dir1; + } else if dir2.exists() { + dir = dir2; } else { - shmem_flink = Config::ipc_path("").replace("ipc", "") + &shmem_flink; + bail!("no vaild flink directory"); } - return shmem_flink; - } - - fn set_all_perm(_p: &str) { - #[cfg(not(windows))] - { - use std::os::unix::fs::PermissionsExt; - std::fs::set_permissions(_p, std::fs::Permissions::from_mode(0o0777)).ok(); + dir = dir.join(hbb_common::config::APP_NAME.read().unwrap().clone()); + if !dir.exists() { + std::fs::create_dir(&dir)?; + set_path_permission(&dir, "F").ok(); } + Ok(dir + .join(format!("shared_memory{}", name)) + .to_string_lossy() + .to_string()) } } @@ -451,7 +450,6 @@ pub mod server { // functions called in main process. pub mod client { use hbb_common::anyhow::Context; - use std::path::PathBuf; use super::*; @@ -515,7 +513,7 @@ pub mod client { #[cfg(feature = "flutter")] { if let Some(dir) = PathBuf::from(&exe).parent() { - if !set_dir_permission(&PathBuf::from(dir)) { + if set_path_permission(&PathBuf::from(dir), "RX").is_err() { *SHMEM.lock().unwrap() = None; bail!("Failed to set permission of {:?}", dir); } @@ -533,7 +531,7 @@ pub mod client { let dst = dir.join("rustdesk.exe"); if std::fs::copy(&exe, &dst).is_ok() { if dst.exists() { - if set_dir_permission(&dir) { + if set_path_permission(&dir, "RX").is_ok() { exe = dst.to_string_lossy().to_string(); } } @@ -566,16 +564,6 @@ pub mod client { *QUICK_SUPPORT.lock().unwrap() = v; } - fn set_dir_permission(dir: &PathBuf) -> bool { - // // give Everyone RX permission - std::process::Command::new("icacls") - .arg(dir.as_os_str()) - .arg("/grant") - .arg("Everyone:(OI)(CI)RX") - .arg("/T") - .spawn() - .is_ok() - } pub struct CapturerPortable; impl CapturerPortable { diff --git a/src/ui/remote.css b/src/ui/remote.css index 66c5ce80f..71b2c1682 100644 --- a/src/ui/remote.css +++ b/src/ui/remote.css @@ -16,7 +16,7 @@ div#quality-monitor { padding: 5px; min-width: 150px; color: azure; - border: solid azure; + border: 0.5px solid azure; } video#handler {