internationalization

This commit is contained in:
rustdesk 2021-12-25 16:45:22 +08:00
parent b3e3f6151d
commit af218dbc83
19 changed files with 774 additions and 243 deletions

30
Cargo.lock generated
View File

@ -570,6 +570,16 @@ dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "cstr_core"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "917ba9efe9e1e736671d5a03f006afc4e7e3f32503e2077e0bcaf519c0c8c1d3"
dependencies = [
"cty",
"memchr",
]
[[package]]
name = "ct-logs"
version = "0.6.0"
@ -589,6 +599,12 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "cty"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
[[package]]
name = "darling"
version = "0.10.2"
@ -2909,6 +2925,7 @@ dependencies = [
"serde_derive",
"serde_json 1.0.66",
"sha2",
"sys-locale",
"uuid",
"whoami",
"winapi 0.3.9",
@ -3282,6 +3299,19 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "sys-locale"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91f89ebb59fa30d4f65fafc2d68e94f6975256fd87e812dd99cb6e020c8563df"
dependencies = [
"cc",
"cstr_core",
"libc",
"web-sys",
"winapi 0.3.9",
]
[[package]]
name = "system-deps"
version = "1.3.2"

View File

@ -21,6 +21,7 @@ whoami = "1.1"
scrap = { path = "libs/scrap" }
hbb_common = { path = "libs/hbb_common" }
enigo = { path = "libs/enigo" }
sys-locale = "0.1"
serde_derive = "1.0"
serde = "1.0"
serde_json = "1.0"

View File

@ -34,6 +34,8 @@ pub const SEC30: Duration = Duration::from_secs(30);
pub struct Client;
pub use super::lang::*;
#[cfg(not(any(target_os = "android")))]
lazy_static::lazy_static! {
static ref AUDIO_HOST: Host = cpal::default_host();
@ -547,7 +549,8 @@ impl AudioHandler {
device: &Device,
) -> ResultType<()> {
let err_fn = move |err| {
log::error!("an error occurred on stream: {}", err);
// too many errors, will improve later
log::trace!("an error occurred on stream: {}", err);
};
let audio_buffer = self.audio_buffer.clone();
let stream = device.build_output_stream(
@ -658,6 +661,12 @@ impl LoginConfigHandler {
self.config = config;
}
pub fn set_option(&mut self, k: String, v: String) {
let mut config = self.load_config();
config.options.insert(k, v);
self.save_config(config);
}
pub fn save_view_style(&mut self, value: String) {
let mut config = self.load_config();
config.view_style = value;

35
src/lang.rs Normal file
View File

@ -0,0 +1,35 @@
use hbb_common::{config::Config, log};
use std::ops::Deref;
mod cn;
mod en;
mod fr;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn translate(name: String) -> String {
let locale = sys_locale::get_locale().unwrap_or_default().to_lowercase();
log::debug!("The current locale is {}", locale);
translate_locale(name, &locale)
}
pub fn translate_locale(name: String, locale: &str) -> String {
let mut lang = Config::get_option("lang");
if lang.is_empty() {
lang = locale
.split("-")
.last()
.map(|x| x.split("_").last().unwrap_or_default())
.unwrap_or_default()
.to_owned();
}
let m = match lang.to_lowercase().as_str() {
"fr" => fr::T.deref(),
"cn" => cn::T.deref(),
_ => en::T.deref(),
};
if let Some(v) = m.get(&name as &str) {
v.to_string()
} else {
name
}
}

190
src/lang/cn.rs Normal file
View File

@ -0,0 +1,190 @@
lazy_static::lazy_static! {
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[
("Status", "状态"),
("Your Desktop", "你的桌面"),
("desk_tip", "你的桌面可以通过下面的 ID和密码访问。"),
("Password", "密码"),
("Ready", "就绪"),
("connecting_status", "正在接入RustDesk网络..."),
("Enable Service", "允许服务"),
("Start Service", "启动服务"),
("Service is not running", "服务没有启动"),
("not_ready_status", "未就绪,请检查网络连接"),
("Control Remote Desktop", "控制远程桌面"),
("Transfer File", "传输文件"),
("Connect", "连接"),
("Recent Sessions", "最近访问过"),
("Address Book", "地址簿"),
("Confirmation", "确认"),
("TCP Tunneling", "TCP隧道"),
("Remove", "删除"),
("Refresh random password", "刷新随机密码"),
("Set your own password", "设置密码"),
("Enable Keyboard/Mouse", "允许控制键盘/鼠标"),
("Enable Clipboard", "允许同步剪贴板"),
("Enable File Transfer", "允许传输文件"),
("Enable TCP Tunneling", "允许建立TCP隧道"),
("IP Whitelisting", "IP白名单"),
("ID/Relay Server", "ID/中继服务器"),
("Stop service", "停止服务"),
("Change ID", "改变ID"),
("Website", "网站"),
("About", "关于"),
("Mute", "静音"),
("Audio Input", "音频输入"),
("ID Server", "ID服务器"),
("Relay Server", "中继服务器"),
("API Server", "API服务器"),
("invalid_http", "必须以http://或者https://开头"),
("Invalid IP", "无效IP"),
("id_change_tip", "只可以使用字母a-z, A-Z, 0-9, _ (下划线)。首字母必须是a-z, A-Z。长度在6与16之间。"),
("Invalid format", "无效格式"),
("This function is turned off by the server", "服务器关闭了此功能"),
("Not available", "已被占用"),
("Too frequent", "修改太频繁,请稍后再试"),
("Cancel", "取消"),
("Skip", "跳过"),
("Close", "关闭"),
("Retry", "再试"),
("OK", "确认"),
("Password Required", "需要密码"),
("Please enter your password", "请输入密码"),
("Remember password", "记住密码"),
("Wrong Password", "密码错误"),
("Do you want to enter again?", "还想输入一次吗?"),
("Connection Error", "连接错误"),
("Error", "错误"),
("Reset by the peer", "连接被对方关闭"),
("Connecting...", "正在连接..."),
("Connection in progress. Please wait.", "连接进行中,请稍等。"),
("Please try 1 minute later", "一分钟后再试"),
("Login Error", "登录错误"),
("Successful", "成功"),
("Connected, waiting for image...", "已连接,等待画面传输..."),
("Name", "文件名"),
("Modified", "修改时间"),
("Size", "大小"),
("Show Hidden Files", "显示隐藏文件"),
("Receive", "接受"),
("Send", "发送"),
("Remote Computer", "远程电脑"),
("Local Computer", "本地电脑"),
("Confirm Delete", "确认删除"),
("Are you sure you want to delete this file?", "是否删除此文件?"),
("Do this for all conflicts", "应用于其它冲突"),
("Deleting", "正在删除"),
("files", "文件"),
("Waiting", "等待..."),
("Finished", "完成"),
("Custom Image Quality", "设置画面质量"),
("Privacy mode", "隐私模式"),
("Adjust Window", "调节窗口"),
("Original", "原始比例"),
("Shrink", "收缩"),
("Stretch", "伸展"),
("Good image quality", "好画质"),
("Balanced", "一般画质"),
("Optimize reaction time", "优化反应时间"),
("Custom", "自定义画质"),
("Show remote cursor", "显示远程光标"),
("Disable clipboard", "禁止剪贴板"),
("Lock after session end", "断开后锁定远程电脑"),
("Insert", "插入"),
("Insert Lock", "锁定远程电脑"),
("Refresh", "刷新画面"),
("ID does not exist", "ID不存在"),
("Failed to connect to rendezvous server", "连接注册服务器失败"),
("Please try later", "请稍后再试"),
("Remote desktop is offline", "远程电脑不在线"),
("Key mismatch", "Key不匹配"),
("Timeout", "连接超时"),
("Failed to connect to relay server", "无法连接到中继服务器"),
("Failed to connect via rendezvous server", "无法通过注册服务器建立连接"),
("Failed to connect via relay server", "无法通过中继服务器建立连接"),
("Failed to make direct connection to remote desktop", "无法建立直接连接"),
("Set Password", "设置密码"),
("OS Password", "操作系统密码"),
("install_tip", "你正在运行未安装版本由于UAC限制作为被控端会在某些情况下无法控制鼠标键盘或者录制屏幕请点击下面的按钮将RustDesk安装到系统从而规避上述问题。"),
("Click to upgrade", "点击这里升级"),
("Configuration Permissions", "配置权限"),
("Configure", "配置"),
("config_acc", "为了能够远程控制你的桌面, 请给予RustDesk\"辅助功能\" 权限。"),
("config_screen", "为了能够远程访问你的桌面, 请给予RustDesk\"屏幕录制\" 权限。"),
("Installing ...", "安装 ..."),
("Install", "安装"),
("Installation", "安装"),
("Installation Path", "安装路径"),
("Create start menu shortcuts", "创建启动菜单快捷方式"),
("Create desktop icon", "创建桌面图标"),
("agreement_tip", "开始安装即表示接受许可协议。"),
("Accept and Install", "同意并安装"),
("End-user license agreement", "用户协议"),
("Generating ...", "正在产生 ..."),
("Your installation is lower version.", "你安装的版本比当前运行的低。"),
("not_close_tcp_tip", "请在使用隧道的时候,不要关闭本窗口"),
("Listening ...", "正在等待隧道连接 ..."),
("Remote Host", "远程主机"),
("Remote Port", "远程端口"),
("Action", "动作"),
("Add", "添加"),
("Local Port", "本地端口"),
("setup_server_tip", "如果需要更快连接速度,你可以选择自建服务器"),
("Too short, at least 6 characters.", "太短了至少6个字符"),
("The confirmation is not identical.", "两次输入不匹配"),
("Permissions", "权限"),
("Accept", "接受"),
("Dismiss", "拒绝"),
("Disconnect", "断开连接"),
("Allow using keyboard and mouse", "允许使用键盘鼠标"),
("Allow using clipboard", "允许使用剪贴板"),
("Allow hearing sound", "允许听到声音"),
("Connected", "已经连接"),
("Direct and encrypted connection", "加密直连"),
("Relayed and encrypted connection", "加密中继连接"),
("Direct and unencrypted connection", "非加密直连"),
("Relayed and unencrypted connection", "非加密中继连接"),
("Enter Remote ID", "输入对方ID"),
("Enter your password", "输入密码"),
("Logging in...", "正在登录..."),
("Enable RDP session sharing", "允许RDP会话共享"),
("Auto Login", "自动登录(设置断开后锁定才有效)"),
("Enable Direct IP Access", "允许IP直接访问"),
("Rename", "改名"),
("Space", "空格"),
("Create Desktop Shortcut", "创建桌面快捷方式"),
("Change Path", "改变路径"),
("Create Folder", "创建文件夹"),
("Please enter the folder name", "请输入文件夹名称"),
("Fix it", "修复"),
("Warning", "警告"),
("Login screen using Wayland is not supported", "不支持使用 Wayland 登录界面"),
("Reboot required", "重启后才能生效"),
("Unsupported display server ", "不支持当前显示服务器"),
("x11 expected", "请切换到 x11"),
("Port", "端口"),
("Settings", "设置"),
("Username", " 用户名"),
("Invalid port", "无效端口"),
("Closed manually by the peer", "被对方手动关闭"),
("Enable remote configuration modification", "允许远程修改配置"),
("Run without install", "无安装运行"),
("Always connected via relay", "强制走中继连接"),
("Always connect via relay", "强制走中继连接"),
("whitelist_tip", "只有白名单里的ip才能访问我"),
("Login", "登录"),
("Logout", "登出"),
("Tags", "标签"),
("Search ID", "查找ID"),
("Current Wayland display server is not supported", "不支持 Wayland 显示服务器"),
("whitelist_sep", "可以使用逗号,分号,空格或者换行符作为分隔符"),
("Add ID", "增加ID"),
("Add Tag", "增加标签"),
("Unselect all tags", "取消选择所有标签"),
("Network error", "网络错误"),
("Username missed", "用户名没有填写"),
("Password missed", "密码没有填写"),
("Wrong credentials", "用户名或者密码错误"),
("Edit Tag", "修改标签"),
].iter().cloned().collect();
}

20
src/lang/en.rs Normal file
View File

@ -0,0 +1,20 @@
lazy_static::lazy_static! {
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[
("desk_tip", "Your desktop can be accessed with this ID and password."),
("connecting_status", "Connecting to the RustDesk network..."),
("not_ready_status", "Not ready. Please check your connection"),
("id_change_tip", "Only a-z, A-Z, 0-9 and _ (underscore) characters allowed. The first letter must be a-z, A-Z. Length between 6 and 16."),
("install_tip", "Due to UAC, RustDesk can not work properly as the remote side in some cases. To avoid UAC, please click the button below to install RustDesk to the system."),
("config_acc", "In order to control your Desktop remotely, you need to grant RustDesk \"Accessibility\" permissions."),
("config_screen", "In order to access your Desktop remotely, you need to grant RustDesk \"Screen Recording\" permissions."),
("agreement_tip", "By starting the installation, you accept the license agreement."),
("not_close_tcp_tip", "Don't close this window while you are using the tunnel"),
("setup_server_tip", "For faster connection, please set up your own server"),
("Auto Login", "Auto Login (Only valid if you set \"Lock after session end\")"),
("whitelist_tip", "Only whitelisted IP can access me"),
("whitelist_sep", "Seperated by comma, semicolon, spaces or new line"),
("Wrong credentials", "Wrong username or password"),
("invalid_http", "must start with http:// or https://"),
].iter().cloned().collect();
}

190
src/lang/fr.rs Normal file
View File

@ -0,0 +1,190 @@
lazy_static::lazy_static! {
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[
("Status", "Statut"),
("Your Desktop", "Votre bureau"),
("desk_tip", "Votre bureau est accessible via l'identifiant et le mot de passe ci-dessous."),
("Password", "Mot de passe"),
("Ready", "Prêt"),
("connecting_status", "Connexion au réseau RustDesk..."),
("Enable Service", "Autoriser le service"),
("Start Service", "Démarrer le service"),
("Service is not running", "Le service ne fonctionne pas"),
("not_ready_status", "Pas prêt, veuillez vérifier la connexion réseau"),
("Control Remote Desktop", "Contrôler le bureau à distance"),
("Transfer File", "Transférer le fichier"),
("Connect", "Connecter"),
("Recent Sessions", "Sessions récentes"),
("Address Book", "Carnet d'adresses"),
("Confirmation", "Confirmation"),
("TCP Tunneling", "Tunneling TCP"),
("Remove", "Supprimer"),
("Refresh random password", "Actualiser le mot de passe aléatoire"),
("Set your own password", "Définir votre propre mot de passe"),
("Enable Keyboard/Mouse", "Activer le contrôle clavier/souris"),
("Enable Clipboard", "Activer la synchronisation du presse-papiers"),
("Enable File Transfer", "Activer le transfert de fichiers"),
("Enable TCP Tunneling", "Activer le tunneling TCP"),
("IP Whitelisting", "Liste blanche IP"),
("ID/Relay Server", "ID/Serveur Relais"),
("Stop service", "Arrêter service"),
("Change ID", "Changer d'ID"),
("Website", "Site Web"),
("About", "Sur"),
("Mute", "Muet"),
("Audio Input", "Entrée audio"),
("ID Server", "Serveur ID"),
("Relay Server", "Serveur Relais"),
("API Server", "Serveur API"),
("invalid_http", "Doit commencer par http:// ou https://"),
("Invalid IP", "IP invalide"),
("id_change_tip", "Seules les lettres a-z, A-Z, 0-9, _ (trait de soulignement) peuvent être utilisées. La première lettre doit être a-z, A-Z. La longueur est comprise entre 6 et 16."),
("Invalid format", "Format invalide"),
("This function is turned off by the server", "Cette fonction est désactivée par le serveur"),
("Not available", "Indisponible"),
("Too frequent", "Modifier trop fréquemment, veuillez réessayer plus tard"),
("Cancel", "Annuler"),
("Skip", "Ignorer"),
("Close", "Fermer"),
("Retry", "Réessayer"),
("OK", "Confirmer"),
("Password Required", "Mot de passe requis"),
("Please enter your password", "Veuillez saisir votre mot de passe"),
("Remember password", "Mémoriser le mot de passe"),
("Wrong Password", "Mauvais mot de passe"),
("Do you want to enter again?", "Voulez-vous participer à nouveau ?"),
("Connection Error", "Erreur de connexion"),
("Error", "Erreur"),
("Reset by the peer", "La connexion a été fermée par le pair"),
("Connecting...", "Connexion..."),
("Connection in progress. Please wait.", "Connexion en cours. Veuillez patienter."),
("Please try 1 minute later", "Réessayez dans une minute"),
("Login Error", "Erreur de connexion"),
("Successful", "Succès"),
("Connected, waiting for image...", "Connecté, en attente de transmission d'image..."),
("Name", "Nom du fichier"),
("Modified", "Modifié"),
("Size", "Taille"),
("Show Hidden Files", "Afficher les fichiers cachés"),
("Receive", "Accepter"),
("Send", "Envoyer"),
("Remote Computer", "Ordinateur distant"),
("Local Computer", "Ordinateur local"),
("Confirm Delete", "Confirmer la suppression"),
("Are you sure you want to delete this file?", "Voulez-vous vraiment supprimer ce fichier ?"),
("Do this for all conflicts", "Appliquer à d'autres conflits"),
("Deleting", "Suppression"),
("files", "fichier"),
("Waiting", "En attente en attente..."),
("Finished", "Terminé"),
("Custom Image Quality", "Définir la qualité d'image"),
("Privacy mode", "Mode privé"),
("Adjust Window", "Ajuster la fenêtre"),
("Original", "Ratio d'origine"),
("Shrink", "Rétréci"),
("Stretch", "Étirer"),
("Good image quality", "Bonne qualité d'image"),
("Balanced", "Qualité d'image normale"),
("Optimize reaction time", "Optimiser le temps de réaction"),
("Custom", "Qualité d'image personnalisée"),
("Show remote cursor", "Afficher le curseur distant"),
("Disable clipboard", "Désactiver le presse-papiers"),
("Lock after session end", "Verrouiller l'ordinateur distant après la déconnexion"),
("Insert", "Insérer"),
("Insert Lock", "Verrouiller l'ordinateur distant"),
("Refresh", "Rafraîchir l'écran"),
("ID does not exist", "L'ID n'existe pas"),
("Failed to connect to rendezvous server", "Échec de la connexion au serveur de rendez-vous"),
("Please try later", "Veuillez essayer plus tard"),
("Remote desktop is offline", "Le bureau à distance est hors ligne"),
("Key mismatch", "Discordance de clé"),
("Timeout", "Connexion expirée"),
("Failed to connect to relay server", "Échec de la connexion au serveur relais"),
("Failed to connect via rendezvous server", "Échec de l'établissement d'une connexion via le serveur de rendez-vous"),
("Failed to connect via relay server", "Impossible d'établir une connexion via le serveur relais"),
("Failed to make direct connection to remote desktop", "Impossible d'établir une connexion directe"),
("Set Password", "Définir le mot de passe"),
("OS Password", "Mot de passe du système d'exploitation"),
("install_tip", "Vous utilisez une version désinstallée. En raison des restrictions UAC, en tant que terminal contrôlé, dans certains cas, il ne sera pas en mesure de contrôler la souris et le clavier ou d'enregistrer l'écran. Veuillez cliquer sur le bouton ci-dessous pour installer RustDesk au système pour éviter la question ci-dessus."),
("Click to upgrade", "Cliquez pour mettre à niveau"),
("Configuration Permissions", "Autorisations de configuration"),
("Configure", "Configurer"),
("config_acc", "Afin de pouvoir contrôler votre bureau à distance, veuillez donner l'autorisation\"accessibilité\" à RustDesk."),
("config_screen", "Afin de pouvoir accéder à votre bureau à distance, veuillez donner l'autorisation à RustDesk\"enregistrement d'écran\"."),
("Installing ...", "Installation ..."),
("Install", "Installer"),
("Installation", "Installation"),
("Installation Path", "Chemin d'installation"),
("Create start menu shortcuts", "Créer des raccourcis dans le menu démarrer"),
("Create desktop icon", "Créer une icône sur le bureau"),
("agreement_tip", "Démarrer l'installation signifie accepter le contrat de licence."),
("Accept and Install", "Accepter et installer"),
("End-user license agreement", "Contrat d'utilisateur"),
("Generating ...", "Génération ..."),
("Your installation is lower version.", "La version que vous avez installée est inférieure à la version en cours d'exécution."),
("not_close_tcp_tip", "Veuillez ne pas fermer cette fenêtre lors de l'utilisation du tunnel"),
("Listening ...", "En attente de connexion tunnel..."),
("Remote Host", "Hôte distant"),
("Remote Port", "Port distant"),
("Action", "Action"),
("Add", "Ajouter"),
("Local Port", "Port local"),
("setup_server_tip", "Si vous avez besoin d'une vitesse de connexion plus rapide, vous pouvez choisir de créer votre propre serveur"),
("Too short, at least 6 characters.", "Trop court, au moins 6 caractères."),
("The confirmation is not identical.", "Les deux entrées ne correspondent pas"),
("Permissions", "Autorisations"),
("Accept", "Accepter"),
("Dismiss", "Rejeter"),
("Disconnect", "Déconnecter"),
("Allow using keyboard and mouse", "Autoriser l'utilisation du clavier et de la souris"),
("Allow using clipboard", "Autoriser l'utilisation du presse-papiers"),
("Allow hearing sound", "Autoriser l'audition du son"),
("Connected", "Connecté"),
("Direct and encrypted connection", "Connexion directe cryptée"),
("Relayed and encrypted connection", "Connexion relais cryptée"),
("Direct and unencrypted connection", "Connexion directe non cryptée"),
("Relayed and unencrypted connection", "Connexion relais non cryptée"),
("Enter Remote ID", "Entrez l'ID à distance"),
("Enter your password", "Entrez votre mot de passe"),
("Logging in...", "Se connecter..."),
("Enable RDP session sharing", "Activer le partage de session RDP"),
("Auto Login", "Connexion automatique (le verrouillage ne sera effectif qu'après la déconnexion du paramètre)"),
("Enable Direct IP Access", "Autoriser l'accès direct IP"),
("Rename", "Renommer"),
("Space", "Espace"),
("Create Desktop Shortcut", "Créer un raccourci sur le bureau"),
("Change Path", "Changer de chemin"),
("Create Folder", "Créer un dossier"),
("Please enter the folder name", "Veuillez saisir le nom du dossier"),
("Fix it", "Réparez-le"),
("Warning", "Avertissement"),
("Login screen using Wayland is not supported", "L'écran de connexion utilisant Wayland n'est pas pris en charge"),
("Reboot required", "Redémarrage pour prendre effet"),
("Unsupported display server ", "Le serveur d'affichage actuel n'est pas pris en charge"),
("x11 expected", "Veuillez passer à x11"),
("Port", "Port"),
("Settings", "Paramètres"),
("Username", " Nom d'utilisateur"),
("Invalid port", "Port invalide"),
("Closed manually by the peer", "Fermé manuellement par le pair"),
("Enable remote configuration modification", "Autoriser la modification de la configuration à distance"),
("Run without install", "Exécuter sans installer"),
("Always connected via relay", "Forcer la connexion relais"),
("Always connect via relay", "Forcer la connexion relais"),
("whitelist_tip", "Seul l'ip dans la liste blanche peut m'accéder"),
("Login", "Connexion"),
("Logout", "Déconnexion"),
("Tags", "Étiqueter"),
("Search ID", "Identifiant de recherche"),
("Current Wayland display server is not supported", "Le serveur d'affichage Wayland n'est pas pris en charge"),
("whitelist_sep", "Vous pouvez utiliser une virgule, un point-virgule, un espace ou une nouvelle ligne comme séparateur"),
("Add ID", "Ajouter ID"),
("Add Tag", "Ajouter une balise"),
("Unselect all tags", "Désélectionner toutes les balises"),
("Network error", "Erreur réseau"),
("Username missed", "Nom d'utilisateur manqué"),
("Password missed", "Mot de passe manqué"),
("Wrong credentials", "Identifiant ou mot de passe erroné"),
("Edit Tag", "Modifier la balise"),
].iter().cloned().collect();
}

View File

@ -27,3 +27,4 @@ use common::*;
pub mod cli;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
mod port_forward;
mod lang;

View File

@ -562,6 +562,11 @@ impl UI {
allow_err!(std::process::Command::new(p).arg(url).spawn());
}
fn t(&self, name: String) -> String {
crate::client::translate(name)
}
fn is_xfce(&self) -> bool {
crate::platform::is_xfce()
}
@ -569,6 +574,7 @@ impl UI {
impl sciter::EventHandler for UI {
sciter::dispatch_script_call! {
fn t(String);
fn is_xfce();
fn get_id();
fn get_password();

View File

@ -300,6 +300,10 @@ impl ConnectionManager {
fn exit(&self) {
std::process::exit(0);
}
fn t(&self, name: String) -> String {
crate::client::translate(name)
}
}
impl sciter::EventHandler for ConnectionManager {
@ -308,6 +312,7 @@ impl sciter::EventHandler for ConnectionManager {
}
sciter::dispatch_script_call! {
fn t(String);
fn get_icon();
fn close(i32);
fn authorize(i32);

View File

@ -33,22 +33,22 @@ class Body: Reactor.Component
<div>
<div .id style="font-weight: bold; font-size: 1.2em;">{c.name}</div>
<div .id>({c.peer_id})</div>
<div style="margin-top: 1.2em">Connected <span #time>{getElaspsed(c.time)}</span></div>
<div style="margin-top: 1.2em">{translate('Connected')} {" "} <span #time>{getElaspsed(c.time)}</span></div>
</div>
</div>
<div />
{c.is_file_transfer || c.port_forward ? "" : <div>Permissions</div>}
{c.is_file_transfer || c.port_forward ? "" : <div>{translate('Permissions')}</div>}
{c.is_file_transfer || c.port_forward ? "" : <div .permissions>
<div class={!c.keyboard ? "disabled" : ""} title="Allow using keyboard and mouse"><icon .keyboard /></div>
<div class={!c.clipboard ? "disabled" : ""} title="Allow using clipboard"><icon .clipboard /></div>
<div class={!c.audio ? "disabled" : ""} title="Allow hearing sound"><icon .audio /></div>
<div class={!c.keyboard ? "disabled" : ""} title={translate('Allow using keyboard and mouse')}><icon .keyboard /></div>
<div class={!c.clipboard ? "disabled" : ""} title={translate('Allow using clipboard')}><icon .clipboard /></div>
<div class={!c.audio ? "disabled" : ""} title={translate('Allow hearing sound')}><icon .audio /></div>
</div>}
{c.port_forward ? <div>Port Forwarding: {c.port_forward}</div> : ""}
<div style="size:*"/>
<div .buttons>
{auth ? "" : <button .button tabindex="-1" #accept>Accept</button>}
{auth ? "" : <button .button tabindex="-1" .outline #dismiss>Dismiss</button>}
{auth ? <button .button tabindex="-1" #disconnect>Disconnect</button> : ""}
{auth ? "" : <button .button tabindex="-1" #accept>{translate('Accept')}</button>}
{auth ? "" : <button .button tabindex="-1" .outline #dismiss>{translate('Dismiss')}</button>}
{auth ? <button .button tabindex="-1" #disconnect>{translate('Disconnect')}</button> : ""}
</div>
{c.is_file_transfer || c.port_forward ? "" : <div .chaticon>{svg_chat}</div>}
</div>
@ -101,6 +101,7 @@ class Body: Reactor.Component
connection.authorized = true;
body.update();
handler.authorize(cid);
view.windowState = View.WINDOW_MINIMIZED;
});
}

View File

@ -10,6 +10,15 @@ var is_file_transfer;
var is_xfce = false;
try { is_xfce = handler.is_xfce(); } catch(e) {}
function translate(name) {
try {
return handler.t(name);
} catch(_) {
return name;
}
}
function hashCode(str) {
var hash = 160 << 16 + 114 << 8 + 91;
for (var i = 0; i < str.length; i += 1) {
@ -207,7 +216,7 @@ function getMsgboxParams() {
return msgbox_params;
}
function msgbox(type, title, text, callback, height, width, retry=0) {
function msgbox(type, title, text, callback, height, width, retry=0, contentStyle="") {
var has_msgbox = msgbox_params != null;
if (!has_msgbox && !type) return;
var remember = false;
@ -217,7 +226,8 @@ function msgbox(type, title, text, callback, height, width, retry=0) {
msgbox_params = {
remember: remember, type: type, text: text, title: title,
getParams: getMsgboxParams,
callback: callback, retry: retry,
callback: callback, translate: translate,
retry: retry, contentStyle: contentStyle,
};
if (has_msgbox) return;
var dialog = {
@ -239,7 +249,7 @@ function msgbox(type, title, text, callback, height, width, retry=0) {
} else if (res == "!alive") {
// do nothing
} else if (res.type == "input-password") {
if (!is_port_forward) connecting();
if (!is_port_forward) handler.msgbox("connecting", "Connecting...", "Logging in...");
handler.login(res.password, res.remember);
} else if (res.reconnect) {
if (!is_port_forward) connecting();
@ -251,10 +261,10 @@ function connecting() {
handler.msgbox("connecting", "Connecting...", "Connection in progress. Please wait.");
}
handler.msgbox = function(type, title, text, callback=null, height=180, width=500, retry=0) {
handler.msgbox = function(type, title, text, callback=null, height=180, width=500, retry=0, contentStyle="") {
// directly call view.Dialog from native may crash, add timer here, seem safe
// too short time, msgbox won't get focus, per my test, 150 is almost minimun
self.timer(150ms, function() { msgbox(type, title, text, callback, height, width, retry); });
self.timer(150ms, function() { msgbox(type, title, text, callback, height, width, retry, contentStyle); });
}
handler.block_msgbox = function(type, title, text, callback=null, height=180, width=500, retry=0) {
@ -312,6 +322,47 @@ function Progress()
this.value = "";
}
var svg_eye_cross = <svg viewBox="0 -21 511.96 511">
<path d="m506.68 261.88c7.043-16.984 7.043-36.461 0-53.461-41.621-100.4-140.03-165.27-250.71-165.27-46.484 0-90.797 11.453-129.64 32.191l-68.605-68.609c-8.3438-8.3398-21.824-8.3398-30.168 0-8.3398 8.3398-8.3398 21.824 0 30.164l271.49 271.49 86.484 86.488 68.676 68.672c4.1797 4.1797 9.6406 6.2695 15.102 6.2695 5.4609 0 10.922-2.0898 15.082-6.25 8.3438-8.3398 8.3438-21.824 0-30.164l-62.145-62.145c36.633-27.883 66.094-65.109 84.438-109.38zm-293.91-100.1c12.648-7.5742 27.391-11.969 43.199-11.969 47.062 0 85.332 38.273 85.332 85.336 0 15.805-4.3945 30.547-11.969 43.199z"/>
<path d="m255.97 320.48c-47.062 0-85.336-38.273-85.336-85.332 0-3.0938 0.59766-6.0195 0.91797-9.0039l-106.15-106.16c-25.344 24.707-46.059 54.465-60.117 88.43-7.043 16.98-7.043 36.457 0 53.461 41.598 100.39 140.01 165.27 250.69 165.27 34.496 0 67.797-6.3164 98.559-18.027l-89.559-89.559c-2.9844 0.32031-5.9062 0.91797-9 0.91797z"/>
</svg>;
class PasswordComponent: Reactor.Component {
this var visible = false;
this var value = '';
this var name = 'password';
function this(params) {
if (params && params.value) {
this.value = params.value;
}
if (params && params.name) {
this.name = params.name;
}
}
function render() {
return <div .password>
<input name={this.name} value={this.value} type={this.visible ? "text" : "password"} .outline-focus />
{this.visible ? svg_eye_cross : svg_eye}
</div>;
}
event click $(svg) {
var el = this.$(input);
var value = el.value;
var start = el.xcall(#selectionStart) || 0;
var end = el.xcall(#selectionEnd);
this.update({ visible: !this.visible });
self.timer(30ms, function() {
var el = this.$(input);
view.focus = el;
el.value = value;
el.xcall(#setSelection, start, end);
});
}
}
function isReasonableSize(r) {
var x = r[0];
var y = r[1];

View File

@ -141,11 +141,11 @@ class JobTable: Reactor.Component {
}
function getStatus(job) {
if (!job.entries) return "Waiting";
if (!job.entries) return translate("Waiting");
var i = job.file_num + 1;
var n = job.num_entries || job.entries.length;
if (i > n) i = n;
var res = i + ' / ' + n + " files";
var res = i + ' / ' + n + " " + translate("files");
if (job.total_size > 0) {
var s = getSize(0, job.finished_size);
if (s) s += " / ";
@ -155,7 +155,7 @@ class JobTable: Reactor.Component {
var percent = job.total_size == 0 ? 100 : (100. * job.finished_size / job.total_size).toInteger(); // (100. * i / (n || 1)).toInteger();
if (job.finished) percent = '100';
if (percent) res += ", " + percent + "%";
if (job.finished) res = "Finished " + res;
if (job.finished) res = translate("Finished") + " " + res;
if (job.speed) res += ", " + getSize(0, job.speed) + "/s";
return res;
}
@ -250,7 +250,7 @@ class FolderView : Reactor.Component {
return <div .title>
{svg_computer}
<div .platform>{platformSvg(handler.get_platform(this.is_remote), "white")}</div>
<div><span>{this.is_remote ? "Remote Computer" : "Local Computer"}</span></div>
<div><span>{translate(this.is_remote ? "Remote Computer" : "Local Computer")}</span></div>
</div>
}
@ -273,7 +273,7 @@ class FolderView : Reactor.Component {
function renderOpBar() {
if (this.is_remote) {
return <div .toolbar .remote>
<div .send .button>{svg_send}<span>Receive</span></div>
<div .send .button>{svg_send}<span>{translate('Receive')}</span></div>
<div .spacer></div>
<div .add-folder .button>{svg_add_folder}</div>
<div .trash .button>{svg_trash}</div>
@ -283,7 +283,7 @@ class FolderView : Reactor.Component {
<div .add-folder .button>{svg_add_folder}</div>
<div .trash .button>{svg_trash}</div>
<div .spacer></div>
<div .send .button><span>Send</span>{svg_send}</div>
<div .send .button><span>{translate('Send')}</span>{svg_send}</div>
</div>;
}
@ -308,14 +308,14 @@ class FolderView : Reactor.Component {
var id = (this.is_remote ? "remote" : "local") + "-folder-view";
return <table @{this.table} .folder-view .has_current id={id}>
<thead>
<tr><th></th><th .sortable>Name</th><th .sortable>Modified</th><th .sortable>Size</th></tr>
<tr><th></th><th .sortable>{translate('Name')}</th><th .sortable>{translate('Modified')}</th><th .sortable>{translate('Size')}</th></tr>
</thead>
<tbody>
{rows}
</tbody>
<popup>
<menu.context id={id}>
<li #switch-hidden class={this.show_hidden ? "selected" : ""}><span>{svg_checkmark}</span>Show Hidden Files</li>
<li #switch-hidden class={this.show_hidden ? "selected" : ""}><span>{svg_checkmark}</span>{translate('Show Hidden Files')}</li>
</menu>
</popup>
</table>;
@ -431,8 +431,8 @@ class FolderView : Reactor.Component {
event click $(.add-folder) () {
var me = this;
handler.msgbox("custom", "Create Folder", "<div .form> \
<div>Please enter the folder name:</div> \
handler.msgbox("custom", translate("Create Folder"), "<div .form> \
<div>" + translate("Please enter the folder name") + ":</div> \
<div><input|text(name) .outline-focus /></div> \
</div>", function(res=null) {
if (!res) return;
@ -523,7 +523,7 @@ class FolderView : Reactor.Component {
var file_transfer;
class FileTransfer: Reactor.Component {
function this(params) {
function this() {
file_transfer = this;
}
@ -600,7 +600,7 @@ var create_dir_jobs = {}
function confirmDelete(path, is_remote) {
handler.block_msgbox("custom-skip", "Confirm Delete", "<div .form> \
<div>Are you sure you want to delete this file?</div> \
<div>" + translate('Are you sure you want to delete this file?') + "</div> \
<div.ellipsis style=\"font-weight: bold;\">" + path + "</div> \
</div>", function(res=null) {
if (res) {
@ -620,10 +620,10 @@ handler.confirmDeleteFiles = function(id, i, name) {
var file_path = job.path;
if (name) file_path += handler.get_path_sep(job.is_remote) + name;
handler.block_msgbox("custom-skip", "Confirm Delete", "<div .form> \
<div>Deleting #" + (i + 1) + " of " + n + " files.</div> \
<div>Are you sure you want to delete this file?</div> \
<div>" + translate('Deleting') + " #" + (i + 1) + " / " + n + " " + translate('files') + ".</div> \
<div>" + translate('Are you sure you want to delete this file?') + "</div> \
<div.ellipsis style=\"font-weight: bold;\" .text>" + name + "</div> \
<div><button|checkbox(remember) {ts}>Do this for all conflicts</button></div> \
<div><button|checkbox(remember) {ts}>" + translate('Do this for all conflicts') + "</button></div> \
</div>", function(res=null) {
if (!res) {
jt.updateJobStatus(id, i - 1, "cancel");

View File

@ -31,6 +31,10 @@ if (is_linux) {
}
}
function get_id() {
return handler.get_option('alias') || handler.get_id()
}
function stateChanged() {
stdout.println('state changed from ' + cur_window_state + ' -> ' + view.windowState);
cur_window_state = view.windowState;
@ -58,7 +62,7 @@ var old_window_state = View.WINDOW_SHOWN;
var input_blocked;
class Header: Reactor.Component {
function this(params) {
function this() {
header = this;
}
@ -67,18 +71,18 @@ class Header: Reactor.Component {
var title_conn;
if (this.secure_connection && this.direct_connection) {
icon_conn = svg_secure;
title_conn = "Direct and secure connection";
title_conn = translate("Direct and encrypted connection");
} else if (this.secure_connection && !this.direct_connection) {
icon_conn = svg_secure_relay;
title_conn = "Relayed and secure connection";
title_conn = translate("Relayed and encrypted connection");
} else if (!this.secure_connection && this.direct_connection) {
icon_conn = svg_insecure;
title_conn = "Direct and insecure connection";
title_conn = translate("Direct and unencrypted connection");
} else {
icon_conn = svg_insecure_relay;
title_conn = "Relayed and insecure connection";
title_conn = translate("Relayed and unencrypted connection");
}
var title = handler.get_id();
var title = get_id();
if (pi.hostname) title += "(" + pi.username + "@" + pi.hostname + ")";
if ((pi.displays || []).length == 0) {
return <div .ellipsis style="size:*;text-align:center;margin:*;">{title}</div>;
@ -89,14 +93,14 @@ class Header: Reactor.Component {
</div>;
});
updateWindowToolbarPosition();
var style = "flow: horizontal;";
if (is_osx) style += "margin: *";
var style = "flow:horizontal;";
if (is_osx) style += "margin:*";
self.timer(1ms, toggleMenuState);
return <div style={style}>
{is_osx || is_xfce ? "" : <span #fullscreen>{svg_fullscreen}</span>}
<div #screens>
<span #secure title={title_conn}>{icon_conn}</span>
<div .remote-id>{handler.get_id()}</div>
<div .remote-id>{get_id()}</div>
<div style="flow:horizontal;border-spacing: 0.5em;">{screens}</div>
{this.renderGlobalScreens()}
</div>
@ -111,22 +115,22 @@ class Header: Reactor.Component {
function renderDisplayPop() {
return <popup>
<menu.context #display-options>
<li #adjust-window style="display:none">Adjust Window</li>
<li #adjust-window style="display:none">{translate('Adjust Window')}</li>
<div #adjust-window .separator style="display:none"/>
<li #original type="view-style"><span>{svg_checkmark}</span>Original</li>
<li #shrink type="view-style"><span>{svg_checkmark}</span>Shrink</li>
<li #stretch type="view-style"><span>{svg_checkmark}</span>Stretch</li>
<li #original type="view-style"><span>{svg_checkmark}</span>{translate('Original')}</li>
<li #shrink type="view-style"><span>{svg_checkmark}</span>{translate('Shrink')}</li>
<li #stretch type="view-style"><span>{svg_checkmark}</span>{translate('Stretch')}</li>
<div .separator />
<li #best type="image-quality"><span>{svg_checkmark}</span>Good image quality</li>
<li #balanced type="image-quality"><span>{svg_checkmark}</span>Balanced</li>
<li #low type="image-quality"><span>{svg_checkmark}</span>Optimize reaction time</li>
<li #custom type="image-quality"><span>{svg_checkmark}</span>Custom</li>
<li #best type="image-quality"><span>{svg_checkmark}</span>{translate('Good image quality')}</li>
<li #balanced type="image-quality"><span>{svg_checkmark}</span>{translate('Balanced')}</li>
<li #low type="image-quality"><span>{svg_checkmark}</span>{translate('Optimize reaction time')}</li>
<li #custom type="image-quality"><span>{svg_checkmark}</span>{translate('Custom')}</li>
<div .separator />
<li #show-remote-cursor .toggle-option><span>{svg_checkmark}</span>Show remote cursor</li>
{audio_enabled ? <li #disable-audio .toggle-option><span>{svg_checkmark}</span>Mute</li> : ""}
{keyboard_enabled && clipboard_enabled ? <li #disable-clipboard .toggle-option><span>{svg_checkmark}</span>Disable clipboard</li> : ""}
{keyboard_enabled ? <li #lock-after-session-end .toggle-option><span>{svg_checkmark}</span>Lock after session end</li> : ""}
{false && pi.platform == "Windows" ? <li #privacy-mode .toggle-option><span>{svg_checkmark}</span>Privacy mode</li> : ""}
<li #show-remote-cursor .toggle-option><span>{svg_checkmark}</span>{translate('Show remote cursor')}</li>
{audio_enabled ? <li #disable-audio .toggle-option><span>{svg_checkmark}</span>{translate('Mute')}</li> : ""}
{keyboard_enabled && clipboard_enabled ? <li #disable-clipboard .toggle-option><span>{svg_checkmark}</span>{translate('Disable clipboard')}</li> : ""}
{keyboard_enabled ? <li #lock-after-session-end .toggle-option><span>{svg_checkmark}</span>{translate('Lock after session end')}</li> : ""}
{false && pi.platform == "Windows" ? <li #privacy-mode .toggle-option><span>{svg_checkmark}</span>{translate('Privacy mode')}</li> : ""}
</menu>
</popup>;
}
@ -134,23 +138,20 @@ class Header: Reactor.Component {
function renderActionPop() {
return <popup>
<menu.context #action-options>
<li #transfer-file>Transfer File</li>
<li #tunnel>TCP Tunneling</li>
<li #transfer-file>{translate('Transfer File')}</li>
<li #tunnel>{translate('TCP Tunneling')}</li>
<div .separator />
{keyboard_enabled && (pi.platform == "Linux" || pi.sas_enabled) ? <li #ctrl-alt-del>Insert Ctrl + Alt + Del</li> : ""}
<li #ctrl-space>Insert Ctrl + Space</li>
<li #alt-tab>Insert Alt + Tab</li>
{false && <li #super-x>Insert Win/Super + ...</li>}
{keyboard_enabled && (pi.platform == "Linux" || pi.sas_enabled) ? <li #ctrl-alt-del>{translate('Insert')} Ctrl + Alt + Del</li> : ""}
<div .separator />
{keyboard_enabled ? <li #lock-screen>Insert Lock</li> : ""}
{keyboard_enabled ? <li #lock-screen>{translate('Insert Lock')}</li> : ""}
{false && pi.platform == "Windows" ? <li #block-input>Block user input </li> : ""}
{handler.support_refresh() ? <li #refresh>Refresh</li> : ""}
{handler.support_refresh() ? <li #refresh>{translate('Refresh')}</li> : ""}
</menu>
</popup>;
}
function renderGlobalScreens() {
if (pi.displays.length < 2) return "";
if (pi.displays.length < 3) return "";
var x0 = 9999999;
var y0 = 9999999;
var x = -9999999;
@ -232,18 +233,6 @@ class Header: Reactor.Component {
event click $(#ctrl-alt-del) {
handler.ctrl_alt_del();
}
event click $(#alt-tab) {
handler.alt_tab();
}
event click $(#ctrl-space) {
handler.ctrl_space();
}
event click $(#super-x) {
handler.super_x();
}
event click $(#lock-screen) {
handler.lock_screen();
@ -289,8 +278,8 @@ function handle_custom_image_quality() {
var bitrate0 = tmp[0] || 50;
var quantizer0 = tmp.length > 1 ? tmp[1] : 100;
handler.msgbox("custom", "Custom Image Quality", "<div .form> \
<div><input type=\"hslider\" style=\"width: 66%\" name=\"bitrate\" max=\"100\" min=\"10\" value=\"" + bitrate0 + "\"/ buddy=\"bitrate-buddy\"><b #bitrate-buddy>x</b>% bitrate</div> \
<div><input type=\"hslider\" style=\"width: 66%\" name=\"quantizer\" max=\"100\" min=\"0\" value=\"" + quantizer0 + "\"/ buddy=\"quantizer-buddy\"><b #quantizer-buddy>x</b>% quantizer</div> \
<div><input type=\"hslider\" style=\"width: 50%\" name=\"bitrate\" max=\"100\" min=\"10\" value=\"" + bitrate0 + "\"/ buddy=\"bitrate-buddy\"><b #bitrate-buddy>x</b>% bitrate</div> \
<div><input type=\"hslider\" style=\"width: 50%\" name=\"quantizer\" max=\"100\" min=\"0\" value=\"" + quantizer0 + "\"/ buddy=\"quantizer-buddy\"><b #quantizer-buddy>x</b>% quantizer</div> \
</div>", function(res=null) {
if (!res) return;
if (!res.bitrate) return;

View File

@ -18,29 +18,35 @@ var svg_menu = <svg #menu viewBox="0 0 512 512">
<circle cx="256" cy="64" r="64"/>
</svg>;
var my_id = "";
function get_id() {
my_id = handler.get_id();
return my_id;
}
class ConnectStatus: Reactor.Component {
function render() {
return
<div .connect-status>
<span class={"connect-status-icon connect-status" + (service_stopped ? 0 : connect_status)} />
{this.getConnectStatusStr()}
{service_stopped ? <span class="link">Start Service</span> : ""}
{service_stopped ? <span .link #start-service>{translate('Start Service')}</span> : ""}
</div>;
}
function getConnectStatusStr() {
if (service_stopped) {
return "Service is not running";
return translate("Service is not running");
} else if (connect_status == -1) {
return "Not ready. Please check your connection";
return translate('not_ready_status');
} else if (connect_status == 0) {
return "Connecting to the RustDesk network...";
return translate('connecting_status');
}
return "Ready";
return translate("Ready");
}
event click $(.connect-status .link) () {
handler.set_option("stop-service", "");
event click $(#start-service) () {
handler.set_option("stop-service", "");
}
}
@ -50,7 +56,7 @@ class RecentSessions: Reactor.Component {
if (sessions.length == 0) return <span />;
sessions = sessions.map(this.getSession);
return <div style="width: *">
<div .recent-sessions-title>RECENT SESSIONS</div>
<div .recent-sessions-title>{translate("Recent Sessions")}</div>
<div .recent-sessions-content key={sessions.length}>
{sessions}
</div>
@ -126,7 +132,7 @@ function createNewConnect(id, type) {
id = id.replace(/\s/g, "");
app.remote_id.value = formatId(id);
if (!id) return;
if (id == handler.get_id()) {
if (id == my_id) {
handler.msgbox("custom-error", "Error", "You cannot connect to your own computer");
return;
}
@ -149,10 +155,10 @@ class AudioInputs: Reactor.Component {
inputs = ["Mute"].concat(inputs);
var me = this;
self.timer(1ms, function() { me.toggleMenuState() });
return <li>Audio Input
return <li>{translate('Audio Input')}
<menu #audio-input key={inputs.length}>
{inputs.map(function(name) {
return <li id={name}><span>{svg_checkmark}</span>{name}</li>;
return <li id={name}><span>{svg_checkmark}</span>{translate(name)}</li>;
})}
</menu>
</li>;
@ -190,7 +196,6 @@ class MyIdMenu: Reactor.Component {
}
function render() {
var me = this;
return <div #myid>
{this.renderPop()}
ID{svg_menu}
@ -200,19 +205,18 @@ class MyIdMenu: Reactor.Component {
function renderPop() {
return <popup>
<menu.context #config-options>
<li #enable-keyboard><span>{svg_checkmark}</span>Enable Keyboard/Mouse</li>
<li #enable-clipboard><span>{svg_checkmark}</span>Enable Clipboard</li>
<li #enable-file-transfer><span>{svg_checkmark}</span>Enable File Transfer</li>
<li #enable-tunnel><span>{svg_checkmark}</span>Enable TCP Tunneling</li>
<li #enable-keyboard><span>{svg_checkmark}</span>{translate('Enable Keyboard/Mouse')}</li>
<li #enable-clipboard><span>{svg_checkmark}</span>{translate('Enable Clipboard')}</li>
<li #enable-file-transfer><span>{svg_checkmark}</span>{translate('Enable File Transfer')}</li>
<li #enable-tunnel><span>{svg_checkmark}</span>{translate('Enable TCP Tunneling')}</li>
<AudioInputs />
<div .separator />
<li #whitelist title="Only whitelisted IP can access me">IP Whitelisting</li>
<li #custom-server>ID/Relay Server</li>
<li #whitelist title={translate('whitelist_tip')}>{translate('IP Whitelisting')}</li>
<li #custom-server>{translate('ID/Relay Server')}</li>
<div .separator />
<li #stop-service class={service_stopped ? "line-through" : "selected"}><span>{svg_checkmark}</span>Enable Service</li>
<li #stop-service class={service_stopped ? "line-through" : "selected"}><span>{svg_checkmark}</span>{translate("Enable Service")}</li>
<div .separator />
<li #forum>Forum</li>
<li #about>About {handler.get_app_name()}</li>
<li #about>{translate('About')} {" "} {handler.get_app_name()}</li>
</menu>
</popup>;
}
@ -240,8 +244,9 @@ class MyIdMenu: Reactor.Component {
}
if (me.id == "whitelist") {
var old_value = handler.get_option("whitelist").split(",").join("\n");
handler.msgbox("custom-whitelist", "IP Whitelisting", "<div .form> \
<textarea spellcheck=\"false\" name=\"text\" novalue=\"0.0.0.0\" style=\"overflow: scroll-indicator; height: 160px; font-size: 1.2em; padding: 0.5em;\">" + old_value + "</textarea>\
handler.msgbox("custom-whitelist", translate("IP Whitelisting"), "<div .form> \
<div>" + translate("whitelist_sep") + "</div> \
<textarea spellcheck=\"false\" name=\"text\" novalue=\"0.0.0.0\" style=\"overflow: scroll-indicator; width:*; height: 160px; font-size: 1.2em; padding: 0.5em;\">" + old_value + "</textarea>\
</div> \
", function(res=null) {
if (!res) return;
@ -250,7 +255,7 @@ class MyIdMenu: Reactor.Component {
var values = value.split(/[\s,;\n]+/g);
for (var ip in values) {
if (!ip.match(/^\d+\.\d+\.\d+\.\d+$/)) {
return "Invalid ip: " + ip;
return translate("Invalid IP") + ": " + ip;
}
}
value = values.join("\n");
@ -260,12 +265,12 @@ class MyIdMenu: Reactor.Component {
handler.set_option("whitelist", value.replace("\n", ","));
}, 300);
} else if (me.id == "custom-server") {
var configOptions = handler.get_options();
var configOptions = handler.get_options();
var old_relay = configOptions["relay-server"] || "";
var old_id = configOptions["custom-rendezvous-server"] || "";
handler.msgbox("custom-server", "ID/Relay Server", "<div .form> \
<div><span style='width: 100px; display:inline-block'>ID Server: </span><input style='width: 250px' name='id' value='" + old_id + "' /></div> \
<div><span style='width: 100px; display:inline-block'>Relay Server: </span><input style='width: 250px' name='relay' value='" + old_relay + "' /></div> \
<div><span style='width: 100px; display:inline-block'>" + translate("ID Server") + ": </span><input .outline-focus style='width: 250px' name='id' value='" + old_id + "' /></div> \
<div><span style='width: 100px; display:inline-block'>" + translate("Relay Server") + ": </span><input style='width: 250px' name='relay' value='" + old_relay + "' /></div> \
</div> \
", function(res=null) {
if (!res) return;
@ -274,18 +279,16 @@ class MyIdMenu: Reactor.Component {
if (id == old_id && relay == old_relay) return;
if (id) {
var err = handler.test_if_valid_server(id);
if (err) return "ID Server: " + err;
if (err) return translate("ID Server") + ": " + err;
}
if (relay) {
var err = handler.test_if_valid_server(relay);
if (err) return "Relay Server: " + err;
if (err) return translate("Relay Server") + ": " + err;
}
configOptions["custom-rendezvous-server"] = id;
configOptions["relay-server"] = relay;
handler.set_options(configOptions);
});
} else if (me.id == "forum") {
handler.open_url("https:://forum.rustdesk.com");
}, 240);
} else if (me.id == "stop-service") {
handler.set_option("stop-service", service_stopped ? "" : "Y");
} else if (me.id == "about") {
@ -293,7 +296,7 @@ class MyIdMenu: Reactor.Component {
handler.msgbox("custom-nocancel-nook-hasclose", "About " + name, "<div style='line-height: 2em'> \
<div>Version: " + handler.get_version() + " \
<div .link .custom-event url='http://rustdesk.com/privacy'>Privacy Statement</div> \
<div .link .custom-event url='http://forum.rustdesk.com'>Forum</div> \
<div .link .custom-event url='http://rustdesk.com'>Website</div> \
<div style='background: #2c8cff; color: white; padding: 1em; margin-top: 1em;'>Copyright &copy; 2020 CarrieZ Studio \
<br /> Author: Carrie \
<p style='font-weight: bold'>Made with heart in this chaotic world!</p>\
@ -319,13 +322,13 @@ class App: Reactor.Component
<div .app>
<popup>
<menu.context #remote-context>
<li #connect>Connect</li>
<li #transfer>Transfer File</li>
<li #tunnel>TCP Tunneling</li>
<li #rdp>RDP</li>
<li #rename>Rename</li>
<li #remove>Remove</li>
{is_win && <li #shortcut>Create Desktop Shortcut</li>}
<li #connect>{translate('Connect')}</li>
<li #transfer>{translate('Transfer File')}</li>
<li #tunnel>{translate('TCP Tunneling')}</li>
<li #rdp>RDP</li>
<li #rename>{translate('Rename')}</li>
<li #remove>{translate('Remove')}</li>
{is_win && <li #shortcut>{translate('Create Desktop Shortcut')}</li>}
</menu>
</popup>
<popup>
@ -336,18 +339,18 @@ class App: Reactor.Component
</popup>
<div .left-pane>
<div>
<div .title>Your Desktop</div>
<div .lighter-text>Your desktop can be accessed with this ID and password.</div>
<div .title>{translate('Your Desktop')}</div>
<div .lighter-text>{translate('desk_tip')}</div>
<div .your-desktop>
<MyIdMenu />
{key_confirmed ? <input type="text" readonly value={formatId(handler.get_id())}/> : "Generating ..."}
<MyIdMenu />
{key_confirmed ? <input type="text" readonly value={formatId(get_id())}/> : translate("Generating ...")}
</div>
<div .your-desktop>
<div>Password</div>
<div>{translate('Password')}</div>
<Password />
</div>
</div>
{handler.is_installed() ? "": <InstalllMe />}
{handler.is_installed() ? "": <InstallMe />}
{handler.is_installed() && software_update_url ? <UpdateMe /> : ""}
{handler.is_installed() && !software_update_url && handler.is_installed_lower_version() ? <UpgradeMe /> : ""}
{is_can_screen_recording ? "": <CanScreenRecording />}
@ -359,11 +362,11 @@ class App: Reactor.Component
<div .right-pane>
<div .right-content>
<div .card-connect>
<div .title>Control Remote Desktop</div>
<div .title>{translate('Control Remote Desktop')}</div>
<ID @{this.remote_id} />
<div .right-buttons>
<button .button .outline #file-transfer>Transfer File</button>
<button .button #connect>Connect</button>
<button .button .outline #file-transfer>{translate('Transfer File')}</button>
<button .button #connect>{translate('Connect')}</button>
</div>
</div>
<RecentSessions @{this.recent_sessions} />
@ -386,11 +389,12 @@ class App: Reactor.Component
}
}
class InstalllMe: Reactor.Component {
class InstallMe: Reactor.Component {
function render() {
return <div .install-me>
<div>Install RustDesk</div>
<div #install-me .link>Install RustDesk on this computer ...</div>
<span />
<div>{translate('install_tip')}</div>
<button #install-me .button>{translate('Install')}</button>
</div>;
}
@ -445,9 +449,9 @@ class UpgradeMe: Reactor.Component {
function render() {
var update_or_download = is_osx ? "download" : "update";
return <div .install-me>
<div>{handler.get_app_name()} Status</div>
<div>An update is available for RustDesk.</div>
<div #install-me .link style="padding-top: 1em">Click to upgrade</div>
<div>{translate('Status')}</div>
<div>{translate('Your installation is lower version.')}</div>
<div #install-me .link style="padding-top: 1em">{translate('Click to upgrade')}</div>
</div>;
}
@ -458,9 +462,9 @@ class UpgradeMe: Reactor.Component {
class UpdateMe: Reactor.Component {
function render() {
var update_or_download = is_osx ? "download" : "update";
var update_or_download = "download"; // !is_win ? "download" : "update";
return <div .install-me>
<div>{handler.get_app_name()} Status</div>
<div>{translate('Status')}</div>
<div>There is a newer version of {handler.get_app_name()} ({handler.get_new_version()}) available.</div>
<div #install-me .link style="padding-top: 1em">Click to {update_or_download}</div>
<div #download-percent style="display:hidden; padding-top: 1em;" />
@ -468,14 +472,16 @@ class UpdateMe: Reactor.Component {
}
event click $(#install-me) {
if (is_osx) {
handler.open_url("https://rustdesk.com");
return;
if (!is_win) {
handler.open_url("https://rustdesk.com");
return;
}
var url = software_update_url + '.' + handler.get_software_ext();
var path = handler.get_software_store_path();
var onsuccess = function(md5) {
$(#download-percent).content("Installing ...");
$(#download-percent).content(translate("Installing ..."));
handler.update_me(path);
};
var onerror = function(err) {
@ -506,9 +512,9 @@ class SystemError: Reactor.Component {
class TrustMe: Reactor.Component {
function render() {
return <div .trust-me>
<div>Configuration Permissions</div>
<div>In order to control your Desktop remotely, you need to grant RustDesk "Accessibility" permissions</div>
<div #trust-me .link>Configure</div>
<div>{translate('Configuration Permissions')}</div>
<div>{translate('config_acc')}</div>
<div #trust-me .link>{translate('Configure')}</div>
</div>;
}
@ -521,9 +527,9 @@ class TrustMe: Reactor.Component {
class CanScreenRecording: Reactor.Component {
function render() {
return <div .trust-me>
<div>Configuration Permissions</div>
<div>In order to access your Desktop remotely, you need to grant RustDesk "Screen Recording" permissions</div>
<div #screen-recording .link>Configure</div>
<div>{translate('Configuration Permissions')}</div>
<div>{translate('config_screen')}</div>
<div #screen-recording .link>{translate('Configure')}</div>
</div>;
}
@ -536,9 +542,10 @@ class CanScreenRecording: Reactor.Component {
class FixWayland: Reactor.Component {
function render() {
return <div .trust-me>
<div>Warning</div>
<div>Login screen using Wayland is not supported</div>
<div #fix-wayland .link>Fix it</div>
<div>{translate('Warning')}</div>
<div>{translate('Login screen using Wayland is not supported')}</div>
<div #fix-wayland .link>{translate('Fix it')}</div>
<div style="text-align: center">({translate('Reboot required')})</div>
</div>;
}
@ -551,9 +558,10 @@ class FixWayland: Reactor.Component {
class ModifyDefaultLogin: Reactor.Component {
function render() {
return <div .trust-me>
<div>Warning</div>
<div>Current Wayland display server is not supported</div>
<div #modify-default-login .link>Fix it(re-login required)</div>
<div>{translate('Warning')}</div>
<div>{translate('Current Wayland display server is not supported')}</div>
<div #modify-default-login .link>{translate('Fix it')}</div>
<div style="text-align: center">({translate('Reboot required')})</div>
</div>;
}
@ -622,19 +630,19 @@ class Password: Reactor.Component {
event click $(li#set-password) {
var me = this;
handler.msgbox("custom-password", "Set Password", "<div .form .set-password> \
<div><span>Password:</span><input|password(password) /></div> \
<div><span>Confirmation:</span><input|password(confirmation) /></div> \
handler.msgbox("custom-password", translate("Set Password"), "<div .form .set-password> \
<div><span>" + translate('Password') + ":</span><input|password(password) .outline-focus /></div> \
<div><span>" + translate('Confirmation') + ":</span><input|password(confirmation) /></div> \
</div> \
", function(res=null) {
if (!res) return;
var p0 = (res.password || "").trim();
var p1 = (res.confirmation || "").trim();
if (p0.length < 6) {
return "Too short, at least 6 characters.";
return translate("Too short, at least 6 characters.");
}
if (p0 != p1) {
return "The confirmation is not identical.";
return translate("The confirmation is not identical.");
}
handler.update_password(p0);
me.update();
@ -644,7 +652,7 @@ class Password: Reactor.Component {
class ID: Reactor.Component {
function render() {
return <input type="text" #remote_id .outline-focus novalue="Enter Remote ID" maxlength="13"
return <input type="text" #remote_id .outline-focus novalue={translate("Enter Remote ID")} maxlength="15"
value={formatId(handler.get_remote_id())} />;
}
@ -735,6 +743,10 @@ function checkConnectStatus() {
key_confirmed = tmp[1];
app.update();
}
if (tmp[2] && tmp[2] != my_id) {
stdout.println("id updated");
app.update();
}
tmp = handler.get_error();
if (system_error != tmp) {
system_error = tmp;

View File

@ -5,17 +5,17 @@ function self.ready() {
class Install: Reactor.Component {
function render() {
return <div .content>
<div style="font-size: 2em;">Installation</div>
<div style="margin: 2em 0;">Installation Path: <input|text disabled value={view.install_path()} /></div>
<div><button|checkbox #startmenu checked>Create start menu shortcuts</button></div>
<div><button|checkbox #desktopicon checked>Create desktop icon</button></div>
<div #aggrement .link style="margin-top: 2em;">End-user license agreement</div>
<div>By starting the installation, you accept the license agreement.</div>
<div style="font-size: 2em;">{translate('Installation')}</div>
<div style="margin: 2em 0;">{translate('Installation Path')} {": "}<input|text disabled value={view.install_path()} /></div>
<div><button|checkbox #startmenu checked>{translate('Create start menu shortcuts')}</button></div>
<div><button|checkbox #desktopicon checked>{translate('Create desktop icon')}</button></div>
<div #aggrement .link style="margin-top: 2em;">{translate('End-user license agreement')}</div>
<div>{translate('agreement_tip')}</div>
<div style="height: 1px; background: gray; margin-top: 1em" />
<div style="text-align: right;">
<progress style={"color:" + color} style="display: none" />
<button .button id="cancel" .outline style="margin-right: 2em;">Cancel</button>
<button .button id="submit">Accept and Install</button>
<button .button id="cancel" .outline style="margin-right: 2em;">{translate('Cancel')}</button>
<button .button id="submit">{translate('Accept and Install')}</button>
</div>
</div>;
}
@ -42,4 +42,4 @@ class Install: Reactor.Component {
}
}
$(body).content(<Install />);
$(body).content(<Install />);

View File

@ -1,4 +1,5 @@
var type, title, text, getParams, remember, retry, callback;
var type, title, text, getParams, remember, retry, callback, contentStyle;
var my_translate;
function updateParams(params) {
type = params.type;
@ -7,7 +8,10 @@ function updateParams(params) {
getParams = params.getParams;
remember = params.remember;
callback = params.callback;
my_translate = params.translate;
retry = params.retry;
contentStyle = params.contentStyle;
try { text = translate_text(text); } catch (e) {}
if (retry > 0) {
self.timer(retry * 1000, function() {
view.close({ reconnect: true });
@ -15,39 +19,20 @@ function updateParams(params) {
}
}
function translate_text(text) {
if (text.indexOf('Failed') == 0 && text.indexOf(': ') > 0) {
var fds = text.split(': ');
for (var i = 0; i < fds.length; ++i) {
fds[i] = my_translate(fds[i]);
}
text = fds.join(': ');
}
return text;
}
var params = view.parameters;
updateParams(params);
var svg_eye_cross = <svg viewBox="0 -21 511.96 511">
<path d="m506.68 261.88c7.043-16.984 7.043-36.461 0-53.461-41.621-100.4-140.03-165.27-250.71-165.27-46.484 0-90.797 11.453-129.64 32.191l-68.605-68.609c-8.3438-8.3398-21.824-8.3398-30.168 0-8.3398 8.3398-8.3398 21.824 0 30.164l271.49 271.49 86.484 86.488 68.676 68.672c4.1797 4.1797 9.6406 6.2695 15.102 6.2695 5.4609 0 10.922-2.0898 15.082-6.25 8.3438-8.3398 8.3438-21.824 0-30.164l-62.145-62.145c36.633-27.883 66.094-65.109 84.438-109.38zm-293.91-100.1c12.648-7.5742 27.391-11.969 43.199-11.969 47.062 0 85.332 38.273 85.332 85.336 0 15.805-4.3945 30.547-11.969 43.199z"/>
<path d="m255.97 320.48c-47.062 0-85.336-38.273-85.336-85.332 0-3.0938 0.59766-6.0195 0.91797-9.0039l-106.15-106.16c-25.344 24.707-46.059 54.465-60.117 88.43-7.043 16.98-7.043 36.457 0 53.461 41.598 100.39 140.01 165.27 250.69 165.27 34.496 0 67.797-6.3164 98.559-18.027l-89.559-89.559c-2.9844 0.32031-5.9062 0.91797-9 0.91797z"/>
</svg>;
class Password: Reactor.Component {
this var visible = false;
function render() {
return <div .password>
<input name="password" type={this.visible ? "text" : "password"} .outline-focus />
{this.visible ? svg_eye_cross : svg_eye}
</div>;
}
event click $(svg) {
var el = this.$(input);
var value = el.value;
var start = el.xcall(#selectionStart) || 0;
var end = el.xcall(#selectionEnd);
this.update({ visible: !this.visible });
self.timer(30ms, function() {
var el = this.$(input);
view.focus = el;
el.value = value;
el.xcall(#setSelection, start, end);
});
}
}
var body;
class Body: Reactor.Component {
@ -74,9 +59,9 @@ class Body: Reactor.Component {
function getInputPasswordContent() {
var ts = remember ? { checked: true } : {};
return <div .form>
<div>Please enter your password</div>
<Password />
<div><button|checkbox(remember) {ts}>Remember password</button></div>
<div>{my_translate('Please enter your password')}</div>
<PasswordComponent />
<div><button|checkbox(remember) {ts}>{my_translate('Remember password')}</button></div>
</div>;
}
@ -116,27 +101,27 @@ class Body: Reactor.Component {
var me = this;
self.timer(1ms, function() {
if (typeof content == "string")
me.$(#content).html = content;
me.$(#content).html = my_translate(content);
else
me.$(#content).content(content);
});
return (
<div style="size: *">
<header style={"height: 2em; background: " + color}>
<caption role="window-caption">{title}</caption>
<caption role="window-caption">{my_translate(title)}</caption>
</header>
<div style="padding: 1em 2em; size: *;">
<div style="height: *; flow: horizontal">
{icon && <div style="height: *; margin: * 0; padding-right: 2em;">{icon}</div>}
<div style="size: *; margin: * 0;" #content />
<div style={contentStyle || "size: *; margin: * 0;"} #content />
</div>
<div style="text-align: right;">
<span #error />
{show_progress ? <progress style={"color:" + color} /> : ""}
{hasCancel || hasRetry ? <button .button #cancel .outline>{hasRetry ? "OK" : "Cancel"}</button> : ""}
{this.hasSkip() ? <button .button #skip .outline>Skip</button> : ""}
{hasOk || hasRetry ? <button .button #submit>{hasRetry ? "Retry" : "OK"}</button> : ""}
{hasClose ? <button .button #cancel .outline>Close</button> : ""}
<span style="display:inline-block; max-width: 260px; font-size:12px;" #error />
<progress #progress style={"color:" + color + "; display: " + (show_progress ? "inline-block" : "none")} />
{hasCancel || hasRetry ? <button .button #cancel .outline>{my_translate(hasRetry ? "OK" : "Cancel")}</button> : ""}
{this.hasSkip() ? <button .button #skip .outline>{my_translate('Skip')}</button> : ""}
{hasOk || hasRetry ? <button .button #submit>{my_translate(hasRetry ? "Retry" : "OK")}</button> : ""}
{hasClose ? <button .button #cancel .outline>{my_translate('Close')}</button> : ""}
</div>
</div>
</div>);
@ -149,6 +134,17 @@ class Body: Reactor.Component {
$(body).content(<Body />);
function show_progress(show=1, err="") {
if (show == -1) {
view.close()
return;
}
$(#progress).style.set {
display: show ? "inline-block" : "none"
};
$(#error).text = err;
}
function submit() {
if ($(button#submit)) {
$(button#submit).sendEvent("click");
@ -211,9 +207,12 @@ event click $(button#submit) {
}
var values = getValues();
if (callback) {
var err = callback(values);
var err = callback(values, show_progress);
if (err && !err.trim()) {
return;
}
if (err) {
$(#error).text = err;
show_progress(false, err);
return;
}
}
@ -235,7 +234,7 @@ event keydown (evt) {
function set_outline_focus() {
self.timer(30ms, function() {
var el = $(input.outline-focus);
var el = $(.outline-focus);
if (el) view.focus = el;
else {
el = $(#submit);

View File

@ -21,17 +21,17 @@ class PortForward: Reactor.Component {
});
return <div #file-transfer><section>
{pfs.length ? <div style="background: green; color: white; text-align: center; padding: 0.5em;">
<span style="font-size: 1.2em">Listening ...</span><br/>
<span style="font-size: 0.8em; color: #ddd">Don't close this window while you are using the tunnel</span>
<span style="font-size: 1.2em">{translate('Listening ...')}</span><br/>
<span style="font-size: 0.8em; color: #ddd">{translate('not_close_tcp_tip')}</span>
</div> : ""}
<table #port-forward>
<thead>
<tr>
<th>Local Port</th>
<th>{translate('Local Port')}</th>
<th style="width: 1em" />
<th>Remote Host</th>
<th>Remote Port</th>
{args.length ? "" : <th style="width: 6em">Action</th>}
<th>{translate('Remote Host')}</th>
<th>{translate('Remote Port')}</th>
{args.length ? "" : <th style="width: 6em">{translate('Action')}</th>}
</tr>
</thead>
<tbody key={pfs.length}>
@ -41,7 +41,7 @@ class PortForward: Reactor.Component {
<td .right-arrow style="text-align: center">{svg_arrow}</td>
<td><input|text #remote-host novalue="localhost" /></td>
<td><input|number #remote-port /></td>
<td style="margin:0;"><button .button #add>Add</button></td>
<td style="margin:0;"><button .button #add>{translate('Add')}</button></td>
</tr>
}
{pfs}

View File

@ -54,7 +54,6 @@ pub struct HandlerInner {
sender: Option<mpsc::UnboundedSender<Data>>,
thread: Option<std::thread::JoinHandle<()>>,
close_state: HashMap<String, String>,
last_down_key: Option<(String, i32, bool)>,
}
#[derive(Clone, Default)]
@ -64,7 +63,6 @@ pub struct Handler {
id: String,
args: Vec<String>,
lc: Arc<RwLock<LoginConfigHandler>>,
super_on: bool,
}
impl Deref for Handler {
@ -146,6 +144,8 @@ impl sciter::EventHandler for Handler {
fn get_id();
fn get_default_pi();
fn get_option(String);
fn t(String);
fn set_option(String, String);
fn save_close_state(String, String);
fn is_file_transfer();
fn is_port_forward();
@ -155,9 +155,6 @@ impl sciter::EventHandler for Handler {
fn send_mouse(i32, i32, i32, bool, bool, bool, bool);
fn key_down_or_up(bool, String, i32, bool, bool, bool, bool, bool);
fn ctrl_alt_del();
fn ctrl_space();
fn alt_tab();
fn super_x();
fn transfer_file();
fn tunnel();
fn lock_screen();
@ -286,6 +283,10 @@ impl Handler {
self.lc.read().unwrap().remember
}
fn t(&self, name: String) -> String {
crate::client::translate(name)
}
fn is_xfce(&self) -> bool {
crate::platform::is_xfce()
}
@ -408,6 +409,10 @@ impl Handler {
self.lc.read().unwrap().get_option(&k)
}
fn set_option(&self, k: String, v: String) {
self.lc.write().unwrap().set_option(k, v);
}
fn save_close_state(&self, k: String, v: String) {
self.write().unwrap().close_state.insert(k, v);
}
@ -534,7 +539,6 @@ impl Handler {
fn reconnect(&mut self) {
let cloned = self.clone();
let mut lock = self.write().unwrap();
lock.last_down_key.take();
lock.thread.take().map(|t| t.join());
lock.thread = Some(std::thread::spawn(move || {
io_loop(cloned);
@ -667,7 +671,6 @@ impl Handler {
let evt_type = mask & 0x7;
if buttons == 1 && evt_type == 1 && ctrl && self.peer_platform() != "Mac OS" {
self.send_mouse((1 << 3 | 2) as _, x, y, alt, ctrl, shift, command);
return;
}
}
}
@ -820,20 +823,6 @@ impl Handler {
}
}
fn super_x(&mut self) {
self.super_on = true;
}
fn ctrl_space(&mut self) {
let key = "VK_SPACE".to_owned();
self.key_down_or_up(3, key, 0, false, true, false, false, false);
}
fn alt_tab(&mut self) {
let key = "VK_TAB".to_owned();
self.key_down_or_up(3, key, 0, true, false, false, false, false);
}
fn lock_screen(&mut self) {
let lock = "LOCK_SCREEN".to_owned();
self.key_down_or_up(1, lock, 0, false, false, false, false, false);
@ -866,6 +855,18 @@ impl Handler {
command: bool,
extended: bool,
) {
if self.peer_platform() == "Windows" {
if ctrl && alt && name == "VK_DELETE" {
self.ctrl_alt_del();
return;
}
if command && name == "VK_L" {
self.lock_screen();
return;
}
}
// extended: e.g. ctrl key on right side, https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-keybd_event
// not found api of osx and xdo
log::debug!(
@ -881,15 +882,6 @@ impl Handler {
extended,
);
let mut command = command;
if self.super_on {
command = true;
}
if down_or_up == 0 {
self.super_on = false;
}
let mut name = name;
#[cfg(target_os = "linux")]
if code == 65383 {