Add Android device deployment flow (#15146)

* Add Android device deployment flow

  Notify the Android Flutter UI when the server requires deployment, add a deploy dialog with API token/custom ID inputs, and reuse shared deploy logic
  for CLI and FFI

Signed-off-by: 21pages <sunboeasy@gmail.com>

* Hide Android deploy API token input

Signed-off-by: 21pages <sunboeasy@gmail.com>

* add more translations

Signed-off-by: 21pages <sunboeasy@gmail.com>

* optimize transations

Signed-off-by: 21pages <sunboeasy@gmail.com>

* Hide deploy action for outgoing-only clients

Signed-off-by: 21pages <sunboeasy@gmail.com>

* Fix deployment register throttle state reset

Signed-off-by: 21pages <sunboeasy@gmail.com>

* Move Android deploy dialog out of settings page

Signed-off-by: 21pages <sunboeasy@gmail.com>

* Use async mutex for deploy register throttle

Signed-off-by: 21pages <sunboeasy@gmail.com>

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
21pages 2026-06-02 14:28:30 +08:00 committed by GitHub
commit d99ddf6816
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
59 changed files with 604 additions and 66 deletions

View file

@ -27,6 +27,7 @@ import 'common.dart';
import 'consts.dart';
import 'mobile/pages/home_page.dart';
import 'mobile/pages/server_page.dart';
import 'mobile/widgets/deploy_dialog.dart';
import 'models/platform_model.dart';
import 'package:flutter_hbb/plugin/handlers.dart'
@ -575,6 +576,14 @@ _registerEventHandler() {
NativeUiHandler.instance.onEvent(evt);
});
}
if (isAndroid) {
platformFFI.registerEventHandler(
'android_needs_deploy', 'android_needs_deploy', (_) async {
WidgetsBinding.instance.addPostFrameCallback((_) {
showDeployPromptDialog();
});
});
}
}
Widget keyListenerBuilder(BuildContext context, Widget? child) {

View file

@ -17,6 +17,7 @@ import '../../common/widgets/login.dart';
import '../../consts.dart';
import '../../models/model.dart';
import '../../models/platform_model.dart';
import '../widgets/deploy_dialog.dart';
import '../widgets/dialog.dart';
import 'home_page.dart';
import 'scan_page.dart';
@ -728,6 +729,13 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
onPressed: (context) {
changeSocks5Proxy();
}),
if (isAndroid && !bind.isOutgoingOnly())
SettingsTile(
title: Text(translate('Deploy')),
leading: Icon(Icons.cloud_upload),
onPressed: (context) {
showDeployDialog();
}),
if (!disabledSettings && !_hideNetwork && !_hideWebSocket)
SettingsTile.switchTile(
title: Text(translate('Use WebSocket')),

View file

@ -0,0 +1,114 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../common.dart';
import '../../models/platform_model.dart';
const _deployDialogTag = 'android-deploy-device';
void showDeployPromptDialog() {
gFFI.dialogManager.dismissByTag(_deployDialogTag);
gFFI.dialogManager.show<bool>((setState, close, context) {
submit() => close(true);
return CustomAlertDialog(
title: Text(translate("Deploy")),
content: Text(translate("server_requires_deployment_tip")),
actions: [
dialogButton("Cancel", onPressed: close, isOutline: true),
dialogButton("OK", onPressed: submit),
],
onSubmit: submit,
onCancel: close,
);
}, tag: _deployDialogTag).then((deploy) {
if (deploy == true) {
showDeployDialog();
}
});
}
void showDeployDialog() {
gFFI.dialogManager.dismissByTag(_deployDialogTag);
final tokenController = TextEditingController();
final idController = TextEditingController();
var errorText = "";
var isInProgress = false;
gFFI.dialogManager.show((setState, close, context) {
submit() async {
if (isInProgress) return;
final token = tokenController.text.trim();
if (token.isEmpty) {
setState(() {
errorText = translate("token is required!");
});
return;
}
setState(() {
errorText = "";
isInProgress = true;
});
String res;
try {
res = await bind.mainDeployDevice(
token: token, id: idController.text.trim());
} catch (e) {
setState(() {
errorText = translate(e.toString());
isInProgress = false;
});
return;
}
if (res.isEmpty) {
close();
await gFFI.serverModel.fetchID();
showToast(translate("Successful"));
} else {
setState(() {
errorText = translate(res.toString());
isInProgress = false;
});
}
}
return CustomAlertDialog(
title: Text(translate("Deploy")),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: tokenController,
decoration: InputDecoration(labelText: translate("API Token")),
obscureText: true,
enableSuggestions: false,
autocorrect: false,
autofocus: true,
).workaroundFreezeLinuxMint(),
TextField(
controller: idController,
decoration:
InputDecoration(labelText: translate("Custom ID (optional)")),
).workaroundFreezeLinuxMint(),
if (errorText.isNotEmpty)
Align(
alignment: Alignment.centerLeft,
child: SelectableText(
errorText,
style: TextStyle(
color: Theme.of(context).colorScheme.error,
fontSize: 12,
),
).paddingOnly(top: 8),
),
if (isInProgress) const LinearProgressIndicator().paddingOnly(top: 8),
],
),
actions: [
dialogButton("Cancel",
onPressed: isInProgress ? null : close, isOutline: true),
dialogButton("OK", onPressed: isInProgress ? null : submit),
],
onSubmit: submit,
onCancel: isInProgress ? null : close,
);
}, tag: _deployDialogTag);
}

View file

@ -2034,7 +2034,14 @@ class RustdeskImpl {
}
String mainResolveAvatarUrl({required String avatar, dynamic hint}) {
return js.context.callMethod('getByName', ['resolve_avatar_url', avatar])?.toString() ?? avatar;
return js.context.callMethod(
'getByName', ['resolve_avatar_url', avatar])?.toString() ??
avatar;
}
Future<String> mainDeployDevice(
{required String token, required String id, dynamic hint}) {
throw UnimplementedError("mainDeployDevice");
}
void dispose() {}

View file

@ -644,6 +644,8 @@ pub fn core_main() -> Option<Vec<String>> {
} else if args[0] == "--deploy" {
if config::Config::no_register_device() {
println!("Cannot deploy an unregistrable device!");
} else if config::is_outgoing_only() {
println!("Cannot deploy Outgoing-only clients.");
} else if crate::platform::is_installed() && is_root() {
let max = args.len() - 1;
let pos = args.iter().position(|x| x == "--token").unwrap_or(max);
@ -661,72 +663,28 @@ pub fn core_main() -> Option<Vec<String>> {
}
};
let new_id = get_value("--id");
let local_id = crate::ipc::get_id();
let id_to_deploy = new_id.clone().unwrap_or_else(|| local_id.clone());
let uuid = crate::encode64(hbb_common::get_uuid());
let pk = crate::encode64(
hbb_common::config::Config::get_key_pair().1,
);
let body = serde_json::json!({
"id": id_to_deploy,
"uuid": uuid,
"pk": pk,
});
let header = "Authorization: Bearer ".to_owned() + &token;
let url = crate::ui_interface::get_api_server() + "/api/devices/deploy";
match crate::post_request_sync(url, body.to_string(), &header) {
Err(err) => {
println!("Request failed: {}", err);
std::process::exit(1);
match crate::ui_interface::deploy_device(token, new_id) {
crate::ui_interface::DeployResult::Ok => {
println!("Device deployed.");
}
Ok(text) => {
let parsed: serde_json::Value =
serde_json::from_str(&text).unwrap_or(serde_json::Value::Null);
let result = parsed["result"].as_str().unwrap_or("");
match result {
"OK" => {
if let Some(ref new_id) = new_id {
if *new_id != local_id {
if let Err(err) =
crate::ipc::set_config("id", new_id.clone())
{
println!(
"Failed to persist deployed id locally: {}",
err
);
std::process::exit(1);
}
}
}
if let Err(err) = crate::ipc::notify_deployed() {
log::warn!("Failed to notify deployed state: {}", err);
}
println!("Device deployed.");
}
"NOT_ENABLED" => {
println!("Server does not require deployment.");
std::process::exit(3);
}
"INVALID_INPUT" => {
println!("Invalid input.");
std::process::exit(5);
}
"ID_TAKEN" => {
println!(
"Id `{}` is already used by another machine on the server.",
id_to_deploy
);
std::process::exit(6);
}
_ => {
if text.is_empty() {
println!("Unknown response.");
} else {
println!("{}", text);
}
std::process::exit(1);
}
}
crate::ui_interface::DeployResult::NotEnabled => {
println!("Server does not require deployment.");
std::process::exit(3);
}
crate::ui_interface::DeployResult::InvalidInput => {
println!("Invalid input.");
std::process::exit(5);
}
crate::ui_interface::DeployResult::IdTaken(id) => {
println!(
"Id `{}` is already used by another machine on the server.",
id
);
std::process::exit(6);
}
crate::ui_interface::DeployResult::Error(err) => {
println!("{}", err);
std::process::exit(1);
}
}
} else {

View file

@ -1153,6 +1153,22 @@ pub fn main_get_api_server() -> String {
get_api_server()
}
pub fn main_deploy_device(token: String, id: String) -> String {
#[cfg(target_os = "android")]
{
let new_id = match id.trim() {
"" => None,
id => Some(id.to_owned()),
};
ui_interface::deploy_device(token, new_id).message()
}
#[cfg(not(target_os = "android"))]
{
let _ = (token, id);
"Deployment is not supported on this platform.".to_owned()
}
}
pub fn main_resolve_avatar_url(avatar: String) -> SyncReturn<String> {
SyncReturn(resolve_avatar_url(avatar))
}
@ -2116,6 +2132,7 @@ pub fn main_start_service() {
#[cfg(target_os = "android")]
{
config::Config::set_option("stop-service".into(), "".into());
crate::rendezvous_mediator::reset_needs_deploy_notification();
crate::rendezvous_mediator::RendezvousMediator::restart();
}
}
@ -3055,6 +3072,7 @@ pub mod server_side {
pub unsafe extern "system" fn Java_ffi_FFI_startService(_env: JNIEnv, _class: JClass) {
log::debug!("startService from jvm");
config::Config::set_option("stop-service".into(), "".into());
crate::rendezvous_mediator::reset_needs_deploy_notification();
crate::rendezvous_mediator::RendezvousMediator::restart();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "كلمة المرور المحددة مسبقًا قيد الاستخدام"),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "Пададзены пароль цяпер выкарыстоўваецца"),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "当前使用预设密码"),
("Enable privacy mode", "允许隐私模式"),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", "API 令牌"),
("Deploy", "部署"),
("Custom ID (optional)", "自定义 ID可选"),
("server_requires_deployment_tip", "服务器要求显式部署此设备。是否立即部署?"),
("The server does not require explicit deployment.", "服务器不需要显式部署。"),
("Unknown response.", "未知响应。"),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "Das voreingestellte Passwort wird derzeit verwendet."),
("Enable privacy mode", "Datenschutzmodus aktivieren"),
("allow-remote-toolbar-docking-any-edge", "Andocken der Remote-Symbolleiste an jeden Fensterrand zulassen"),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -275,5 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("password-hidden-tip", "Permanent password is set (hidden)."),
("preset-password-in-use-tip", "Preset password is currently in use."),
("allow-remote-toolbar-docking-any-edge", "Allow docking remote toolbar to any window edge"),
("server_requires_deployment_tip", "The server requires this device to be deployed explicitly. Deploy now?"),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "Se está usando la contraseña predeterminada."),
("Enable privacy mode", "Habilitar modo privado"),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "Le mot de passe prédéfini est actuellement utilisé."),
("Enable privacy mode", "Activer le mode de confidentialité"),
("allow-remote-toolbar-docking-any-edge", "Autoriser lancrage de la barre doutils à distance sur nimporte quel bord de la fenêtre"),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "પ્રીસેટ પાસવર્ડ વપરાશમાં છે."),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "पूर्व-निर्धारित पासवर्ड उपयोग में है।"),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "Jelenleg az alapértelmezett jelszót használja."),
("Enable privacy mode", "Adatvédelmi mód aktiválása"),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "È attualmente in uso la password preimpostata."),
("Enable privacy mode", "Abilita modalità privacy"),
("allow-remote-toolbar-docking-any-edge", "Consenti ancoraggio barra strumenti remota a qualsiasi bordo della finestra"),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "プリセットパスワードが現在使用されています"),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "현재 사전 설정된 비밀번호가 사용 중입니다."),
("Enable privacy mode", "개인정보 보호 모드 사용함"),
("allow-remote-toolbar-docking-any-edge", "원격 도구 모음을 창 가장자리에 도킹 허용"),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "Iepriekš iestatītā parole pašlaik tiek izmantota."),
("Enable privacy mode", "Iespējot privātuma režīmu"),
("allow-remote-toolbar-docking-any-edge", "Atļaut attālās rīkjoslas piestiprināšanu pie jebkuras loga malas"),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "പ്രീസെറ്റ് പാസ്‌വേഡ് ഉപയോഗത്തിലാണ്."),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "Het basis wachtwoord is momenteel in gebruik."),
("Enable privacy mode", "Privacymodus inschakelen"),
("allow-remote-toolbar-docking-any-edge", "Sta toe om de werkbalk-op-afstand aan de rand van het venster te plaatsen"),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "Obecnie używane jest hasło domyślne."),
("Enable privacy mode", "Włącz tryb prywatny"),
("allow-remote-toolbar-docking-any-edge", "Zezwalaj na dokowanie zdalnego paska narzędzi do dowolnej krawędzi"),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "A senha predefinida está sendo usada."),
("Enable privacy mode", "Habilitar modo de privacidade"),
("allow-remote-toolbar-docking-any-edge", "Permitir fixar a barra de ferramentas remota em qualquer borda da janela"),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "Se folosește o parolă prestabilită. Se recomandă setarea unei parole personalizate pentru securitate sporită."),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "Установленный пароль сейчас используется."),
("Enable privacy mode", "Использовать режим конфиденциальности"),
("allow-remote-toolbar-docking-any-edge", "Разрешать прикрепление удалённой панели инструментов к любому краю окна"),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "Önceden ayarlanmış parola kullanılıyor"),
("Enable privacy mode", "Gizlilik modunu etkinleştir"),
("allow-remote-toolbar-docking-any-edge", "Uzak araç çubuğunun pencerenin herhangi bir kenarına sabitlenmesine izin ver"),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "目前正在使用預設密碼"),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
("allow-remote-toolbar-docking-any-edge", ""),
("API Token", ""),
("Deploy", ""),
("Custom ID (optional)", ""),
("server_requires_deployment_tip", ""),
("The server does not require explicit deployment.", ""),
("Unknown response.", ""),
].iter().cloned().collect();
}

View file

@ -42,6 +42,8 @@ static SHOULD_EXIT: AtomicBool = AtomicBool::new(false);
static MANUAL_RESTARTED: AtomicBool = AtomicBool::new(false);
static SENT_REGISTER_PK: AtomicBool = AtomicBool::new(false);
pub(crate) static NEEDS_DEPLOY: AtomicBool = AtomicBool::new(false);
#[cfg(target_os = "android")]
static NOTIFIED_NEEDS_DEPLOY: AtomicBool = AtomicBool::new(false);
// register_pk retry interval (ms) when device is awaiting deployment
const DEPLOY_RETRY_INTERVAL: i64 = 30_000;
lazy_static::lazy_static! {
@ -66,6 +68,26 @@ async fn deploy_register_throttled() -> bool {
.unwrap_or(false)
}
#[cfg(target_os = "android")]
fn notify_android_needs_deploy() {
if NOTIFIED_NEEDS_DEPLOY.load(Ordering::SeqCst) {
return;
}
let event = serde_json::json!({ "name": "android_needs_deploy" }).to_string();
if matches!(
crate::flutter::push_global_event(crate::flutter::APP_TYPE_MAIN, event),
Some(true)
) {
NOTIFIED_NEEDS_DEPLOY.store(true, Ordering::SeqCst);
}
}
#[cfg(target_os = "android")]
pub(crate) fn reset_needs_deploy_notification() {
NEEDS_DEPLOY.store(false, Ordering::SeqCst);
NOTIFIED_NEEDS_DEPLOY.store(false, Ordering::SeqCst);
}
#[derive(Clone)]
pub struct RendezvousMediator {
addr: TargetAddr<'static>,
@ -117,6 +139,7 @@ impl RendezvousMediator {
crate::platform::linux_desktop_manager::start_xdesktop();
}
scrap::codec::test_av1();
*LAST_NOT_DEPLOYED_REGISTER.lock().await = None;
loop {
let timeout = Arc::new(RwLock::new(CONNECT_TIMEOUT));
let conn_start_time = Instant::now();
@ -322,6 +345,8 @@ impl RendezvousMediator {
Config::set_host_key_confirmed(&self.host_prefix, true);
*SOLVING_PK_MISMATCH.lock().await = "".to_owned();
NEEDS_DEPLOY.store(false, Ordering::SeqCst);
#[cfg(target_os = "android")]
reset_needs_deploy_notification();
}
Ok(register_pk_response::Result::UUID_MISMATCH) => {
self.handle_uuid_mismatch(sink).await?;
@ -336,6 +361,8 @@ impl RendezvousMediator {
// was deleted by an admin while running.
Config::set_key_confirmed(false);
Config::set_host_key_confirmed(&self.host_prefix, false);
#[cfg(target_os = "android")]
notify_android_needs_deploy();
}
_ => {
log::error!("unknown RegisterPkResponse");

View file

@ -1020,6 +1020,102 @@ pub fn get_api_server() -> String {
)
}
pub enum DeployResult {
Ok,
NotEnabled,
InvalidInput,
IdTaken(String),
Error(String),
}
impl DeployResult {
pub fn message(&self) -> String {
match self {
Self::Ok => "".to_owned(),
Self::NotEnabled => "The server does not require explicit deployment.".to_owned(),
Self::InvalidInput => "Invalid input.".to_owned(),
Self::IdTaken(id) => {
format!(
"Id `{}` is already used by another machine on the server.",
id
)
}
Self::Error(err) => err.clone(),
}
}
}
pub fn deploy_device(token: String, new_id: Option<String>) -> DeployResult {
if Config::no_register_device() {
return DeployResult::Error("Cannot deploy an unregistrable device!".to_owned());
}
let token = token.trim();
if token.is_empty() {
return DeployResult::Error("token is required!".to_owned());
}
#[cfg(any(target_os = "android", target_os = "ios"))]
let local_id = Config::get_id();
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let local_id = ipc::get_id();
let id_to_deploy = new_id.clone().unwrap_or_else(|| local_id.clone());
let uuid = crate::encode64(hbb_common::get_uuid());
let pk = crate::encode64(Config::get_key_pair().1);
let body = serde_json::json!({
"id": id_to_deploy,
"uuid": uuid,
"pk": pk,
});
let header = "Authorization: Bearer ".to_owned() + token;
let url = get_api_server() + "/api/devices/deploy";
let text = match crate::post_request_sync(url, body.to_string(), &header) {
Ok(text) => text,
Err(err) => return DeployResult::Error(format!("Request failed: {}", err)),
};
let parsed: serde_json::Value = serde_json::from_str(&text).unwrap_or(serde_json::Value::Null);
match parsed["result"].as_str().unwrap_or("") {
"OK" => {
if let Some(new_id) = new_id {
if new_id != local_id {
#[cfg(any(target_os = "android", target_os = "ios"))]
{
Config::set_key_confirmed(false);
Config::set_id(&new_id);
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if let Err(err) = ipc::set_config("id", new_id) {
return DeployResult::Error(format!(
"Failed to persist deployed id locally: {}",
err
));
}
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if let Err(err) = ipc::notify_deployed() {
log::warn!("Failed to notify deployed state: {}", err);
}
#[cfg(target_os = "android")]
{
crate::rendezvous_mediator::NEEDS_DEPLOY
.store(false, std::sync::atomic::Ordering::SeqCst);
crate::rendezvous_mediator::reset_needs_deploy_notification();
crate::rendezvous_mediator::RendezvousMediator::restart();
}
DeployResult::Ok
}
"NOT_ENABLED" => DeployResult::NotEnabled,
"INVALID_INPUT" => DeployResult::InvalidInput,
"ID_TAKEN" => DeployResult::IdTaken(id_to_deploy),
_ => {
if text.is_empty() {
DeployResult::Error("Unknown response.".to_owned())
} else {
DeployResult::Error(text)
}
}
}
}
#[inline]
pub fn has_hwcodec() -> bool {
// Has real hardware codec using gpu