repo_description: An open-source remote desktop application designed for self-hosting, as an alternative to TeamViewer.
repo_topics: android anydesk dart flatpak flutter flutter-apps ios linux macos p2p rdp remote-control remote-desktop rust rust-lang teamviewer vnc wayland windows
code: #[cfg(any(target_os = "android", target_os = "ios"))]
  use hbb_common::password_security;
  use hbb_common::{
      allow_err,
      bytes::Bytes,
      config::{self, keys::*, Config, LocalConfig, PeerConfig, CONNECT_TIMEOUT, RENDEZVOUS_PORT},
      directories_next,
      futures::future::join_all,
      log,
      rendezvous_proto::*,
      tokio,
  };
  #[cfg(not(any(target_os = "android", target_os = "ios")))]
  use hbb_common::{
      sleep,
      tokio::{sync::mpsc, time},
  };
  use serde_derive::Serialize;
  #[cfg(not(any(target_os = "android", target_os = "ios")))]
  use std::process::Child;
  use std::{
      collections::HashMap,
      sync::{Arc, Mutex},
  };
  
  use crate::common::SOFTWARE_UPDATE_URL;
  #[cfg(feature = "flutter")]
  use crate::hbbs_http::account;
  #[cfg(not(any(target_os = "ios")))]
  use crate::ipc;
  
  type Message = RendezvousMessage;
  
  #[cfg(not(any(target_os = "android", target_os = "ios")))]
  pub type Children = Arc<Mutex<(bool, HashMap<(String, String), Child>)>>;
  
  #[derive(Clone, Debug, Serialize)]
  pub struct UiStatus {
      pub status_num: i32,
      #[cfg(not(feature = "flutter"))]
      pub key_confirmed: bool,
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      pub mouse_time: i64,
      #[cfg(not(feature = "flutter"))]
      pub id: String,
      #[cfg(feature = "flutter")]
      pub video_conn_count: usize,
  }
  
  #[derive(Debug, Clone, Serialize)]
  pub struct LoginDeviceInfo {
      pub os: String,
      pub r#type: String,
      pub name: String,
  }
  
  lazy_static::lazy_static! {
      static ref UI_STATUS : Arc<Mutex<UiStatus>> = Arc::new(Mutex::new(UiStatus{
          status_num: 0,
          #[cfg(not(feature = "flutter"))]
          key_confirmed: false,
          #[cfg(not(any(target_os = "android", target_os = "ios")))]
          mouse_time: 0,
          #[cfg(not(feature = "flutter"))]
          id: "".to_owned(),
          #[cfg(feature = "flutter")]
          video_conn_count: 0,
      }));
      static ref ASYNC_JOB_STATUS : Arc<Mutex<String>> = Default::default();
      static ref ASYNC_HTTP_STATUS : Arc<Mutex<HashMap<String, String>>> = Arc::new(Mutex::new(HashMap::new()));
      static ref TEMPORARY_PASSWD : Arc<Mutex<String>> = Arc::new(Mutex::new("".to_owned()));
      static ref IS_REMOTE_MODIFY_ENABLED_BY_CONTROL_PERMISSIONS : Arc<Mutex<Option<bool>>> = Arc::new(Mutex::new(None));
  }
  
  #[cfg(not(any(target_os = "android", target_os = "ios")))]
  lazy_static::lazy_static! {
      static ref OPTION_SYNCED: Arc<Mutex<bool>> = Default::default();
      static ref OPTIONS : Arc<Mutex<HashMap<String, String>>> = Arc::new(Mutex::new(Config::get_options()));
      pub static ref SENDER : Mutex<mpsc::UnboundedSender<ipc::Data>> = Mutex::new(check_connect_status(true));
      static ref CHILDREN : Children = Default::default();
  }
  
  #[cfg(target_os = "windows")]
  lazy_static::lazy_static! {
      pub static ref IS_FILE_TRANSFER_ENABLED: Arc<Mutex<Option<bool>>> = Arc::new(Mutex::new(None));
  }
  
  const INIT_ASYNC_JOB_STATUS: &str = " ";
  
  #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
  #[inline]
  pub fn get_id() -> String {
      #[cfg(any(target_os = "android", target_os = "ios"))]
      return Config::get_id();
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      return ipc::get_id();
  }
  
  #[inline]
  pub fn goto_install() {
      allow_err!(crate::run_me(vec!["--install"]));
      std::process::exit(0);
  }
  
  #[inline]
  pub fn install_me(_options: String, _path: String, _silent: bool, _debug: bool) {
      #[cfg(windows)]
      std::thread::spawn(move || {
          allow_err!(crate::platform::windows::install_me(
              &_options, _path, _silent, _debug
          ));
          std::process::exit(0);
      });
  }
  
  #[inline]
  pub fn update_me(_path: String) {
      goto_install();
  }
  
  #[inline]
  pub fn run_without_install() {
      crate::run_me(vec!["--noinstall"]).ok();
      std::process::exit(0);
  }
  
  #[inline]
  pub fn show_run_without_install() -> bool {
      let mut it = std::env::args();
      if let Some(tmp) = it.next() {
          if crate::is_setup(&tmp) {
              return it.next() == None;
          }
      }
      false
  }
  
  #[inline]
  pub fn get_license() -> String {
      #[cfg(windows)]
      if let Ok(lic) = crate::platform::windows::get_license_from_exe_name() {
          #[cfg(feature = "flutter")]
          return format!("Key: {}\nHost: {}\nAPI: {}", lic.key, lic.host, lic.api);
          // default license format is html formed (sciter)
          #[cfg(not(feature = "flutter"))]
          return format!(
              "<br /> Key: {} <br /> Host: {} API: {}",
              lic.key, lic.host, lic.api
          );
      }
      Default::default()
  }
  
  #[inline]
  pub fn refresh_options() {
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      {
          *OPTIONS.lock().unwrap() = Config::get_options();
      }
  }
  
  #[inline]
  pub fn get_option<T: AsRef<str>>(key: T) -> String {
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      {
          let map = OPTIONS.lock().unwrap();
          if let Some(v) = map.get(key.as_ref()) {
              v.to_owned()
          } else {
              "".to_owned()
          }
      }
      #[cfg(any(target_os = "android", target_os = "ios"))]
      {
          Config::get_option(key.as_ref())
      }
  }
  
  #[inline]
  pub fn use_texture_render() -> bool {
      #[cfg(target_os = "android")]
      return false;
      #[cfg(target_os = "ios")]
      return false;
  
      #[cfg(target_os = "macos")]
      return cfg!(feature = "flutter")
          && LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) == "Y";
  
      #[cfg(target_os = "linux")]
      return cfg!(feature = "flutter")
          && LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) != "N";
  
      #[cfg(target_os = "windows")]
      {
          if !cfg!(feature = "flutter") {
              return false;
          }
          // https://learn.microsoft.com/en-us/windows/win32/sysinfo/targeting-your-application-at-windows-8-1
          #[cfg(debug_assertions)]
          let default_texture = true;
          #[cfg(not(debug_assertions))]
          let default_texture = crate::platform::is_win_10_or_greater();
          if default_texture {
              LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) != "N"
          } else {
              return LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) == "Y";
          }
      }
  }
  
  #[inline]
  pub fn is_option_fixed(key: &str) -> bool {
      config::OVERWRITE_DISPLAY_SETTINGS
          .read()
          .unwrap()
          .contains_key(key)
          || config::OVERWRITE_LOCAL_SETTINGS
              .read()
              .unwrap()
              .contains_key(key)
          || config::OVERWRITE_SETTINGS.read().unwrap().contains_key(key)
  }
  
  #[inline]
  pub fn get_local_option(key: String) -> String {
      crate::get_local_option(&key)
  }
  
  #[inline]
  #[cfg(feature = "flutter")]
  pub fn get_hard_option(key: String) -> String {
      config::HARD_SETTINGS
          .read()
          .unwrap()
          .get(&key)
          .cloned()
          .unwrap_or_default()
  }
  
  #[inline]
  pub fn get_builtin_option(key: &str) -> String {
      crate::get_builtin_option(key)
  }
  
  #[inline]
  pub fn set_local_option(key: String, value: String) {
      LocalConfig::set_option(key.clone(), value);
  }
  
  /// Resolve relative avatar path (e.g. "/avatar/xxx") to absolute URL
  /// by prepending the API server address.
  pub fn resolve_avatar_url(avatar: String) -> String {
      let avatar = avatar.trim().to_owned();
      if avatar.starts_with('/') {
          let api_server = get_api_server();
          if !api_server.is_empty() {
              return format!("{}{}", api_server.trim_end_matches('/'), avatar);
          }
      }
      avatar
  }
  
  #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
  #[inline]
  pub fn get_local_flutter_option(key: String) -> String {
      LocalConfig::get_flutter_option(&key)
  }
  
  #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
  #[inline]
  pub fn set_local_flutter_option(key: String, value: String) {
      LocalConfig::set_flutter_option(key, value);
  }
  
  #[cfg(feature = "flutter")]
  #[inline]
  pub fn get_kb_layout_type() -> String {
      LocalConfig::get_kb_layout_type()
  }
  
  #[cfg(feature = "flutter")]
  #[inline]
  pub fn set_kb_layout_type(kb_layout_type: String) {
      LocalConfig::set_kb_layout_type(kb_layout_type);
  }
  
  #[inline]
  pub fn peer_has_password(id: String) -> bool {
      !PeerConfig::load(&id).password.is_empty()
  }
  
  #[inline]
  pub fn forget_password(id: String) {
      let mut c = PeerConfig::load(&id);
      c.password.clear();
      c.store(&id);
  }
  
  #[inline]
  pub fn get_peer_option(id: String, name: String) -> String {
      let c = PeerConfig::load(&id);
      c.options.get(&name).unwrap_or(&"".to_owned()).to_owned()
  }
  
  #[inline]
  #[cfg(feature = "flutter")]
  pub fn get_peer_flutter_option(id: String, name: String) -> String {
      let c = PeerConfig::load(&id);
      c.ui_flutter.get(&name).unwrap_or(&"".to_owned()).to_owned()
  }
  
  #[inline]
  #[cfg(feature = "flutter")]
  pub fn set_peer_flutter_option(id: String, name: String, value: String) {
      let mut c = PeerConfig::load(&id);
      if value.is_empty() {
          c.ui_flutter.remove(&name);
      } else {
          c.ui_flutter.insert(name, value);
      }
      c.store(&id);
  }
  
  #[inline]
  pub fn set_peer_option(id: String, name: String, value: String) {
      let mut c = PeerConfig::load(&id);
      if value.is_empty() {
          c.options.remove(&name);
      } else {
          c.options.insert(name, value);
      }
      c.store(&id);
  }
  
  #[inline]
  pub fn get_options() -> String {
      let options = {
          #[cfg(not(any(target_os = "android", target_os = "ios")))]
          {
              OPTIONS.lock().unwrap()
          }
          #[cfg(any(target_os = "android", target_os = "ios"))]
          {
              Config::get_options()
          }
      };
      let mut m = serde_json::Map::new();
      for (k, v) in options.iter() {
          m.insert(k.into(), v.to_owned().into());
      }
      serde_json::to_string(&m).unwrap_or_default()
  }
  
  #[inline]
  pub fn test_if_valid_server(host: String, test_with_proxy: bool) -> String {
      hbb_common::socket_client::test_if_valid_server(&host, test_with_proxy)
  }
  
  #[inline]
  #[cfg(feature = "flutter")]
  #[cfg(not(any(target_os = "android", target_os = "ios")))]
  pub fn get_sound_inputs() -> Vec<String> {
      let mut a = Vec::new();
      #[cfg(not(target_os = "linux"))]
      {
          fn get_sound_inputs_() -> Vec<String> {
              let mut out = Vec::new();
              use cpal::traits::{DeviceTrait, HostTrait};
              // Do not use `cpal::host_from_id(cpal::HostId::ScreenCaptureKit)` for feature = "screencapturekit"
              // Because we explicitly handle the "System Sound" device.
              let host = cpal::default_host();
              if let Ok(devices) = host.devices() {
                  for device in devices {
                      if device.default_input_config().is_err() {
                          continue;
                      }
                      if let Ok(name) = device.name() {
                          out.push(name);
                      }
                  }
              }
              out
          }
  
          let inputs = Arc::new(Mutex::new(Vec::new()));
          let cloned = inputs.clone();
          // can not call below in UI thread, because conflict with sciter sound com initialization
          std::thread::spawn(move || *cloned.lock().unwrap() = get_sound_inputs_())
              .join()
              .ok();
          for name in inputs.lock().unwrap().drain(..) {
              a.push(name);
          }
      }
      #[cfg(target_os = "linux")]
      {
          let inputs: Vec<String> = crate::platform::linux::get_pa_sources()
              .drain(..)
              .map(|x| x.1)
              .collect();
  
          for name in inputs {
              a.push(name);
          }
      }
      a
  }
  
  #[inline]
  pub fn set_options(m: HashMap<String, String>) {
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      {
          *OPTIONS.lock().unwrap() = m.clone();
          ipc::set_options(m).ok();
      }
      #[cfg(any(target_os = "android", target_os = "ios"))]
      Config::set_options(m);
  }
  
  #[inline]
  pub fn set_option(key: String, value: String) {
      if &key == "stop-service" {
          #[cfg(target_os = "macos")]
          {
              let is_stop = value == "Y";
              if is_stop && crate::platform::uninstall_service(true, false) {
                  return;
              }
          }
          #[cfg(any(target_os = "windows", target_os = "linux"))]
          {
              if crate::platform::is_installed() {
                  if value == "Y" {
                      if crate::platform::uninstall_service(true, false) {
                          return;
                      }
                  } else {
                      if crate::platform::install_service() {
                          return;
                      }
                  }
                  return;
              }
          }
      } else if &key == "audio-input" {
          #[cfg(not(target_os = "ios"))]
          crate::audio_service::restart();
      }
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      {
          let mut options = OPTIONS.lock().unwrap();
          if value.is_empty() {
              options.remove(&key);
          } else {
              options.insert(key.clone(), value.clone());
          }
          ipc::set_options(options.clone()).ok();
      }
      #[cfg(any(target_os = "android", target_os = "ios"))]
      {
          let _nat = crate::CheckTestNatType::new();
          Config::set_option(key, value);
      }
  }
  
  #[inline]
  pub fn install_path() -> String {
      #[cfg(windows)]
      return crate::platform::windows::get_install_info().1;
      #[cfg(not(windows))]
      return "".to_owned();
  }
  
  #[inline]
  pub fn install_options() -> String {
      #[cfg(windows)]
      return crate::platform::windows::get_install_options();
      #[cfg(not(windows))]
      return "{}".to_owned();
  }
  
  #[inline]
  pub fn get_socks() -> Vec<String> {
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      let s = ipc::get_socks();
      #[cfg(any(target_os = "android", target_os = "ios"))]
      let s = Config::get_socks();
      match s {
          None => Vec::new(),
          Some(s) => {
              let mut v = Vec::new();
              v.push(s.proxy);
              v.push(s.username);
              v.push(s.password);
              v
          }
      }
  }
  
  #[inline]
  pub fn set_socks(proxy: String, username: String, password: String) {
      let socks = config::Socks5Server {
          proxy,
          username,
          password,
      };
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      ipc::set_socks(socks).ok();
      #[cfg(any(target_os = "android", target_os = "ios"))]
      {
          let _nat = crate::CheckTestNatType::new();
          if socks.proxy.is_empty() {
              Config::set_socks(None);
          } else {
              Config::set_socks(Some(socks));
          }
          log::info!("socks updated");
      }
      #[cfg(target_os = "android")]
      {
          crate::RendezvousMediator::restart();
      }
  }
  
  #[inline]
  #[cfg(feature = "flutter")]
  pub fn get_proxy_status() -> bool {
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      return ipc::get_proxy_status();
  
      // Currently, only the desktop version has proxy settings.
      #[cfg(any(target_os = "android", target_os = "ios"))]
      return false;
  }
  
  #[cfg(not(any(target_os = "android", target_os = "ios")))]
  #[inline]
  pub fn is_installed() -> bool {
      crate::platform::is_installed()
  }
  
  #[cfg(any(target_os = "android", target_os = "ios"))]
  #[inline]
  pub fn is_installed() -> bool {
      false
  }
  
  #[inline]
  pub fn is_share_rdp() -> bool {
      #[cfg(windows)]
      return crate::platform::windows::is_share_rdp();
      #[cfg(not(windows))]
      return false;
  }
  
  #[inline]
  pub fn set_share_rdp(_enable: bool) {
      #[cfg(windows)]
      crate::platform::windows::set_share_rdp(_enable);
  }
  
  #[inline]
  pub fn is_installed_lower_version() -> bool {
      #[cfg(not(windows))]
      return false;
      #[cfg(windows)]
      {
          let b = crate::platform::windows::get_reg("BuildDate");
          return crate::BUILD_DATE.cmp(&b).is_gt();
      }
  }
  
  #[inline]
  #[cfg(not(any(target_os = "android", target_os = "ios")))]
  pub fn get_mouse_time() -> f64 {
      UI_STATUS.lock().unwrap().mouse_time as f64
  }
  
  #[inline]
  pub fn check_mouse_time() {
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      {
          let sender = SENDER.lock().unwrap();
          allow_err!(sender.send(ipc::Data::MouseMoveTime(0)));
      }
  }
  
  #[inline]
  #[cfg(not(any(target_os = "android", target_os = "ios")))]
  pub fn get_connect_status() -> UiStatus {
      UI_STATUS.lock().unwrap().clone()
  }
  
  #[inline]
  pub fn temporary_password() -> String {
      #[cfg(any(target_os = "android", target_os = "ios"))]
      return password_security::temporary_password();
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      return TEMPORARY_PASSWD.lock().unwrap().clone();
  }
  
  #[inline]
  pub fn update_temporary_password() {
      #[cfg(any(target_os = "android", target_os = "ios"))]
      password_security::update_temporary_password();
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      allow_err!(ipc::update_temporary_password());
  }
  
  #[inline]
  pub fn is_permanent_password_set() -> bool {
      #[cfg(any(target_os = "android", target_os = "ios"))]
      return Config::has_permanent_password();
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      {
          let daemon_is_set = ipc::is_permanent_password_set();
          // `daemon_is_set` is authoritative for the return value. Local storage is only used to
          // decide whether we should attempt a sync to clear stale user-side state.
          let local_storage_is_empty = if daemon_is_set {
              true
          } else {
              let (storage, _) = Config::get_local_permanent_password_storage_and_salt();
              storage.is_empty()
          };
          if daemon_is_set || !local_storage_is_empty {
              allow_err!(ipc::sync_permanent_password_storage_from_daemon());
          }
          daemon_is_set
      }
  }
  
  #[inline]
  pub fn is_local_permanent_password_set() -> bool {
      #[cfg(any(target_os = "android", target_os = "ios"))]
      return Config::has_local_permanent_password();
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      {
          allow_err!(ipc::sync_permanent_password_storage_from_daemon());
          Config::has_local_permanent_password()
      }
  }
  
  pub fn set_permanent_password_with_result(password: String) -> bool {
      if config::Config::is_disable_change_permanent_password() {
          return false;
      }
      #[cfg(any(target_os = "android", target_os = "ios"))]
      {
          config::Config::set_permanent_password(&password);
          return true;
      }
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      {
          match crate::ipc::set_permanent_password_with_ack(password) {
              Ok(ok) => ok,
              Err(err) => {
                  log::warn!("Failed to set permanent password via IPC: {err}");
                  false
              }
          }
      }
  }
  
  #[inline]
  pub fn get_peer(id: String) -> PeerConfig {
      PeerConfig::load(&id)
  }
  
  #[inline]
  pub fn get_fav() -> Vec<String> {
      LocalConfig::get_fav()
  }
  
  #[inline]
  pub fn store_fav(fav: Vec<String>) {
      LocalConfig::set_fav(fav);
  }
  
  #[inline]
  pub fn is_process_trusted(_prompt: bool) -> bool {
      #[cfg(target_os = "macos")]
      return crate::platform::macos::is_process_trusted(_prompt);
      #[cfg(not(target_os = "macos"))]
      return true;
  }
  
  #[inline]
  pub fn is_can_screen_recording(_prompt: bool) -> bool {
      #[cfg(target_os = "macos")]
      return crate::platform::macos::is_can_screen_recording(_prompt);
      #[cfg(not(target_os = "macos"))]
      return true;
  }
  
  #[inline]
  pub fn is_installed_daemon(_prompt: bool) -> bool {
      #[cfg(target_os = "macos")]
      return crate::platform::macos::is_installed_daemon(_prompt);
      #[cfg(not(target_os = "macos"))]
      return true;
  }
  
  #[inline]
  #[cfg(feature = "flutter")]
  pub fn is_can_input_monitoring(_prompt: bool) -> bool {
      #[cfg(target_os = "macos")]
      return crate::platform::macos::is_can_input_monitoring(_prompt);
      #[cfg(not(target_os = "macos"))]
      return true;
  }
  
  #[inline]
  pub fn get_error() -> String {
      #[cfg(not(any(feature = "cli")))]
      #[cfg(target_os = "linux")]
      {
          let dtype = crate::platform::linux::get_display_server();
          if crate::platform::linux::DISPLAY_SERVER_WAYLAND == dtype {
              return crate::server::wayland::common_get_error();
          }
          if dtype != crate::platform::linux::DISPLAY_SERVER_X11 {
              return format!(
                  "{} {}, {}",
                  crate::client::translate("Unsupported display server".to_owned()),
                  dtype,
                  crate::client::translate("x11 expected".to_owned()),
              );
          }
      }
      return "".to_owned();
  }
  
  #[inline]
  pub fn is_login_wayland() -> bool {
      #[cfg(target_os = "linux")]
      return crate::platform::linux::is_login_wayland();
      #[cfg(not(target_os = "linux"))]
      return false;
  }
  
  #[inline]
  pub fn current_is_wayland() -> bool {
      #[cfg(target_os = "linux")]
      return crate::platform::linux::current_is_wayland();
      #[cfg(not(target_os = "linux"))]
      return false;
  }
  
  #[inline]
  pub fn get_new_version() -> String {
      (*SOFTWARE_UPDATE_URL
          .lock()
          .unwrap()
          .rsplit('/')
          .next()
          .unwrap_or(""))
      .to_string()
  }
  
  #[inline]
  pub fn get_version() -> String {
      crate::VERSION.to_owned()
  }
  
  #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
  #[inline]
  pub fn get_app_name() -> String {
      crate::get_app_name()
  }
  
  #[cfg(windows)]
  #[inline]
  pub fn create_shortcut(_id: String) {
      crate::platform::windows::create_shortcut(&_id).ok();
  }
  
  #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
  #[inline]
  pub fn discover() {
      std::thread::spawn(move || {
          allow_err!(crate::lan::discover());
      });
  }
  
  #[cfg(feature = "flutter")]
  pub fn peer_to_map(id: String, p: PeerConfig) -> HashMap<&'static str, String> {
      use hbb_common::sodiumoxide::base64;
      HashMap::<&str, String>::from_iter([
          ("id", id),
          ("username", p.info.username.clone()),
          ("hostname", p.info.hostname.clone()),
          ("platform", p.info.platform.clone()),
          (
              "alias",
              p.options.get("alias").unwrap_or(&"".to_owned()).to_owned(),
          ),
          (
              "hash",
              base64::encode(p.password, base64::Variant::Original),
          ),
      ])
  }
  
  #[cfg(feature = "flutter")]
  pub fn peer_exists(id: &str) -> bool {
      PeerConfig::exists(id)
  }
  
  #[inline]
  pub fn get_lan_peers() -> Vec<HashMap<&'static str, String>> {
      config::LanPeers::load()
          .peers
          .iter()
          .map(|peer| {
              HashMap::<&str, String>::from_iter([
                  ("id", peer.id.clone()),
                  ("username", peer.username.clone()),
                  ("hostname", peer.hostname.clone()),
                  ("platform", peer.platform.clone()),
              ])
          })
          .collect()
  }
  
  #[inline]
  pub fn remove_discovered(id: String) {
      let mut peers = config::LanPeers::load().peers;
      peers.retain(|x| x.id != id);
      config::LanPeers::store(&peers);
  }
  
  #[inline]
  pub fn get_uuid() -> String {
      crate::encode64(hbb_common::get_uuid())
  }
  
  #[inline]
  pub fn get_init_async_job_status() -> String {
      INIT_ASYNC_JOB_STATUS.to_string()
  }
  
  #[inline]
  pub fn reset_async_job_status() {
      *ASYNC_JOB_STATUS.lock().unwrap() = get_init_async_job_status();
  }
  
  #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
  #[inline]
  pub fn change_id(id: String) {
      reset_async_job_status();
      let old_id = get_id();
      std::thread::spawn(move || {
          change_id_shared(id, old_id);
      });
  }
  
  #[inline]
  pub fn http_request(url: String, method: String, body: Option<String>, header: String) {
      // Respond to concurrent requests for resources
      let current_request = ASYNC_HTTP_STATUS.clone();
      current_request
          .lock()
          .unwrap()
          .insert(url.clone(), " ".to_owned());
      std::thread::spawn(move || {
          let res = match crate::http_request_sync(url.clone(), method, body, header) {
              Err(err) => {
                  log::error!("{}", err);
                  err.to_string()
              }
              Ok(text) => text,
          };
          current_request.lock().unwrap().insert(url, res);
      });
  }
  
  #[inline]
  pub fn get_async_http_status(url: String) -> Option<String> {
      match ASYNC_HTTP_STATUS.lock().unwrap().get(&url) {
          None => None,
          Some(_str) => Some(_str.to_string()),
      }
  }
  
  #[inline]
  #[cfg(not(feature = "flutter"))]
  pub fn post_request(url: String, body: String, header: String) {
      *ASYNC_JOB_STATUS.lock().unwrap() = " ".to_owned();
      std::thread::spawn(move || {
          *ASYNC_JOB_STATUS.lock().unwrap() = match crate::post_request_sync(url, body, &header) {
              Err(err) => err.to_string(),
              Ok(text) => text,
          };
      });
  }
  
  #[inline]
  pub fn get_async_job_status() -> String {
      ASYNC_JOB_STATUS.lock().unwrap().clone()
  }
  
  #[inline]
  pub fn get_langs() -> String {
      use serde_json::json;
      let mut x: Vec<(&str, String)> = crate::lang::LANGS
          .iter()
          .map(|a| (a.0, format!("{} ({})", a.1, a.0)))
          .collect();
      x.sort_by(|a, b| a.0.cmp(b.0));
      json!(x).to_string()
  }
  
  #[inline]
  pub fn video_save_directory(root: bool) -> String {
      let appname = crate::get_app_name();
      // ui process can show it correctly Once vidoe process created it.
      let try_create = |path: &std::path::Path| {
          if !path.exists() {
              std::fs::create_dir_all(path).ok();
          }
          if path.exists() {
              path.to_string_lossy().to_string()
          } else {
              "".to_string()
          }
      };
  
      if root {
          // Currently, only installed windows run as root
          #[cfg(windows)]
          {
              let drive = std::env::var("SystemDrive").unwrap_or("C:".to_owned());
              let dir =
                  std::path::PathBuf::from(format!("{drive}\\ProgramData\\{appname}\\recording",));
              return dir.to_string_lossy().to_string();
          }
      }
      // Get directory from config file otherwise --server will use the old value from global var.
      #[cfg(any(target_os = "linux", target_os = "macos"))]
      let dir = LocalConfig::get_option_from_file(OPTION_VIDEO_SAVE_DIRECTORY);
      #[cfg(not(any(target_os = "linux", target_os = "macos")))]
      let dir = LocalConfig::get_option(OPTION_VIDEO_SAVE_DIRECTORY);
      if !dir.is_empty() {
          return dir;
      }
      #[cfg(any(target_os = "android", target_os = "ios"))]
      if let Ok(home) = config::APP_HOME_DIR.read() {
          let mut path = home.to_owned();
          path.push_str(format!("/{appname}/ScreenRecord").as_str());
          let dir = try_create(&std::path::Path::new(&path));
          if !dir.is_empty() {
              return dir;
          }
      }
  
      if let Some(user) = directories_next::UserDirs::new() {
          if let Some(video_dir) = user.video_dir() {
              let dir = try_create(&video_dir.join(&appname));
              if !dir.is_empty() {
                  return dir;
              }
              if video_dir.exists() {
                  return video_dir.to_string_lossy().to_string();
              }
          }
          if let Some(desktop_dir) = user.desktop_dir() {
              if desktop_dir.exists() {
                  return desktop_dir.to_string_lossy().to_string();
              }
          }
          let home = user.home_dir();
          if home.exists() {
              return home.to_string_lossy().to_string();
          }
      }
  
      // same order as above
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      if let Some(home) = crate::platform::get_active_user_home() {
          let name = if cfg!(target_os = "macos") {
              "Movies"
          } else {
              "Videos"
          };
          let video_dir = home.join(name);
          let dir = try_create(&video_dir.join(&appname));
          if !dir.is_empty() {
              return dir;
          }
          if video_dir.exists() {
              return video_dir.to_string_lossy().to_string();
          }
          let desktop_dir = home.join("Desktop");
          if desktop_dir.exists() {
              return desktop_dir.to_string_lossy().to_string();
          }
          if home.exists() {
              return home.to_string_lossy().to_string();
          }
      }
  
      if let Ok(exe) = std::env::current_exe() {
          if let Some(parent) = exe.parent() {
              let dir = try_create(&parent.join("videos"));
              if !dir.is_empty() {
                  return dir;
              }
              // basically exist
              return parent.to_string_lossy().to_string();
          }
      }
      Default::default()
  }
  
  #[inline]
  pub fn get_api_server() -> String {
      crate::get_api_server(
          get_option("api-server"),
          get_option("custom-rendezvous-server"),
      )
  }
  
  #[inline]
  pub fn has_hwcodec() -> bool {
      // Has real hardware codec using gpu
      (cfg!(feature = "hwcodec") && cfg!(not(target_os = "ios"))) || cfg!(feature = "mediacodec")
  }
  
  #[inline]
  pub fn has_vram() -> bool {
      cfg!(feature = "vram")
  }
  
  #[cfg(feature = "flutter")]
  #[inline]
  pub fn supported_hwdecodings() -> (bool, bool) {
      let decoding =
          scrap::codec::Decoder::supported_decodings(None, use_texture_render(), None, &vec![]);
      #[allow(unused_mut)]
      let (mut h264, mut h265) = (decoding.ability_h264 > 0, decoding.ability_h265 > 0);
      #[cfg(feature = "vram")]
      {
          // supported_decodings check runtime luid
          let vram = scrap::vram::VRamDecoder::possible_available_without_check();
          if vram.0 {
              h264 = true;
          }
          if vram.1 {
              h265 = true;
          }
      }
      (h264, h265)
  }
  
  #[cfg(not(any(target_os = "android", target_os = "ios")))]
  #[inline]
  pub fn is_root() -> bool {
      crate::platform::is_root()
  }
  
  #[cfg(any(target_os = "android", target_os = "ios"))]
  #[inline]
  pub fn is_root() -> bool {
      false
  }
  
  #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
  #[inline]
  pub fn check_super_user_permission() -> bool {
      #[cfg(any(windows, target_os = "linux", target_os = "macos"))]
      return crate::platform::check_super_user_permission().unwrap_or(false);
      #[cfg(not(any(windows, target_os = "linux", target_os = "macos")))]
      return true;
  }
  
  #[cfg(not(any(target_os = "android", target_os = "ios", feature = "flutter")))]
  pub fn check_zombie() {
      let mut deads = Vec::new();
      loop {
          let mut lock = CHILDREN.lock().unwrap();
          let mut n = 0;
          for (id, c) in lock.1.iter_mut() {
              if let Ok(Some(_)) = c.try_wait() {
                  deads.push(id.clone());
                  n += 1;
              }
          }
          for ref id in deads.drain(..) {
              lock.1.remove(id);
          }
          if n > 0 {
              lock.0 = true;
          }
          drop(lock);
          std::thread::sleep(std::time::Duration::from_millis(100));
      }
  }
  
  #[inline]
  #[cfg(not(any(target_os = "android", target_os = "ios", feature = "flutter")))]
  pub fn recent_sessions_updated() -> bool {
      let mut children = CHILDREN.lock().unwrap();
      if children.0 {
          children.0 = false;
          true
      } else {
          false
      }
  }
  
  #[cfg(not(any(target_os = "android", target_os = "ios", feature = "flutter")))]
  pub fn new_remote(id: String, remote_type: String, force_relay: bool) {
      let mut lock = CHILDREN.lock().unwrap();
      let mut args = vec![format!("--{}", remote_type), id.clone()];
      if force_relay {
          args.push("".to_string()); // password
          args.push("--relay".to_string());
      }
      let key = (id.clone(), remote_type.clone());
      if let Some(c) = lock.1.get_mut(&key) {
          if let Ok(Some(_)) = c.try_wait() {
              lock.1.remove(&key);
          } else {
              if remote_type == "rdp" {
                  allow_err!(c.kill());
                  std::thread::sleep(std::time::Duration::from_millis(30));
                  c.try_wait().ok();
                  lock.1.remove(&key);
              } else {
                  return;
              }
          }
      }
      match crate::run_me(args) {
          Ok(child) => {
              lock.1.insert(key, child);
          }
          Err(err) => {
              log::error!("Failed to spawn remote: {}", err);
          }
      }
  }
  
  // Make sure `SENDER` is inited here.
  #[inline]
  #[cfg(not(any(target_os = "android", target_os = "ios")))]
  pub fn start_option_status_sync() {
      let _sender = SENDER.lock().unwrap();
  }
  
  // not call directly
  #[cfg(not(any(target_os = "android", target_os = "ios")))]
  fn check_connect_status(reconnect: bool) -> mpsc::UnboundedSender<ipc::Data> {
      let (tx, rx) = mpsc::unbounded_channel::<ipc::Data>();
      std::thread::spawn(move || check_connect_status_(reconnect, rx));
      tx
  }
  
  #[cfg(feature = "flutter")]
  pub fn account_auth(op: String, id: String, uuid: String, remember_me: bool) {
      account::OidcSession::account_auth(get_api_server(), op, id, uuid, remember_me);
  }
  
  #[cfg(feature = "flutter")]
  pub fn account_auth_cancel() {
      account::OidcSession::auth_cancel();
  }
  
  #[cfg(feature = "flutter")]
  pub fn account_auth_result() -> String {
      serde_json::to_string(&account::OidcSession::get_result()).unwrap_or_default()
  }
  
  #[cfg(feature = "flutter")]
  pub fn set_user_default_option(key: String, value: String) {
      use hbb_common::config::UserDefaultConfig;
      UserDefaultConfig::load().set(key, value);
  }
  
  #[cfg(feature = "flutter")]
  pub fn get_user_default_option(key: String) -> String {
      use hbb_common::config::UserDefaultConfig;
      UserDefaultConfig::load().get(&key)
  }
  
  pub fn get_fingerprint() -> String {
      #[cfg(any(target_os = "android", target_os = "ios"))]
      if Config::get_key_confirmed() {
          return crate::common::pk_to_fingerprint(Config::get_key_pair().1);
      } else {
          return "".to_owned();
      }
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      return ipc::get_fingerprint();
  }
  
  #[inline]
  pub fn get_login_device_info() -> LoginDeviceInfo {
      LoginDeviceInfo {
          // std::env::consts::OS is better than whoami::platform() here.
          os: std::env::consts::OS.to_owned(),
          r#type: "client".to_owned(),
          name: crate::common::hostname(),
      }
  }
  
  #[inline]
  pub fn get_login_device_info_json() -> String {
      serde_json::to_string(&get_login_device_info()).unwrap_or("{}".to_string())
  }
  
  // notice: avoiding create ipc connection repeatedly,
  // because windows named pipe has serious memory leak issue.
  #[cfg(not(any(target_os = "android", target_os = "ios")))]
  #[tokio::main(flavor = "current_thread")]
  async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver<ipc::Data>) {
      #[cfg(not(feature = "flutter"))]
      let mut key_confirmed = false;
      let mut rx = rx;
      let mut mouse_time = 0;
      #[cfg(feature = "flutter")]
      let mut video_conn_count = 0;
      #[cfg(not(feature = "flutter"))]
      let mut id = "".to_owned();
      let is_cm = crate::common::is_cm();
  
      loop {
          if let Ok(mut c) = ipc::connect(1000, "").await {
              let mut timer = crate::rustdesk_interval(time::interval(time::Duration::from_secs(1)));
              loop {
                  tokio::select! {
                      res = c.next() => {
                          match res {
                              Err(err) => {
                                  log::error!("ipc connection closed: {}", err);
                                  if is_cm {
                                      crate::ui_cm_interface::quit_cm();
                                  }
                                  break;
                              }
                              #[cfg(not(any(target_os = "android", target_os = "ios")))]
                              Ok(Some(ipc::Data::MouseMoveTime(v))) => {
                                  mouse_time = v;
                                  UI_STATUS.lock().unwrap().mouse_time = v;
                              }
                              Ok(Some(ipc::Data::Options(Some(v)))) => {
                                  *OPTIONS.lock().unwrap() = v;
                                  *OPTION_SYNCED.lock().unwrap() = true;
                              }
                              Ok(Some(ipc::Data::Config((name, Some(value))))) => {
                                  if name == "id" {
                                      #[cfg(not(feature = "flutter"))]
                                      {
                                          id = value;
                                      }
                                  } else if name == "temporary-password" {
                                      *TEMPORARY_PASSWD.lock().unwrap() = value;
                                  }
                              }
                              #[cfg(feature = "flutter")]
                              Ok(Some(ipc::Data::VideoConnCount(Some(n)))) => {
                                  video_conn_count = n;
                              }
                              Ok(Some(ipc::Data::OnlineStatus(Some((mut x, _c))))) => {
                                  if x > 0 {
                                      x = 1
                                  }
                                  #[cfg(not(feature = "flutter"))]
                                  {
                                      key_confirmed = _c;
                                  }
                                  *UI_STATUS.lock().unwrap() = UiStatus {
                                      status_num: x as _,
                                      #[cfg(not(feature = "flutter"))]
                                      key_confirmed: _c,
                                      #[cfg(not(any(target_os = "android", target_os = "ios")))]
                                      mouse_time,
                                      #[cfg(not(feature = "flutter"))]
                                      id: id.clone(),
                                      #[cfg(feature = "flutter")]
                                      video_conn_count,
                                  };
                              }
                              Ok(Some(ipc::Data::ControlPermissionsRemoteModify(v))) => {
                                  *IS_REMOTE_MODIFY_ENABLED_BY_CONTROL_PERMISSIONS.lock().unwrap() = v;
                              }
                              #[cfg(target_os = "windows")]
                              Ok(Some(ipc::Data::FileTransferEnabledState(v))) => {
                                  if let Some(enabled) = v {
                                      let mut lock = IS_FILE_TRANSFER_ENABLED.lock().unwrap();
                                      if *lock != v {
                                          clipboard::ContextSend::enable(enabled);
                                          *lock = v;
                                      }
                                  }
                              }
                              _ => {}
                          }
                      }
                      Some(data) = rx.recv() => {
                          allow_err!(c.send(&data).await);
                      }
                      _ = timer.tick() => {
                          c.send(&ipc::Data::OnlineStatus(None)).await.ok();
                          c.send(&ipc::Data::Options(None)).await.ok();
                          c.send(&ipc::Data::Config(("id".to_owned(), None))).await.ok();
                          c.send(&ipc::Data::Config(("temporary-password".to_owned(), None))).await.ok();
                          #[cfg(feature = "flutter")]
                          c.send(&ipc::Data::VideoConnCount(None)).await.ok();
                          c.send(&ipc::Data::ControlPermissionsRemoteModify(None)).await.ok();
                          #[cfg(target_os = "windows")]
                          c.send(&ipc::Data::FileTransferEnabledState(None)).await.ok();
                      }
                  }
              }
          }
          if !reconnect {
              OPTIONS
                  .lock()
                  .unwrap()
                  .insert("ipc-closed".to_owned(), "Y".to_owned());
              break;
          }
          *UI_STATUS.lock().unwrap() = UiStatus {
              status_num: -1,
              #[cfg(not(feature = "flutter"))]
              key_confirmed,
              #[cfg(not(any(target_os = "android", target_os = "ios")))]
              mouse_time,
              #[cfg(not(feature = "flutter"))]
              id: id.clone(),
              #[cfg(feature = "flutter")]
              video_conn_count,
          };
          sleep(1.).await;
      }
  }
  
  #[allow(dead_code)]
  pub fn option_synced() -> bool {
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      {
          OPTION_SYNCED.lock().unwrap().clone()
      }
      #[cfg(any(target_os = "android", target_os = "ios"))]
      {
          true
      }
  }
  
  #[cfg(any(target_os = "android", feature = "flutter"))]
  #[cfg(not(any(target_os = "ios")))]
  #[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";
  
  #[inline]
  #[tokio::main(flavor = "current_thread")]
  pub async fn change_id_shared(id: String, old_id: String) -> String {
      let res = change_id_shared_(id, old_id).await.to_owned();
      *ASYNC_JOB_STATUS.lock().unwrap() = res.clone();
      res
  }
  
  pub async fn change_id_shared_(id: String, old_id: String) -> &'static str {
      if !hbb_common::is_valid_custom_id(&id) {
          log::debug!(
              "debugging invalid id: \"{id}\", len: {}, base64: \"{}\"",
              id.len(),
              crate::encode64(&id)
          );
          let bom = id.trim_start_matches('\u{FEFF}');
          log::debug!("bom: {}", hbb_common::is_valid_custom_id(&bom));
          return INVALID_FORMAT;
      }
  
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      let uuid = Bytes::from(
          hbb_common::machine_uid::get()
              .unwrap_or("".to_owned())
              .as_bytes()
              .to_vec(),
      );
      #[cfg(any(target_os = "android", target_os = "ios"))]
      let uuid = Bytes::from(hbb_common::get_uuid());
  
      if uuid.is_empty() {
          log::error!("Failed to change id, uuid is_empty");
          return UNKNOWN_ERROR;
      }
  
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      let rendezvous_servers = crate::ipc::get_rendezvous_servers(1_000).await;
      #[cfg(any(target_os = "android", target_os = "ios"))]
      let rendezvous_servers = Config::get_rendezvous_servers();
  
      let mut futs = Vec::new();
      let err: Arc<Mutex<&str>> = Default::default();
      for rendezvous_server in rendezvous_servers {
          let err = err.clone();
          let id = id.to_owned();
          let uuid = uuid.clone();
          let old_id = old_id.clone();
          futs.push(tokio::spawn(async move {
              let tmp = check_id(rendezvous_server, old_id, id, uuid).await;
              if !tmp.is_empty() {
                  *err.lock().unwrap() = tmp;
              }
          }));
      }
      join_all(futs).await;
      let err = *err.lock().unwrap();
      if err.is_empty() {
          #[cfg(not(any(target_os = "android", target_os = "ios")))]
          crate::ipc::set_config_async("id", id.to_owned()).await.ok();
          #[cfg(any(target_os = "android", target_os = "ios"))]
          {
              Config::set_key_confirmed(false);
              Config::set_id(&id);
          }
      }
      err
  }
  
  async fn check_id(
      rendezvous_server: String,
      old_id: String,
      id: String,
      uuid: Bytes,
  ) -> &'static str {
      if let Ok(mut socket) = hbb_common::socket_client::connect_tcp(
          crate::check_port(rendezvous_server, RENDEZVOUS_PORT),
          CONNECT_TIMEOUT,
      )
      .await
      {
          let mut msg_out = Message::new();
          msg_out.set_register_pk(RegisterPk {
              old_id,
              id,
              uuid,
              ..Default::default()
          });
          let mut ok = false;
          if socket.send(&msg_out).await.is_ok() {
              if let Some(msg_in) =
                  crate::common::get_next_nonkeyexchange_msg(&mut socket, None).await
              {
                  match msg_in.union {
                      Some(rendezvous_message::Union::RegisterPkResponse(rpr)) => {
                          match rpr.result.enum_value() {
                              Ok(register_pk_response::Result::OK) => {
                                  ok = true;
                              }
                              Ok(register_pk_response::Result::ID_EXISTS) => {
                                  return "Not available";
                              }
                              Ok(register_pk_response::Result::TOO_FREQUENT) => {
                                  return "Too frequent";
                              }
                              Ok(register_pk_response::Result::NOT_SUPPORT) => {
                                  return "server_not_support";
                              }
                              Ok(register_pk_response::Result::SERVER_ERROR) => {
                                  return "Server error";
                              }
                              Ok(register_pk_response::Result::INVALID_ID_FORMAT) => {
                                  return INVALID_FORMAT;
                              }
                              _ => {}
                          }
                      }
                      _ => {}
                  }
              }
          }
          if !ok {
              return UNKNOWN_ERROR;
          }
      } else {
          return "Failed to connect to rendezvous server";
      }
      ""
  }
  
  // if it's relay id, return id processed, otherwise return original id
  pub fn handle_relay_id(id: &str) -> &str {
      if id.ends_with(r"\r") || id.ends_with(r"/r") {
          &id[0..id.len() - 2]
      } else {
          id
      }
  }
  
  pub fn support_remove_wallpaper() -> bool {
      #[cfg(any(target_os = "windows", target_os = "linux"))]
      return crate::platform::WallPaperRemover::support();
      #[cfg(not(any(target_os = "windows", target_os = "linux")))]
      return false;
  }
  
  pub fn has_valid_2fa() -> bool {
      let raw = get_option("2fa");
      crate::auth_2fa::get_2fa(Some(raw)).is_some()
  }
  
  pub fn generate2fa() -> String {
      crate::auth_2fa::generate2fa()
  }
  
  pub fn verify2fa(code: String) -> bool {
      let res = crate::auth_2fa::verify2fa(code);
      if res {
          refresh_options();
      }
      res
  }
  
  pub fn has_valid_bot() -> bool {
      crate::auth_2fa::TelegramBot::get().map_or(false, |bot| bot.is_some())
  }
  
  pub fn verify_bot(token: String) -> String {
      match crate::auth_2fa::get_chatid_telegram(&token) {
          Err(err) => err.to_string(),
          Ok(None) => {
              "To activate the bot, simply send a message beginning with a forward slash (\"/\") like \"/hello\" to its chat.".to_owned()
          }
          _ => "".to_owned(),
      }
  }
  
  pub fn check_hwcodec() {
      #[cfg(feature = "hwcodec")]
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      {
          use std::sync::Once;
          static ONCE: Once = Once::new();
  
          ONCE.call_once(|| {
              if crate::platform::is_installed() {
                  ipc::notify_server_to_check_hwcodec().ok();
                  ipc::client_get_hwcodec_config_thread(3);
              } else {
                  scrap::hwcodec::start_check_process();
              }
          })
      }
  }
  
  #[cfg(feature = "flutter")]
  pub fn get_unlock_pin() -> String {
      #[cfg(any(target_os = "android", target_os = "ios"))]
      return String::default();
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      return ipc::get_unlock_pin();
  }
  
  #[cfg(feature = "flutter")]
  pub fn set_unlock_pin(pin: String) -> String {
      #[cfg(any(target_os = "android", target_os = "ios"))]
      return String::default();
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      match ipc::set_unlock_pin(pin, true) {
          Ok(_) => String::default(),
          Err(err) => err.to_string(),
      }
  }
  
  #[cfg(feature = "flutter")]
  pub fn get_trusted_devices() -> String {
      #[cfg(any(target_os = "android", target_os = "ios"))]
      return Config::get_trusted_devices_json();
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      return ipc::get_trusted_devices();
  }
  
  #[cfg(feature = "flutter")]
  pub fn remove_trusted_devices(json: &str) {
      let hwids = serde_json::from_str::<Vec<Bytes>>(json).unwrap_or_default();
      #[cfg(any(target_os = "android", target_os = "ios"))]
      Config::remove_trusted_devices(&hwids);
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      ipc::remove_trusted_devices(hwids);
  }
  
  #[cfg(feature = "flutter")]
  pub fn clear_trusted_devices() {
      #[cfg(any(target_os = "android", target_os = "ios"))]
      Config::clear_trusted_devices();
      #[cfg(not(any(target_os = "android", target_os = "ios")))]
      ipc::clear_trusted_devices();
  }
  
  #[cfg(feature = "flutter")]
  pub fn max_encrypt_len() -> usize {
      hbb_common::config::ENCRYPT_MAX_LEN
  }
  
  pub fn is_remote_modify_enabled_by_control_permissions() -> Option<bool> {
      *IS_REMOTE_MODIFY_ENABLED_BY_CONTROL_PERMISSIONS
          .lock()
          .unwrap()
  }
  