Merge pull request #1698 from 21pages/install-page

windows install page
This commit is contained in:
RustDesk 2022-10-11 15:28:50 +08:00 committed by GitHub
commit 5756bee266
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 403 additions and 36 deletions

View File

@ -189,7 +189,7 @@ def build_flutter_arch_manjaro():
os.chdir('..')
os.system('HBB=`pwd` FLUTTER=1 makepkg -f')
def build_flutter_windows_portable():
def build_flutter_windows(version):
os.system("cargo build --lib --features flutter --release")
os.chdir('flutter')
os.system("flutter build windows --release")
@ -203,6 +203,8 @@ def build_flutter_windows_portable():
else:
os.rename("./target/release/rustdesk-portable-packer.exe", "./rustdesk_portable.exe")
print(f"output location: {os.path.abspath(os.curdir)}/rustdesk_portable.exe")
os.system(f"cp -rf ./rustdesk_portable.exe ./rustdesk-{version}-install.exe")
print(f"output location: {os.path.abspath(os.curdir)}/rustdesk-{version}-install.exe")
def main():
parser = make_parser()
@ -227,8 +229,8 @@ def main():
os.system('python3 res/inline-sciter.py')
portable = args.portable
if windows:
if portable:
build_flutter_windows_portable()
if flutter:
build_flutter_windows(version)
return
os.system('cargo build --release --features ' + features)
# os.system('upx.exe target/release/rustdesk.exe')
@ -239,7 +241,7 @@ def main():
'target\\release\\rustdesk.exe')
else:
print('Not signed')
os.system(f'cp -rf target/release/RustDesk.exe rustdesk-{version}-setdown.exe')
os.system(f'cp -rf target/release/RustDesk.exe rustdesk-{version}-win7-install.exe')
elif os.path.isfile('/usr/bin/pacman'):
# pacman -S -needed base-devel
os.system("sed -i 's/pkgver=.*/pkgver=%s/g' PKGBUILD" % version)

View File

@ -3,7 +3,7 @@ import 'dart:io';
const double kDesktopRemoteTabBarHeight = 28.0;
/// [kAppTypeMain] used by 'Desktop Main Page' , 'Mobile (Client and Server)' , 'Desktop CM Page'
/// [kAppTypeMain] used by 'Desktop Main Page' , 'Mobile (Client and Server)' , 'Desktop CM Page', "Install Page"
const String kAppTypeMain = "main";
const String kAppTypeDesktopRemote = "remote";
const String kAppTypeDesktopFileTransfer = "file transfer";

View File

@ -0,0 +1,198 @@
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:window_manager/window_manager.dart';
class InstallPage extends StatefulWidget {
const InstallPage({Key? key}) : super(key: key);
@override
State<InstallPage> createState() => _InstallPageState();
}
class _InstallPageState extends State<InstallPage> with WindowListener {
late final TextEditingController controller;
final RxBool startmenu = true.obs;
final RxBool desktopicon = true.obs;
final RxBool showProgress = false.obs;
final RxBool btnEnabled = true.obs;
@override
void initState() {
windowManager.addListener(this);
controller = TextEditingController(text: bind.installInstallPath());
super.initState();
}
@override
void dispose() {
windowManager.removeListener(this);
super.dispose();
}
@override
void onWindowClose() {
gFFI.close();
super.onWindowClose();
windowManager.setPreventClose(false);
windowManager.close();
}
@override
Widget build(BuildContext context) {
final double em = 13;
final btnFontSize = 0.9 * em;
final double button_radius = 6;
final buttonStyle = OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(button_radius)),
));
final inputBorder = OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderSide: BorderSide(color: Colors.black12));
return Scaffold(
backgroundColor: Colors.white,
body: SingleChildScrollView(
child: Column(
children: [
Row(
children: [
Text(
translate('Installation'),
style: TextStyle(
fontSize: 2 * em, fontWeight: FontWeight.w500),
),
],
),
Row(
children: [
Text('${translate('Installation Path')}: '),
Expanded(
child: TextField(
controller: controller,
readOnly: true,
style: TextStyle(
fontSize: 1.5 * em, fontWeight: FontWeight.w400),
decoration: InputDecoration(
isDense: true,
contentPadding: EdgeInsets.all(0.75 * em),
enabledBorder: inputBorder,
border: inputBorder,
focusedBorder: inputBorder,
constraints: BoxConstraints(maxHeight: 3 * em),
),
)),
Obx(() => OutlinedButton(
onPressed:
btnEnabled.value ? selectInstallPath : null,
style: buttonStyle,
child: Text(translate('Change Path'),
style: TextStyle(
color: Colors.black87,
fontSize: btnFontSize)))
.marginOnly(left: em))
],
).marginSymmetric(vertical: 2 * em),
Row(
children: [
Obx(() => Checkbox(
value: startmenu.value,
onChanged: (b) {
if (b != null) startmenu.value = b;
})),
Text(translate('Create start menu shortcuts'))
],
),
Row(
children: [
Obx(() => Checkbox(
value: desktopicon.value,
onChanged: (b) {
if (b != null) desktopicon.value = b;
})),
Text(translate('Create desktop icon'))
],
),
GestureDetector(
onTap: () => launchUrlString('http://rustdesk.com/privacy'),
child: Row(
children: [
Text(translate('End-user license agreement'),
style: const TextStyle(
decoration: TextDecoration.underline))
],
)).marginOnly(top: 2 * em),
Row(children: [Text(translate('agreement_tip'))])
.marginOnly(top: em),
Divider(color: Colors.black87)
.marginSymmetric(vertical: 0.5 * em),
Row(
children: [
Expanded(
child: Obx(() => Offstage(
offstage: !showProgress.value,
child: LinearProgressIndicator(),
))),
Obx(() => OutlinedButton(
onPressed: btnEnabled.value
? () => windowManager.close()
: null,
style: buttonStyle,
child: Text(translate('Cancel'),
style: TextStyle(
color: Colors.black87,
fontSize: btnFontSize)))
.marginOnly(right: 2 * em)),
Obx(() => ElevatedButton(
onPressed: btnEnabled.value ? install : null,
style: ElevatedButton.styleFrom(
primary: MyTheme.button,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(button_radius)),
)),
child: Text(
translate('Accept and Install'),
style: TextStyle(fontSize: btnFontSize),
))),
Offstage(
offstage: bind.installShowRunWithoutInstall(),
child: Obx(() => OutlinedButton(
onPressed: btnEnabled.value
? () => bind.installRunWithoutInstall()
: null,
style: buttonStyle,
child: Text(translate('Run without install'),
style: TextStyle(
color: Colors.black87,
fontSize: btnFontSize)))
.marginOnly(left: 2 * em)),
),
],
)
],
).paddingSymmetric(horizontal: 8 * em, vertical: 2 * em),
));
}
void install() {
btnEnabled.value = false;
showProgress.value = true;
String args = '--flutter';
if (startmenu.value) args += ' startmenu';
if (desktopicon.value) args += ' desktopicon';
bind.installInstallMe(options: args, path: controller.text);
}
void selectInstallPath() async {
String? install_path = await FilePicker.platform
.getDirectoryPath(initialDirectory: controller.text);
if (install_path != null) {
install_path = '$install_path\\${await bind.mainGetAppName()}';
controller.text = install_path;
}
}
}

View File

@ -4,6 +4,7 @@ import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
import 'package:flutter_hbb/desktop/pages/server_page.dart';
import 'package:flutter_hbb/desktop/pages/install_page.dart';
import 'package:flutter_hbb/desktop/screen/desktop_file_transfer_screen.dart';
import 'package:flutter_hbb/desktop/screen/desktop_port_forward_screen.dart';
import 'package:flutter_hbb/desktop/screen/desktop_remote_screen.dart';
@ -64,6 +65,8 @@ Future<void> main(List<String> args) async {
desktopType = DesktopType.cm;
await windowManager.ensureInitialized();
runConnectionManagerScreen();
} else if (args.contains('--install')) {
runInstallPage();
} else {
desktopType = DesktopType.main;
await windowManager.ensureInitialized();
@ -215,6 +218,30 @@ void runConnectionManagerScreen() async {
});
}
void runInstallPage() async {
await windowManager.ensureInitialized();
await initEnv(kAppTypeMain);
runApp(GetMaterialApp(
debugShowCheckedModeBanner: false,
theme: MyTheme.lightTheme,
themeMode: ThemeMode.light,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: supportedLocales,
home: const InstallPage(),
builder: _keepScaleBuilder()));
windowManager.waitUntilReadyToShow(
WindowOptions(size: Size(800, 600), center: true), () async {
windowManager.show();
windowManager.focus();
windowManager.setOpacity(1);
windowManager.setAlignment(Alignment.center); // ensure
});
}
WindowOptions getHiddenTitleBarWindowOptions({Size? size}) {
return WindowOptions(
size: size,

View File

@ -7,7 +7,8 @@
#include "utils.h"
// #include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
typedef bool (*FUNC_RUSTDESK_CORE_MAIN)(void);
typedef char** (*FUNC_RUSTDESK_CORE_MAIN)(int*);
typedef void (*FUNC_RUSTDESK_FREE_ARGS)( char**, int);
// auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP);
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
@ -26,11 +27,23 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
std::cout << "Failed to get rustdesk_core_main" << std::endl;
return EXIT_FAILURE;
}
if (!rustdesk_core_main())
FUNC_RUSTDESK_FREE_ARGS free_c_args =
(FUNC_RUSTDESK_FREE_ARGS)GetProcAddress(hInstance, "free_c_args");
if (!free_c_args)
{
std::cout << "Failed to get free_c_args" << std::endl;
return EXIT_FAILURE;
}
int args_len = 0;
char** c_args = rustdesk_core_main(&args_len);
if (!c_args)
{
std::cout << "Rustdesk core returns false, exiting without launching Flutter app" << std::endl;
return EXIT_SUCCESS;
}
std::vector<std::string> rust_args(c_args, c_args + args_len);
free_c_args(c_args, args_len);
// Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger.
@ -48,6 +61,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
std::vector<std::string> command_line_arguments =
GetCommandLineArguments();
command_line_arguments.insert(command_line_arguments.end(), rust_args.begin(), rust_args.end());
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
FlutterWindow window(project);

View File

@ -12,29 +12,37 @@ pub mod bin_reader;
const APP_PREFIX: &str = "rustdesk";
const APPNAME_RUNTIME_ENV_KEY: &str = "RUSTDESK_APPNAME";
fn setup(reader: BinaryReader) -> Option<PathBuf> {
// home dir
if let Some(dir) = dirs::data_local_dir() {
let dir = dir.join(APP_PREFIX);
for file in reader.files.iter() {
file.write_to_file(&dir);
}
#[cfg(unix)]
reader.configure_permission(&dir);
Some(dir.join(&reader.exe))
fn setup(reader: BinaryReader, dir: Option<PathBuf>, clear: bool) -> Option<PathBuf> {
let dir = if let Some(dir) = dir {
dir
} else {
eprintln!("not found data local dir");
None
// home dir
if let Some(dir) = dirs::data_local_dir() {
dir.join(APP_PREFIX)
} else {
eprintln!("not found data local dir");
return None;
}
};
if clear {
std::fs::remove_dir_all(&dir).ok();
}
for file in reader.files.iter() {
file.write_to_file(&dir);
}
#[cfg(unix)]
reader.configure_permission(&dir);
Some(dir.join(&reader.exe))
}
fn execute(path: PathBuf) {
fn execute(path: PathBuf, args: Vec<String>) {
println!("executing {}", path.display());
// setup env
let exe = std::env::current_exe().unwrap();
let exe_name = exe.file_name().unwrap();
// run executable
Command::new(path)
.args(args)
.env(APPNAME_RUNTIME_ENV_KEY, exe_name)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
@ -43,9 +51,24 @@ fn execute(path: PathBuf) {
.expect(&format!("failed to execute {:?}", exe_name));
}
fn is_setup(name: &str) -> bool {
name.to_lowercase().ends_with("install.exe") || name.to_lowercase().ends_with("安装.exe")
}
fn main() {
let is_setup = is_setup(
&std::env::current_exe()
.unwrap()
.to_string_lossy()
.to_string(),
);
let reader = BinaryReader::default();
if let Some(exe) = setup(reader) {
execute(exe);
if let Some(exe) = setup(reader, None, is_setup) {
let args = if is_setup {
vec!["--install".to_owned()]
} else {
vec![]
};
execute(exe, args);
}
}

View File

@ -544,7 +544,7 @@ pub fn is_ip(id: &str) -> bool {
}
pub fn is_setup(name: &str) -> bool {
name.to_lowercase().ends_with("setdown.exe") || name.to_lowercase().ends_with("安装.exe")
name.to_lowercase().ends_with("install.exe") || name.to_lowercase().ends_with("安装.exe")
}
pub fn get_custom_rendezvous_server(custom: String) -> String {

View File

@ -6,6 +6,7 @@ pub fn core_main() -> Option<Vec<String>> {
// though async logger more efficient, but it also causes more problems, disable it for now
// let mut _async_logger_holder: Option<flexi_logger::LoggerHandle> = None;
let mut args = Vec::new();
let mut flutter_args = Vec::new();
let mut i = 0;
let mut is_setup = false;
let mut _is_elevate = false;
@ -25,13 +26,18 @@ pub fn core_main() -> Option<Vec<String>> {
}
i += 1;
}
if args.contains(&"--install".to_string()) {
is_setup = true;
}
if is_setup {
if args.is_empty() {
args.push("--install".to_owned());
} else if args[0] == "--noinstall" {
args.clear();
flutter_args.push("--install".to_string());
}
}
if args.contains(&"--noinstall".to_string()) {
args.clear();
}
if args.len() > 0 && args[0] == "--version" {
println!("{}", crate::VERSION);
return None;
@ -171,7 +177,10 @@ pub fn core_main() -> Option<Vec<String>> {
}
}
//_async_logger_holder.map(|x| x.flush());
Some(args)
#[cfg(feature = "flutter")]
return Some(flutter_args);
#[cfg(not(feature = "flutter"))]
return Some(args);
}
fn import_config(path: &str) {

View File

@ -1,5 +1,7 @@
use std::{
collections::HashMap,
ffi::CString,
os::raw::{c_char, c_int},
sync::{Arc, RwLock},
};
@ -24,6 +26,78 @@ lazy_static::lazy_static! {
pub static ref GLOBAL_EVENT_STREAM: RwLock<HashMap<String, StreamSink<String>>> = Default::default(); // rust to dart event channel
}
/// FFI for rustdesk core's main entry.
/// Return true if the app should continue running with UI(possibly Flutter), false if the app should exit.
#[cfg(not(windows))]
#[no_mangle]
pub extern "C" fn rustdesk_core_main() -> bool {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
return crate::core_main::core_main().is_some();
#[cfg(any(target_os = "android", target_os = "ios"))]
false
}
#[cfg(windows)]
#[no_mangle]
pub extern "C" fn rustdesk_core_main(args_len: *mut c_int) -> *mut *mut c_char {
unsafe { std::ptr::write(args_len, 0) };
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
if let Some(args) = crate::core_main::core_main() {
return rust_args_to_c_args(args, args_len);
}
return std::ptr::null_mut() as _;
}
#[cfg(any(target_os = "android", target_os = "ios"))]
return std::ptr::null_mut() as _;
}
// https://gist.github.com/iskakaushik/1c5b8aa75c77479c33c4320913eebef6
fn rust_args_to_c_args(args: Vec<String>, outlen: *mut c_int) -> *mut *mut c_char {
let mut v = vec![];
// Let's fill a vector with null-terminated strings
for s in args {
v.push(CString::new(s).unwrap());
}
// Turning each null-terminated string into a pointer.
// `into_raw` takes ownershop, gives us the pointer and does NOT drop the data.
let mut out = v.into_iter().map(|s| s.into_raw()).collect::<Vec<_>>();
// Make sure we're not wasting space.
out.shrink_to_fit();
assert!(out.len() == out.capacity());
// Get the pointer to our vector.
let len = out.len();
let ptr = out.as_mut_ptr();
std::mem::forget(out);
// Let's write back the length the caller can expect
unsafe { std::ptr::write(outlen, len as c_int) };
// Finally return the data
ptr
}
#[no_mangle]
pub unsafe extern "C" fn free_c_args(ptr: *mut *mut c_char, len: c_int) {
let len = len as usize;
// Get back our vector.
// Previously we shrank to fit, so capacity == length.
let v = Vec::from_raw_parts(ptr, len, len);
// Now drop one string at a time.
for elem in v {
let s = CString::from_raw(elem);
std::mem::drop(s);
}
// Afterwards the vector will be dropped and thus freed.
}
#[derive(Default, Clone)]
pub struct FlutterHandler {
pub event_stream: Arc<RwLock<Option<StreamSink<EventToUI>>>>,

View File

@ -43,16 +43,6 @@ fn initialize(app_dir: &str) {
}
}
/// FFI for rustdesk core's main entry.
/// Return true if the app should continue running with UI(possibly Flutter), false if the app should exit.
#[no_mangle]
pub extern "C" fn rustdesk_core_main() -> bool {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
return crate::core_main::core_main().is_some();
#[cfg(any(target_os = "android", target_os = "ios"))]
false
}
pub enum EventToUI {
Event(String),
Rgba(ZeroCopyBuffer<Vec<u8>>),
@ -1041,6 +1031,22 @@ pub fn main_update_me() -> SyncReturn<bool> {
SyncReturn(true)
}
pub fn install_show_run_without_install() -> SyncReturn<bool> {
SyncReturn(show_run_without_install())
}
pub fn install_run_without_install() {
run_without_install();
}
pub fn install_install_me(options: String, path: String) {
install_me(options, path, false, false);
}
pub fn install_install_path() -> SyncReturn<String> {
SyncReturn(install_path())
}
#[cfg(target_os = "android")]
pub mod server_side {
use jni::{

View File

@ -1025,6 +1025,18 @@ copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{start_menu}\\\"
app_name = crate::get_app_name(),
);
}
let mut flutter_copy = Default::default();
if options.contains("--flutter") {
flutter_copy = format!(
"XCOPY \"{}\" \"{}\" /Y /E /H /C /I /K /R /Z",
std::env::current_exe()?
.parent()
.unwrap()
.to_string_lossy()
.to_string(),
path
);
}
let meta = std::fs::symlink_metadata(std::env::current_exe()?)?;
let size = meta.len() / 1024;
@ -1052,6 +1064,7 @@ if exist \"{tmp_path}\\{app_name} Tray.lnk\" del /f /q \"{tmp_path}\\{app_name}
{uninstall_str}
chcp 65001
md \"{path}\"
{flutter_copy}
copy /Y \"{src_exe}\" \"{exe}\"
copy /Y \"{ORIGIN_PROCESS_EXE}\" \"{path}\\{broker_exe}\"
\"{src_exe}\" --extract \"{path}\"
@ -1114,6 +1127,7 @@ sc delete {app_name}
} else {
&dels
},
flutter_copy = flutter_copy,
);
run_cmds(cmds, debug, "install")?;
std::thread::sleep(std::time::Duration::from_millis(2000));