100% open source

This commit is contained in:
rustdesk
2022-05-12 17:35:25 +08:00
parent 9098619162
commit c1bad84a86
58 changed files with 8397 additions and 3292 deletions

View File

@@ -1,3 +1,210 @@
var selectTags = [];
var ab = { tags: [], peers: [] };
var abLoading;
var abError;
var current_menu_peer_id = '';
var current_menu_tag = '';
class AddressBook: Reactor.Component
{
this var style;
this var selectedTags = function() {
var tags = handler.get_local_option("selected-tags");
if (tags) return tags.split(",");
return [];
}();
function render() {
if (!handler.get_local_option("access_token")) {
return <div style="margin: *"><div #login-link .link style="margin: *; width: 100px; text-align: center; font-size: 1.2em;">{translate("Login")}</div></div>;
}
if (abLoading) {
return <div style="margin: *"><progress style="color: #0071ff" /></div>;
} else if (abError) {
return <div style="margin: *; text-align: center;">{abError}
<div #retry .link style="margin-left: 1em;">{translate("Retry")}</div>
</div>;
}
var peers = this.getPeers();
var me = this;
return <div .app #ab style="size:*">
<popup>
<menu.context #ab-context>
<li #add-id>{translate('Add ID')}</li>
<li #add-tag>{translate('Add Tag')}</li>
<li #unselect-tags>{translate('Unselect all tags')}</li>
</menu>
<menu.context #tag-context>
<li #remove-tag>{translate('Remove')}</li>
</menu>
</popup>
<div .left-pane>
<div style="padding: 0; padding-bottom: 1em" #tags-label>{translate('Tags')}{svg_menu}</div>
<div #tags>
{ab.tags.map(function(t) {
return <span class={me.selectedTags.indexOf(t) >= 0 ? "active" : "inactive"}>{t}</span>;
})}
</div>
</div>
<div .right-pane>
<div .right-content style="padding-top:0; padding-right: 0;">
<SessionList sessions={peers} type="ab" />
</div>
</div>
</div>;
}
event mouseup $(#tags span) (evt, me) {
if(evt.propButton) {
current_menu_tag = me.text;
me.popup($(#tag-context));
return true;
}
}
event click $(#retry) (_, __) {
refreshCurrentUser();
}
event click $(#login-link) (_, __) {
login();
}
event click $(#tags-label svg#menu) (_, me) {
me.popup($(#ab-context));
}
event click $(#add-id) (_, __) {
var me = this;
msgbox("custom-add-id", translate("Add ID"), <div .form>
<div>{translate("whitelist_sep")}</div>
<textarea .outline-focus spellcheck="false" name="text" style="overflow: scroll-indicator; width:*; height: 160px; font-size: 1.2em; padding: 0.5em;"></textarea>
</div>, function(res=null) {
if (!res) return;
var value = (res.text || "").trim();
var values = value.split(/[\s,;\n]+/g);
if (values.length == 0) return;
for (var v in values) {
var found;
for (var i = 0; i < ab.peers.length; ++i) {
if (ab.peers[i].id == v) {
found = true;
break;
}
}
if (found) continue;
ab.peers.push({ id: v });
}
updateAb();
me.update();
}, 300);
}
event click $(#add-tag) (_, __) {
var me = this;
msgbox("custom-add-tag", translate("Add Tag"), <div .form>
<div>{translate("whitelist_sep")}</div>
<textarea .outline-focus spellcheck="false" name="text" style="overflow: scroll-indicator; width:*; height: 160px; font-size: 1.2em; padding: 0.5em;"></textarea>
</div>, function(res=null) {
if (!res) return;
var value = (res.text || "").trim();
var values = value.split(/[\s,;\n]+/g);
if (values.length == 0) return;
for (var v in values) {
if (ab.tags.indexOf(v) < 0) {
ab.tags.push(v);
}
}
updateAb();
me.update();
}, 300);
}
event click $(#remove-tag) (_, me) {
var tag = current_menu_tag;
var i = ab.tags.indexOf(tag);
ab.tags.splice(i, 1);
for (var p in ab.peers) {
if (p.tags) {
i = p.tags.indexOf(tag);
if (i >= 0) p.tags.splice(i, 1);
}
}
updateAb();
this.update();
}
event click $(#unselect-tags) (_, me) {
this.selectedTags = [];
handler.set_local_option("selected-tags", "");
this.update();
}
event click $(#tags span) (_, me) {
me.attributes.toggleClass('active');
if (me.attributes.hasClass('active')) {
this.selectedTags.push(me.text);
} else {
this.selectedTags.splice(this.selectedTags.indexOf(me.text), 1);
}
handler.set_local_option("selected-tags", this.selectedTags.join(','));
this.update();
}
function getPeers() {
var tags = [];
for (var t in this.selectedTags) {
if (ab.tags.indexOf(t) >= 0) tags.push(t);
}
if (tags.length != this.selectedTags.length) {
this.selectedTags = tags;
handler.set_local_option("selected-tags", tags.join(","));
stdout.println("updated selected tags");
}
if (tags.length == 0) return ab.peers;
var peers = [];
if (tags.length > 0) {
for (var p in ab.peers) {
for (var t in (p.tags || [])) {
if (tags.indexOf(t) >= 0) {
peers.push(p);
break;
}
}
}
} else {
peers = ab.peers;
}
return peers;
}
}
class SelectTags: Reactor.Component {
function this(params) {
selectTags = this;
this.tags = params.tags;
}
function render() {
var me = this;
return <div #tags>
{ab.tags.map(function(t) {
return <span class={me.tags.indexOf(t) >= 0 ? "active" : "inactive"}>{t}</span>;
})}
</div>;
}
event click $(#tags span) (_, me) {
me.attributes.toggleClass('active');
var i = this.tags.indexOf(me.text);
if (i < 0) {
this.tags.push(me.text);
} else {
this.tags.splice(i, 1);
}
}
}
var svg_tile = <svg #session-tile viewBox="0 0 158.6 158.6"><path style="stroke-width:.309756" d="M5.4 157.7c-1-.3-2-1-3.2-2.1-2.8-2.8-2.6-1-2.5-32 0-26.7 0-27 .7-28.3a9.3 9.3 0 0 1 4-4.2c1.2-.6 2.3-.6 29-.7 27.5 0 27.6 0 29.1.6.8.4 2 1.2 2.7 2 2.4 2.5 2.3.7 2.2 31.6-.1 26.5-.1 27.6-.7 28.8a9.3 9.3 0 0 1-4.2 4c-1.4.6-1.6.6-28.5.7a235 235 0 0 1-28.6-.4zm91 0a8.5 8.5 0 0 1-5.7-5.4c-.2-.7-.3-8.3-.3-28.3V96.7l.7-1.6a8.9 8.9 0 0 1 4.6-4.3c1.2-.4 3.8-.5 28.9-.4 26.6.1 27.6.1 28.8.7 1.6.8 3.2 2.5 4 4.2.7 1.4.7 1.6.7 28.3.1 31 .3 29.2-2.5 32-2.8 2.7-1 2.6-31.4 2.6-21.4 0-26.8-.1-27.9-.5zM5.3 67a8.7 8.7 0 0 1-4-3C-.5 61.6-.5 62.3-.5 33.6-.4 3.2-.5 5 2.2 2.2 5-.6 3.2-.4 34.2-.3c26.7 0 27 0 28.3.7 1.7.8 3.4 2.4 4.2 4 .6 1.2.6 2.2.7 28.8 0 25.1 0 27.7-.4 29a9 9 0 0 1-4.3 4.5l-1.6.7H33.7c-20.2 0-27.7-.1-28.4-.4Zm89.8-.3a9 9 0 0 1-4.3-4.6c-.5-1.2-.5-3.8-.5-28.9.1-26.6.2-27.6.7-28.8a9.3 9.3 0 0 1 4.2-4c1.4-.7 1.6-.7 28.3-.7 31-.1 29.2-.3 32 2.5 2.8 2.8 2.6 1 2.5 32 0 26.7 0 26.9-.7 28.3a9.3 9.3 0 0 1-4 4.2c-1.2.5-2.2.6-29 .6l-27.7.1z" transform="translate(.4 .4)"/></svg>;
var svg_list = <svg #session-list viewBox="0 0 246.8 185.8"><path style="stroke-width:.482473" d="M-69.2 102.7A16.5 16.5 0 0 1-67 70.4c7.3-1 15 4 17.3 11 1 3 1 8 0 10.8a16.7 16.7 0 0 1-19.5 10.5zm53-3.4a12.3 12.3 0 0 1-7-16.8c1.3-3 3.1-4.7 6-6 2.2-1 2.8-1 87.2-1 92.4 0 87-.2 90.6 2.6.9.7 2.2 2.4 3 3.7 1.2 2.2 1.4 3.1 1.4 6 0 4.8-2.3 8.6-6.8 11l-1.9 1-85.2.1c-71.9 0-85.5 0-87.3-.6zm-53.5-73c-4.7-1.5-8.6-5-10.6-9.1-1.8-4-1.8-9.8 0-13.7 1.6-3.3 4.4-6.2 7.8-8 2.2-1.2 3-1.3 7.1-1.3 4 0 5 .1 7.3 1.3a16.6 16.6 0 0 1 0 29.6c-2 1-3.4 1.4-6.5 1.5-2.2 0-4.5 0-5.1-.3zm52.3-4.8c-2.4-1.1-5.3-4-6.2-6.5-1-2.4-1-7.3.1-9.7.5-1.1 1.8-2.8 2.8-3.8 3.7-3.5-4-3.2 91-3.2h85.5l2.5 1.1a12 12 0 0 1 0 21.8l-2.5 1.2H70.2c-82.5 0-85.7 0-87.6-1zm-52.1-71.6a18 18 0 0 1-10-7.7 17 17 0 0 1-.7-15c2.3-5 5.8-7.9 11.4-9.3 9-2.3 18.3 4 19.8 13.4a16.4 16.4 0 0 1-15.2 19c-2.1.1-4.1 0-5.3-.4zm52.1-5.9c-1.3-.6-3-1.7-3.7-2.5-4.7-5-4.2-13.7 1-18 3.7-3.1-1.8-3 91.5-2.8l84.9.1 2 1a12 12 0 0 1 6.7 11c0 3-.2 3.9-1.4 6-.8 1.4-2.1 3-3 3.8-3.7 2.7 1.8 2.6-90.6 2.6h-85l-2.4-1.2z" transform="translate(81.7 82.6)"/></svg>;
var search_icon = <svg viewBox="0 0 655.278 655.024"><g transform="translate(-24.497 -195.01)"><path d="m649.96 847.92c-2.9592-1.3629-27.183-24.243-63.36-59.846-32.213-31.702-70.814-69.663-85.78-84.357l-27.21-26.717-4.7897 3.5287c-66.337 48.872-145.32 66.878-224.31 51.138-72.966-14.539-136.58-58.184-178.47-122.44-15.945-24.462-30.723-61.471-36.413-91.191-8.9404-46.696-6.2422-90.39 8.3388-135.04 13.39-41.003 34.756-75.42 66.479-107.09 74.506-74.377 183.71-99.89 284.22-66.397 62.352 20.777 117.67 65.579 150.79 122.12 38.716 66.101 46.59 147.55 21.43 221.66-9.9038 29.171-29.788 63.725-49.916 86.743l-7.0583 8.0717 3.0992 2.919c1.7046 1.6054 40.675 39.928 86.602 85.161 89.007 87.664 87.558 86.034 85.619 96.293-1.2888 6.8209-5.2313 12.041-11.321 14.989-6.7901 3.287-11.55 3.4093-17.952 0.46117zm-316.64-154.63c32.373-5.0481 61.075-15.115 86.553-30.358 47.942-28.683 83.505-72.09 100.89-123.14 35.043-102.91-6.4362-214.07-100.89-270.37-52.514-31.302-117.76-40.564-178.06-25.277-81.183 20.579-145.19 82.918-166.86 162.52-5.5757 20.478-7.445 35.423-7.445 59.52s1.8693 39.042 7.445 59.52c21.409 78.63 85.366 141.52 164.81 162.05 29.22 7.5511 66.493 9.756 93.564 5.5347z" stroke-width="1.28"/></g></svg>;
@@ -14,7 +221,6 @@ function getSessionsStyle(type) {
}
var searchPatterns = {};
var current_menu_peer_id = '';
class SearchBar: Reactor.Component {
this var type = "";
@@ -67,7 +273,11 @@ class SessionStyle: Reactor.Component {
var option = getSessionsStyleOption(this.type);
var sessionsStyle = getSessionsStyle(this.type);
handler.set_local_option(option, sessionsStyle == "tile" ? "list" : "tile");
app.multipleSessions.update();
if (is_linux) {
app.multipleSessions.stupidUpdate();
} else {
app.multipleSessions.update();
}
}
}
@@ -106,6 +316,7 @@ class SessionList: Reactor.Component {
<li #connect>{translate('Connect')}</li>
<li #transfer>{translate('Transfer File')}</li>
<li #tunnel>{translate('TCP Tunneling')}</li>
{false && !handler.using_public_server() && <li #force-always-relay><span>{svg_checkmark}</span>{translate('Always connect via relay')}</li>}
<li #rdp>RDP<EditRdpPort /></li>
<div .separator />
<li #rename>{translate('Rename')}</li>
@@ -114,6 +325,7 @@ class SessionList: Reactor.Component {
<li #forget-password>{translate('Unremember Password')}</li>
{(!this.type || this.type == "fav") && <li #add-fav>{translate('Add to Favorites')}</li>}
{(!this.type || this.type == "fav") && <li #remove-fav>{translate('Remove from Favorites')}</li>}
{this.type == "ab" && <li #edit-tag>{translate('Edit Tag')}</li>}
</menu>
</popup>
{sessions}
@@ -179,6 +391,12 @@ class SessionList: Reactor.Component {
// https://sciter.com/forums/topic/replacecustomize-context-menu/
var menu = this.$(menu#remote-context);
current_menu_peer_id = id;
var el = this.$(li#force-always-relay);
if (el) {
var force = handler.get_peer_option(id, "force-always-relay");
el.attributes.toggleClass("selected", force == "Y");
el.attributes.toggleClass("line-through", force != "Y");
}
var conn = this.$(menu #connect);
if (conn) {
var alias = me.parent.parent.$(#alias);
@@ -202,7 +420,16 @@ class SessionList: Reactor.Component {
} else if (action == "transfer") {
createNewConnect(id, "file-transfer");
} else if (action == "remove") {
if (!this.type) {
if (this.type == "ab") {
for (var i = 0; i < ab.peers.length; ++i) {
if (ab.peers[i].id == id) {
ab.peers.splice(i, 1);
app.update();
updateAb();
break;
}
}
} else {
handler.remove_peer(id);
app.update();
}
@@ -211,6 +438,10 @@ class SessionList: Reactor.Component {
} else if (action == "shortcut") {
handler.create_shortcut(id);
} else if (action == "rdp") {
if (is_edit_rdp_port) {
is_edit_rdp_port = false;
return;
}
createNewConnect(id, "rdp");
} else if (action == "add-fav") {
var favs = handler.get_fav();
@@ -230,6 +461,15 @@ class SessionList: Reactor.Component {
createNewConnect(id, "port-forward");
} else if (action == "rename") {
var old_name = handler.get_peer_option(id, "alias");
var abPeer;
if (this.type == "ab") {
for (var v in ab.peers) {
if (v.id == id) {
abPeer = v;
old_name = v.alias || "";
}
}
}
msgbox("custom-rename", "Rename", "<div .form> \
<div><input name='name' .outline-focus style='width: *; height: 23px', value='" + old_name + "' /></div> \
</div> \
@@ -237,10 +477,30 @@ class SessionList: Reactor.Component {
if (!res) return;
var name = (res.name || "").trim();
if (name != old_name) {
if (abPeer) {
abPeer.alias = name;
updateAb();
}
handler.set_peer_option(id, "alias", name);
}
app.update();
});
} else if (action == "force-always-relay") {
var force = handler.get_peer_option(id, "force-always-relay");
handler.set_peer_option(id, "force-always-relay", force == "Y" ? "" : "Y");
} else if (action == "edit-tag") {
var peer;
for (var v in ab.peers) {
if (v.id == id) {
peer = v;
}
}
if (!peer) return;
msgbox("custom-edit-tag", "Edit Tag", <SelectTags tags={peer.tags || []} />, function(res=null) {
if (!res) return;
peer.tags = selectTags.tags;
updateAb();
}, 260, 500, 0, "size: *; margin: 2em 0;");
}
}
}
@@ -267,6 +527,7 @@ class MultipleSessions: Reactor.Component {
<span class={!type ? 'active' : 'inactive'}>{translate('Recent Sessions')}</span>
<span #fav class={type == "fav" ? 'active' : 'inactive'}>{translate('Favorites')}</span>
{handler.is_installed() && <span #lan class={type == "lan" ? 'active' : 'inactive'}>{translate('Discovered')}</span>}
<span #ab class={type == "ab" ? 'active' : 'inactive'}>{translate('Address Book')}</span>
</div>
{!this.hidden && <SearchBar type={type} />}
{!this.hidden && <SessionStyle type={type} />}
@@ -274,6 +535,7 @@ class MultipleSessions: Reactor.Component {
{!this.hidden &&
((type == "fav" && <Favorites />) ||
(type == "lan" && handler.is_installed() && <LanPeers />) ||
(type == "ab" && <AddressBook />) ||
<SessionList sessions={handler.get_recent_sessions()} />)}
</div>;
}
@@ -349,3 +611,146 @@ class LanPeers: Reactor.Component {
}
view.on("size", function() { if (app && app.multipleSessions) app.multipleSessions.onSize(); });
/*
{
peers: [{id: "abcd", username: "", hostname: "", platform: "", alias: "", tags: ["", "", ...]}, ...],
tags: [],
}
*/
function handleAbError(err) {
abLoading = false;
err = translate(err);
stderr.println(err);
abError = err;
app.update();
}
function getAb() {
abLoading = true;
abError = "";
app.update();
httpRequest(handler.get_api_server() + "/api/ab/get", #post, {}, function(data) {
if (data) {
if (data.error) {
handleAbError(data.error);
return;
}
var tm = data.updated_at;
ab = JSON.parse(data.data);
if (!ab.tags) ab.tags = [];
if (!ab.peers) ab.peers = [];
}
abLoading = false;
app.update();
}, function(err, status) {
handleAbError(err);
}, getHttpHeaders());
}
function updateAb() {
httpRequest(handler.get_api_server() + "/api/ab", #post, { data: JSON.stringify(ab) }, function(data) {
}, function(err, status) {
}, getHttpHeaders());
}
function resetAb() {
ab = { tags: [], peers: [] };
app.update();
}
function updateAbPeer() {
if (ab.peers.length == 0) return;
// to-do: inefficient
var sessions = handler.get_recent_sessions();
if (sessions.length == 0) return;
var s = sessions[0];
var id = s[0] || "";
var p;
for (var tmp in ab.peers) {
if (tmp.id == id) p = tmp;
}
if (!p) return;
var username = s[1] || "";
var hostname = s[2] || "";
var platform = s[3] || "";
var alias = s[4] || "";
var updated;
if (username != (p.username || "")) {
p.username = username;
updated = true;
}
if (hostname != (p.hostname || "")) {
p.hostname = hostname;
updated = true;
}
if (platform != (p.platform || "")) {
p.platform = platform;
updated = true;
}
if (alias != (p.alias || "")) {
if (alias) {
p.alias = alias;
} else if (p.alias) {
handler.set_peer_option(id, "alias", p.alias);
}
updated = true;
}
if (updated) {
updateAb();
stdout.println("Ab peer updated");
}
}
var is_edit_rdp_port;
class EditRdpPort: Reactor.Component {
function render() {
return <span style="margin-left: 12px; padding: 0 6px; display: inline-block;" .link>{svg_edit}</span>;
}
function onMouse(evt) {
if (evt.type == Event.MOUSE_DOWN) {
is_edit_rdp_port = true;
editRdpPort();
}
}
}
function editRdpPort() {
var id = current_menu_peer_id;
var p0 = handler.get_peer_option(id, "rdp_port");
var port = p0 ? <input|text name='port' value={p0} /> :
<input|text name='port' novalue={3389} />;
var name0 = handler.get_peer_option(id, "rdp_username");
var pass0 = handler.get_peer_option(id, "rdp_password");
msgbox("custom-rdp-port", 'RDP ' + translate('Settings'), <div .form .set-password>
<div><span>{translate('Port')}:</span>{port}</div>
<div><span>{translate('Username')}:</span><input|text name="username" value={name0} /></div>
<div><span>{translate('Password')}:</span><PasswordComponent value={pass0} /></div>
</div>, function(res=null) {
if (!res) return;
var p = (res.port || '').trim();
if (p != p0) {
if (!p) p = '0';
p = p.toNumber();
if (p < 0 || p != p.toInteger()) {
return translate("Invalid port");
}
if (p == 0) p = "";
else p = p.toInteger() + '';
handler.set_peer_option(id, "rdp_port", p);
}
var name = (res.username || '').trim();
if (name != name0) {
handler.set_peer_option(id, "rdp_username", name);
}
var pass = (res.password || '').trim();
if (pass != pass0) {
handler.set_peer_option(id, "rdp_password", pass);
}
}, 240);
}

View File

@@ -5,7 +5,7 @@ use clipboard::{
};
use hbb_common::{
allow_err,
config::{Config, ICON},
config::Config,
fs, log,
message_proto::*,
protobuf::Message as _,
@@ -21,6 +21,7 @@ use std::{
pub struct ConnectionManagerInner {
root: Option<Element>,
senders: HashMap<i32, mpsc::UnboundedSender<Data>>,
click_time: i64,
}
#[derive(Clone)]
@@ -41,6 +42,7 @@ impl ConnectionManager {
let inner = ConnectionManagerInner {
root: None,
senders: HashMap::new(),
click_time: Default::default(),
};
let cm = Self(Arc::new(RwLock::new(inner)));
let cloned = cm.clone();
@@ -49,7 +51,18 @@ impl ConnectionManager {
}
fn get_icon(&mut self) -> String {
ICON.to_owned()
crate::get_icon()
}
fn check_click_time(&mut self, id: i32) {
let lock = self.read().unwrap();
if let Some(s) = lock.senders.get(&id) {
allow_err!(s.send(Data::ClickTime(0)));
}
}
fn get_click_time(&self) -> f64 {
self.read().unwrap().click_time as _
}
#[inline]
@@ -112,6 +125,9 @@ impl ConnectionManager {
Data::ChatMessage { text } => {
self.call("newMessage", &make_args!(id, text));
}
Data::ClickTime(ms) => {
self.write().unwrap().click_time = ms;
}
Data::FS(v) => match v {
ipc::FS::ReadDir {
dir,
@@ -330,6 +346,8 @@ impl sciter::EventHandler for ConnectionManager {
sciter::dispatch_script_call! {
fn t(String);
fn check_click_time(i32);
fn get_click_time();
fn get_icon();
fn close(i32);
fn authorize(i32);
@@ -421,7 +439,6 @@ async fn start_ipc(cm: ConnectionManager) {
#[tokio::main(flavor = "current_thread")]
async fn start_pa() {
use crate::audio_service::AUDIO_DATA_SIZE_U8;
use hbb_common::config::APP_NAME;
match new_listener("_pa").await {
Ok(mut incoming) => {
@@ -448,14 +465,14 @@ async fn start_pa() {
let spec = pulse::sample::Spec {
format: pulse::sample::Format::F32le,
channels: 2,
rate: crate::platform::linux::PA_SAMPLE_RATE,
rate: crate::platform::PA_SAMPLE_RATE,
};
log::info!("pa monitor: {:?}", device);
// systemctl --user status pulseaudio.service
let mut buf: Vec<u8> = vec![0; AUDIO_DATA_SIZE_U8];
match psimple::Simple::new(
None, // Use the default server
APP_NAME, // Our applications name
&crate::get_app_name(), // Our applications name
pulse::stream::Direction::Record, // We want a record stream
Some(&device), // Use the default device
"record", // Description of our stream

View File

@@ -226,7 +226,13 @@ event click $(div.chaticon) {
}
function checkClickTime(callback) {
callback();
var click_callback_time = getTime();
handler.check_click_time(body.cid);
self.timer(120ms, function() {
var d = click_callback_time - handler.get_click_time();
if (d > 120)
callback();
});
}
function adaptSizeForChat() {
@@ -234,10 +240,10 @@ function adaptSizeForChat() {
display: show_chat ? "block" : "none",
};
var (x, y, w, h) = view.box(#rectw, #border, #screen);
if (show_chat && w < 600) {
view.move(x - (600 - w), y, 600, h);
} else if (!show_chat && w > 450) {
view.move(x + (w - 300), y, 300, h);
if (show_chat && w < scaleIt(600)) {
view.move(x - (scaleIt(600) - w), y, scaleIt(600), h);
} else if (!show_chat && w > scaleIt(450)) {
view.move(x + (w - scaleIt(300)), y, scaleIt(300), h);
}
}
@@ -327,8 +333,8 @@ view << event statechange {
function self.ready() {
adjustBorder();
var (sw, sh) = view.screenBox(#workarea, #dimension);
var w = 300;
var h = 400;
var w = scaleIt(300);
var h = scaleIt(400);
view.move(sw - w, 0, w, h);
}
@@ -372,7 +378,7 @@ function self.closing() {
function adjustHeader() {
var hw = $(header).box(#width);
var hw = $(header).box(#width) / scaleFactor;
var tabswrapper = $(div.tabs-wrapper);
var tabs = $(div.tabs);
var arrows = $(div.tab-arrows);
@@ -380,7 +386,7 @@ function adjustHeader() {
var n = connections.length;
var wtab = 80;
var max = hw - 98;
var need_width = n * wtab + 2; // include border of active tab
var need_width = n * wtab + scaleIt(2); // include border of active tab
if (need_width < max) {
arrows.style.set {
display: "none",

View File

@@ -324,6 +324,33 @@ menu li.line-through {
color: red;
}
#tags {
border: color(border) 1px solid;
size: *;
padding: 0.5em;
overflow-y: scroll-indicator;
border-spacing: 0.5em;
flow: horizontal-flow;
}
#tags span {
display: inline-block;
border: color(border) 1px solid;
border-radius: 6px;
padding: 3px 0.5em;
word-wrap: normal;
}
#tags span.active {
background: color(button);
border-color: color(button);
color: white;
}
#tags span:hover {
border-color: color(hover-border);
}
div#msgbox .msgbox-icon svg {
size: 80px;
background: white;

View File

@@ -16,6 +16,19 @@ function isEnterKey(evt) {
(is_linux && evt.keyCode == 65421));
}
function getScaleFactor() {
if (!is_win) return 1;
return self.toPixels(10000dip) / 10000.;
}
var scaleFactor = getScaleFactor();
view << event resolutionchange {
scaleFactor = getScaleFactor();
}
function scaleIt(x) {
return (x * scaleFactor).toInteger();
}
stdout.println("scaleFactor", scaleFactor);
function translate(name) {
try {
return handler.t(name);
@@ -226,6 +239,8 @@ function msgbox(type, title, content, callback=null, height=180, width=500, hasR
}
var remember = false;
try { remember = handler.get_remember(); } catch(e) {}
var auto_login = false;
try { auto_login = handler.get_option("auto-login") != ''; } catch(e) {}
width += is_xfce ? 50 : 0;
height += is_xfce ? 50 : 0;
@@ -248,7 +263,7 @@ function msgbox(type, title, content, callback=null, height=180, width=500, hasR
} else if (type.indexOf("custom") < 0 && !is_port_forward && !callback) {
callback = function() { view.close(); }
}
$(#msgbox).content(<MsgboxComponent width={width} height={height} type={type} title={title} content={content} remember={remember} callback={callback} contentStyle={contentStyle} hasRetry={hasRetry} />);
$(#msgbox).content(<MsgboxComponent width={width} height={height} auto_login={auto_login} type={type} title={title} content={content} remember={remember} callback={callback} contentStyle={contentStyle} hasRetry={hasRetry} />);
}
function connecting() {
@@ -361,10 +376,32 @@ class PasswordComponent: Reactor.Component {
}
}
// type: #post, #get, #delete, #put
function httpRequest(url, type, params, _onSuccess, _onError, headers="") {
if (type != #post) {
stderr.println("only post ok");
}
handler.post_request(url, JSON.stringify(params), headers);
function check_status() {
var status = handler.get_async_job_status();
if (status == " ") self.timer(0.1s, check_status);
else {
try {
var data = JSON.parse(status);
_onSuccess(data);
} catch (e) {
_onError(status, 0);
}
}
}
check_status();
}
function isReasonableSize(r) {
var x = r[0];
var y = r[1];
return !(x < -3200 || x > 3200 || y < -3200 || y > 3200);
var n = scaleIt(3200);
return !(x < -n || x > n || y < -n || y > n);
}
function awake() {

View File

@@ -60,7 +60,39 @@ function stateChanged() {
var header;
var old_window_state = View.WINDOW_SHOWN;
var is_edit_os_password;
class EditOsPassword: Reactor.Component {
function render() {
return <span style="margin-left: 12px; padding: 0 6px; display: inline-block;" .link>{svg_edit}</span>;
}
function onMouse(evt) {
if (evt.type == Event.MOUSE_DOWN) {
is_edit_os_password = true;
editOSPassword();
}
}
}
function editOSPassword(login=false) {
var p0 = handler.get_option('os-password');
msgbox("custom-os-password", 'OS Password', p0, function(res=null) {
if (!res) return;
var a0 = handler.get_option('auto-login') != '';
var p = (res.password || '').trim();
var a = res.auto_login || false;
if (p == p0 && a == a0) return;
if (p != p0) handler.set_option('os-password', p);
if (a != a0) handler.set_option('auto-login', a ? 'Y' : '');
if (p && login) {
handler.input_os_password(p, true);
}
});
}
class Header: Reactor.Component {
this var conn_note = "";
function this() {
header = this;
}
@@ -138,8 +170,10 @@ class Header: Reactor.Component {
function renderActionPop() {
return <popup>
<menu.context #action-options>
{keyboard_enabled ? <li #os-password>{translate('OS Password')}<EditOsPassword /></li> : ""}
<li #transfer-file>{translate('Transfer File')}</li>
<li #tunnel>{translate('TCP Tunneling')}</li>
{handler.get_audit_server() && <li #note>{translate('Note')}</li>}
<div .separator />
{keyboard_enabled && (pi.platform == "Linux" || pi.sas_enabled) ? <li #ctrl-alt-del>{translate('Insert')} Ctrl + Alt + Del</li> : ""}
{keyboard_enabled ? <li #lock-screen>{translate('Insert Lock')}</li> : ""}
@@ -224,11 +258,33 @@ class Header: Reactor.Component {
event click $(#transfer-file) {
handler.transfer_file();
}
event click $(#os-password) (evt) {
if (is_edit_os_password) {
is_edit_os_password = false;
return;
}
var p = handler.get_option('os-password');
if (!p) editOSPassword(true);
else handler.input_os_password(p, true);
}
event click $(#tunnel) {
handler.tunnel();
}
event click $(#note) {
var self = this;
msgbox("custom", "Note", <div .form>
<textarea .outline-focus spellcheck="false" name="text" novalue="input note here" style="overflow: scroll-indicator; width:*; height: 140px; font-size: 1.2em; padding: 0.5em;">{self.conn_note}</textarea>
</div>, function(res=null) {
if (!res) return;
if (!res.text) return;
self.conn_note = res.text;
handler.send_note(res.text);
}, 280);
}
event click $(#ctrl-alt-del) {
handler.ctrl_alt_del();
}
@@ -355,7 +411,7 @@ function updateWindowToolbarPosition() {
var el = $(div.window-toolbar);
var w1 = el.box(#width, #border);
var w2 = $(header).box(#width, #border);
var x = (w2 - w1) / 2;
var x = (w2 - w1) / 2 / scaleFactor;
el.style.set {
left: x + "px",
display: "block",
@@ -391,10 +447,10 @@ function startChat() {
}
var icon = handler.get_icon();
var (sx, sy, sw, sh) = view.screenBox(#workarea, #rectw);
var w = 300;
var h = 400;
var w = scaleIt(300);
var h = scaleIt(400);
var x = (sx + sw - w) / 2;
var y = sy + 80;
var y = sy + scaleIt(80);
var params = {
type: View.FRAME_WINDOW,
x: x,

View File

@@ -34,6 +34,20 @@ body {
border-right: color(border) 1px solid;
}
#ab .left-pane {
background: white;
border-radius: 1em;
padding: 1em;
}
#ab .right-pane {
background: none;
}
#ab .right-content {
overflow: unset;
}
.left-pane > div:nth-child(1) {
border-spacing: 1em;
padding: 20px;
@@ -358,15 +372,19 @@ div.trust-me > div:nth-child(5) {
text-align: center;
}
div#myid {
div#myid, div#tags-label {
position: relative;
}
div#myid svg#menu {
div#myid svg#menu, div#tags-label svg#menu {
position: absolute;
right: -1em;
}
div#tags-label svg#menu:hover {
background-color: #ddd;
}
div.remote-session svg#menu {
position: absolute;
right: 0;

View File

@@ -1,13 +1,16 @@
if (is_osx) view.windowBlurbehind = #light;
stdout.println("current platform:", OS);
stdout.println("is_xfce: ", is_xfce);
// html min-width, min-height not working on mac, below works for all
view.windowMinSize = (500, 300);
view.windowMinSize = (scaleIt(500), scaleIt(300));
var app;
var tmp = handler.get_connect_status();
var connect_status = tmp[0];
var service_stopped = handler.get_option("stop-service") == "Y";
var rendezvous_service_stopped = false;
var using_public_server = handler.using_public_server();
var software_update_url = "";
var key_confirmed = tmp[1];
var system_error = "";
@@ -42,12 +45,17 @@ class ConnectStatus: Reactor.Component {
} else if (connect_status == 0) {
return translate('connecting_status');
}
return translate("Ready");
if (!handler.using_public_server()) return translate('Ready');
return <span>{translate("Ready")}, <span .link #setup-server>{translate("setup_server_tip")}</span></span>;
}
event click $(#start-service) () {
handler.set_option("stop-service", "");
}
event click $(#setup-server) () {
handler.open_url("https://rustdesk.com/blog/id-relay-set/");
}
}
function createNewConnect(id, type) {
@@ -62,6 +70,19 @@ function createNewConnect(id, type) {
handler.new_remote(id, type);
}
class ShareRdp: Reactor.Component {
function render() {
var rdp_shared_string = translate("Enable RDP session sharing");
var cls = handler.is_share_rdp() ? "selected" : "line-through";
return <li class={cls}><span>{svg_checkmark}</span>{rdp_shared_string}</li>;
}
function onClick() {
handler.set_share_rdp(!handler.is_share_rdp());
this.update();
}
}
var direct_server;
class DirectServer: Reactor.Component {
function this() {
@@ -144,6 +165,13 @@ class AudioInputs: Reactor.Component {
}
}
function getUserName() {
try {
return JSON.parse(handler.get_local_option("user_info")).name;
} catch(e) {}
return '';
}
class MyIdMenu: Reactor.Component {
function this() {
myIdMenu = this;
@@ -152,11 +180,12 @@ class MyIdMenu: Reactor.Component {
function render() {
return <div #myid>
{this.renderPop()}
{translate("ID")}{svg_menu}
ID{svg_menu}
</div>;
}
function renderPop() {
var username = handler.get_local_option("access_token") ? getUserName() : '';
return <popup>
<menu.context #config-options>
<li #enable-keyboard><span>{svg_checkmark}</span>{translate('Enable Keyboard/Mouse')}</li>
@@ -164,16 +193,24 @@ class MyIdMenu: Reactor.Component {
<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 />
<li #allow-remote-config-modification><span>{svg_checkmark}</span>{translate('Enable remote configuration modification')}</li>
<div .separator />
<li #custom-server>{translate('ID/Relay Server')}</li>
<li #whitelist title={translate('whitelist_tip')}>{translate('IP Whitelisting')}</li>
<li #socks5-server>{translate('Socks5 Proxy')}</li>
{is_win ? <li #install-virtual-display>Install virtual display</li> : ""}
<div .separator />
<li #stop-service><span>{svg_checkmark}</span>{translate("Enable Service")}</li>
<li #stop-service class={service_stopped ? "line-through" : "selected"}><span>{svg_checkmark}</span>{translate("Enable Service")}</li>
{handler.is_rdp_service_open() ? <ShareRdp /> : ""}
<DirectServer />
{false && handler.using_public_server() && <li #allow-always-relay><span>{svg_checkmark}</span>{translate('Always connected via relay')}</li>}
{handler.has_rendezvous_service() ? <li #stop-rendezvous-service>{translate(rendezvous_service_stopped ? "Start ID/relay service" : "Stop ID/relay service")}</li> : ""}
{handler.is_ok_change_id() ? <div .separator /> : ""}
{username ?
<li #logout>{translate('Logout')} ({username})</li> :
<li #login>{translate('Login')}</li>}
{handler.is_ok_change_id() && key_confirmed ? <li #change-id>{translate('Change ID')}</li> : ""}
<div .separator />
<li #about>{translate('About')} {" "} {handler.get_app_name()}</li>
<li #about>{translate('About')} {" "}{handler.get_app_name()}</li>
</menu>
</popup>;
}
@@ -190,15 +227,25 @@ class MyIdMenu: Reactor.Component {
this.$(svg#menu).popup(menu);
}
event click $(li#login) () {
login();
}
event click $(li#logout) () {
logout();
}
function toggleMenuState() {
for (var el in $$(menu#config-options>li)) {
if (el.id && el.id.indexOf("enable-") == 0) {
var enabled = handler.get_option(el.id) != "N";
el.attributes.toggleClass("selected", enabled);
el.attributes.toggleClass("line-through", !enabled);
} else if (el.id && el.id === "stop-service") {
el.attributes.toggleClass("selected", !service_stopped);
el.attributes.toggleClass("line-through", service_stopped);
}
if (el.id && el.id.indexOf("allow-") == 0) {
var enabled = handler.get_option(el.id) == "Y";
el.attributes.toggleClass("selected", enabled);
el.attributes.toggleClass("line-through", !enabled);
}
}
}
@@ -207,9 +254,10 @@ class MyIdMenu: Reactor.Component {
var name = handler.get_app_name();
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://rustdesk.com'>Website</div> \
<div .link .custom-event url='https://rustdesk.com/privacy'>Privacy Statement</div> \
<div .link .custom-event url='https://rustdesk.com'>Website</div> \
<div style='background: #2c8cff; color: white; padding: 1em; margin-top: 1em;'>Copyright &copy; 2022 Purslane Ltd.\
<br />" + handler.get_license() + " \
<p style='font-weight: bold'>Made with heart in this chaotic world!</p>\
</div>\
</div>", function(el) {
@@ -223,11 +271,14 @@ class MyIdMenu: Reactor.Component {
if (me.id && me.id.indexOf("enable-") == 0) {
handler.set_option(me.id, handler.get_option(me.id) == "N" ? "" : "N");
}
if (me.id && me.id.indexOf("allow-") == 0) {
handler.set_option(me.id, handler.get_option(me.id) == "Y" ? "" : "Y");
}
if (me.id == "whitelist") {
var old_value = handler.get_option("whitelist").split(",").join("\n");
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: 140px; font-size: 1.2em; padding: 0.5em;\">" + old_value + "</textarea>\
<textarea .outline-focus spellcheck=\"false\" name=\"text\" novalue=\"0.0.0.0\" style=\"overflow: scroll-indicator; width:*; height: 140px; font-size: 1.2em; padding: 0.5em;\">" + old_value + "</textarea>\
</div> \
", function(res=null) {
if (!res) return;
@@ -248,16 +299,22 @@ class MyIdMenu: Reactor.Component {
} else if (me.id == "custom-server") {
var configOptions = handler.get_options();
var old_relay = configOptions["relay-server"] || "";
var old_api = configOptions["api-server"] || "";
var old_id = configOptions["custom-rendezvous-server"] || "";
var old_key = configOptions["key"] || "";
msgbox("custom-server", "ID/Relay Server", "<div .form .set-password> \
<div><span>" + translate("ID Server") + ": </span><input .outline-focus name='id' value='" + old_id + "' /></div> \
<div><span>" + translate("Relay Server") + ": </span><input name='relay' value='" + old_relay + "' /></div> \
<div><span>" + translate("API Server") + ": </span><input name='api' value='" + old_api + "' /></div> \
<div><span>" + translate("Key") + ": </span><input name='key' value='" + old_key + "' /></div> \
</div> \
", function(res=null) {
if (!res) return;
var id = (res.id || "").trim();
var relay = (res.relay || "").trim();
if (id == old_id && relay == old_relay) return;
var api = (res.api || "").trim().toLowerCase();
var key = (res.key || "").trim();
if (id == old_id && relay == old_relay && key == old_key && api == old_api) return;
if (id) {
var err = handler.test_if_valid_server(id);
if (err) return translate("ID Server") + ": " + err;
@@ -266,10 +323,17 @@ class MyIdMenu: Reactor.Component {
var err = handler.test_if_valid_server(relay);
if (err) return translate("Relay Server") + ": " + err;
}
if (api) {
if (0 != api.indexOf("https://") && 0 != api.indexOf("http://")) {
return translate("API Server") + ": " + translate("invalid_http");
}
}
configOptions["custom-rendezvous-server"] = id;
configOptions["relay-server"] = relay;
configOptions["api-server"] = api;
configOptions["key"] = key;
handler.set_options(configOptions);
}, 240);
}, 260);
} else if (me.id == "socks5-server") {
var socks5 = handler.get_socks() || {};
var old_proxy = socks5[0] || "";
@@ -292,10 +356,33 @@ class MyIdMenu: Reactor.Component {
}
handler.set_socks(proxy, username, password);
}, 240);
} else if (me.id == "install-virtual-display") {
handler.install_virtual_display();
} else if (me.id == "stop-service") {
handler.set_option("stop-service", service_stopped ? "" : "Y");
} else if (me.id == "stop-rendezvous-service") {
handler.set_option("stop-rendezvous-service", rendezvous_service_stopped ? "" : "Y");
} else if (me.id == "change-id") {
msgbox("custom-id", translate("Change ID"), "<div .form> \
<div>" + translate('id_change_tip') + " </div> \
<div><span style='width: 100px; display:inline-block'>ID: </span><input .outline-focus style='width: 250px' name='id' /></div> \
</div> \
", function(res=null, show_progress) {
if (!res) return;
show_progress();
var id = (res.id || "").trim();
if (!id) return;
if (id == my_id) return;
handler.change_id(id);
function check_status() {
var status = handler.get_async_job_status();
if (status == " ") self.timer(0.1s, check_status);
else {
if (status) show_progress(false, translate(status));
else show_progress(-1);
}
}
check_status();
return " ";
});
} else if (me.id == "about") {
this.showAbout()
}
@@ -387,6 +474,7 @@ class App: Reactor.Component
</div>
<ConnectStatus @{this.connect_status} />
</div>
<div #overlay style="position: absolute;size:*;background:black;opacity:0.5;display:none" />
<div #msgbox />
</div>;
}
@@ -418,48 +506,28 @@ class InstallMe: Reactor.Component {
}
}
const http = function() {
function makeRequest(httpverb) {
return function( params ) {
params.type = httpverb;
view.request(params);
};
}
function download(from, to, args..)
{
var rqp = { type:#get, url: from, toFile: to };
var fn = 0;
var on = 0;
for( var p in args )
if( p instanceof Function )
{
switch(++fn) {
case 1: rqp.success = p; break;
case 2: rqp.error = p; break;
case 3: rqp.progress = p; break;
}
} else if( p instanceof Object )
{
switch(++on) {
case 1: rqp.params = p; break;
case 2: rqp.headers = p; break;
}
function download(from, to, args..) {
var rqp = { type:#get, url: from, toFile: to };
var fn = 0;
var on = 0;
for( var p in args ) {
if( p instanceof Function ) {
switch(++fn) {
case 1: rqp.success = p; break;
case 2: rqp.error = p; break;
case 3: rqp.progress = p; break;
}
} else if( p instanceof Object ) {
switch(++on) {
case 1: rqp.params = p; break;
case 2: rqp.headers = p; break;
}
}
view.request(rqp);
}
return {
get: makeRequest(#get),
post: makeRequest(#post),
put: makeRequest(#put),
del: makeRequest(#delete),
download: download
};
}();
}
view.request(rqp);
}
// current running version is higher than installed
class UpgradeMe: Reactor.Component {
function render() {
var update_or_download = is_osx ? "download" : "update";
@@ -509,7 +577,7 @@ class UpdateMe: Reactor.Component {
el.content("Downloading %" + (loaded * 100 / total));
};
stdout.println("Downloading " + url + " to " + path);
http.download(
download(
url,
self.url(path),
onsuccess, onerror, onprogress);
@@ -778,7 +846,6 @@ event keydown (evt) {
$(body).content(<App />);
function self.closing() {
// return false; // can prevent window close
var (x, y, w, h) = view.box(#rectw, #border, #screen);
handler.closing(x, y, w, h);
return true;
@@ -787,13 +854,19 @@ function self.closing() {
function self.ready() {
var r = handler.get_size();
if (isReasonableSize(r) && r[2] > 0) {
view.move(r[0], r[1], r[2], r[3]);
var (sx, sy, sw, sh) = view.screenBox(#workarea, #rectw);
if (r[2] >= sw && r[3] >= sh) {
self.timer(1ms, function() { view.windowState = View.WINDOW_MAXIMIZED; });
} else {
view.move(r[0], r[1], r[2], r[3]);
}
} else {
centerize(800, 600);
centerize(scaleIt(800), scaleIt(600));
}
if (!handler.get_remote_id()) {
view.focus = $(#remote_id);
}
refreshCurrentUser();
}
function showAbout() {
@@ -801,6 +874,7 @@ function showAbout() {
}
function showSettings() {
if ($(#overlay).style#display == 'block') return;
myIdMenu.showSettingMenu();
}
@@ -811,6 +885,16 @@ function checkConnectStatus() {
service_stopped = tmp;
app.update();
}
tmp = !!handler.get_option("stop-rendezvous-service");
if (tmp != rendezvous_service_stopped) {
rendezvous_service_stopped = tmp;
myIdMenu.update();
}
tmp = handler.using_public_server();
if (tmp != using_public_server) {
using_public_server = tmp;
app.connect_status.update();
}
tmp = handler.get_connect_status();
if (tmp[0] != connect_status) {
connect_status = tmp[0];
@@ -836,10 +920,126 @@ function checkConnectStatus() {
}
if (handler.recent_sessions_updated()) {
stdout.println("recent sessions updated");
updateAbPeer();
app.update();
}
checkConnectStatus();
});
check_if_overlay();
checkConnectStatus();
});
}
var enter = false;
function self.onMouse(evt) {
switch(evt.type) {
case Event.MOUSE_ENTER:
enter = true;
check_if_overlay();
break;
case Event.MOUSE_LEAVE:
$(#overlay).style#display = 'none';
enter = false;
break;
}
}
function check_if_overlay() {
if (!handler.get_option('allow-remote-config-modification')) {
var time0 = getTime();
handler.check_mouse_time();
self.timer(120ms, function() {
if (!enter) return;
var d = time0 - handler.get_mouse_time();
if (d < 120) $(#overlay).style#display = 'block';
});
}
}
checkConnectStatus();
function login() {
var name0 = getUserName();
var pass0 = '';
msgbox("custom-login", translate('Login'), <div .form .set-password>
<div><span>{translate('Username')}:</span><input|text name="username" value={name0} .outline-focus /></div>
<div><span>{translate('Password')}:</span><PasswordComponent value={pass0} /></div>
</div>, function(res=null, show_progress) {
if (!res) return;
show_progress();
var name = (res.username || '').trim();
if (!name) {
show_progress(false, translate("Username missed"));
return " ";
}
var pass = (res.password || '').trim();
if (!pass) {
show_progress(false, translate("Password missed"));
return " ";
}
abLoading = true;
var url = handler.get_api_server();
httpRequest(url + "/api/login", #post, {username: name, password: pass, id: my_id, uuid: handler.get_uuid()}, function(data) {
if (data.error) {
abLoading = false;
var err = translate(data.error);
show_progress(false, err);
return;
}
handler.set_local_option("access_token", data.access_token);
handler.set_local_option("user_info", JSON.stringify(data.user));
show_progress(-1);
myIdMenu.update();
getAb();
}, function(err, status) {
abLoading = false;
err = translate(err);
if (url.indexOf('rustdesk') < 0) err = url + ', ' + err;
show_progress(false, err);
});
return " ";
});
}
function reset_token() {
handler.set_local_option("access_token", "");
handler.set_local_option("user_info", "");
handler.set_local_option("selected-tags", "");
myIdMenu.update();
resetAb();
if (abComponent) {
abComponent.update();
}
}
function logout() {
var url = handler.get_api_server();
httpRequest(url + "/api/logout", #post, {id: my_id, uuid: handler.get_uuid()}, function(data) {
}, function(err, status) {
msgbox("custom-error", translate('Error'), err);
}, getHttpHeaders());
reset_token();
}
function refreshCurrentUser() {
if (!handler.get_local_option("access_token")) return;
abLoading = true;
abError = "";
app.update();
httpRequest(handler.get_api_server() + "/api/currentUser", #post, {id: my_id, uuid: handler.get_uuid()}, function(data) {
if (data.error) {
handleAbError(data.error);
return;
}
handler.set_local_option("user_info", JSON.stringify(data));
myIdMenu.update();
getAb();
}, function(err, status) {
if (status == 401 || status == 400) {
reset_token();
}
handleAbError(err);
}, getHttpHeaders());
}
function getHttpHeaders() {
return "Authorization: Bearer " + handler.get_local_option("access_token");
}

View File

@@ -5,7 +5,7 @@
div.content {
size: *;
background: white;
padding:2em 10em;
padding:2em 8em;
border-spacing: 1em;
}
input {

View File

@@ -1,12 +1,16 @@
function self.ready() {
centerize(800, 600);
centerize(scaleIt(800), scaleIt(600));
}
var install_path = "";
class Install: Reactor.Component {
function render() {
return <div .content>
<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 style="margin: 2em 0;">{translate('Installation Path')} {": "}<input|text disabled value={view.install_path()} #path_input />
<button .button .outline #path style="margin-left: 1em">{translate('Change Path')}</button>
</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>
@@ -16,6 +20,9 @@ class Install: Reactor.Component {
<progress style={"color:" + color} style="display: none" />
<button .button id="cancel" .outline style="margin-right: 2em;">{translate('Cancel')}</button>
<button .button id="submit">{translate('Accept and Install')}</button>
{handler.show_run_without_install() && <button .button #run-without-install .outline style="margin-left: 2em;">
{translate('Run without install')}
</button>}
</div>
</div>;
}
@@ -24,6 +31,21 @@ class Install: Reactor.Component {
view.close();
}
event click $(#run-without-install) {
handler.run_without_install();
}
event click $(#path) {
install_path = view.selectFolder() || "";
if (install_path) {
install_path = install_path.urlUnescape();
install_path = install_path.replace("file://", "").replace("/", "\\");
if (install_path[install_path.length - 1] != "\\") install_path += "\\";
install_path += handler.get_app_name();
$(#path_input).value = install_path;
}
}
event click $(#aggrement) {
view.open_url("http://rustdesk.com/privacy");
}
@@ -38,7 +60,7 @@ class Install: Reactor.Component {
if ($(#desktopicon).value) {
args += "desktopicon ";
}
view.install_me(args);
view.install_me(args, install_path);
}
}

View File

@@ -25,6 +25,7 @@ class MsgboxComponent: Reactor.Component {
this.remember = params.remember;
this.callback = params.callback;
this.hasRetry = params.hasRetry;
this.auto_login = params.auto_login;
this.contentStyle = params.contentStyle;
try { this.content = translate_text(this.content); } catch (e) {}
}
@@ -58,11 +59,18 @@ class MsgboxComponent: Reactor.Component {
if (this.type == "input-password") {
return this.getInputPasswordContent();
}
if (this.type == "custom-os-password") {
var ts = this.auto_login ? { checked: true } : {};
return <div .form>
<PasswordComponent value={this.content} />
<div><button|checkbox(auto_login) {ts}>{translate('Auto Login')}</button></div>
</div>;
}
return this.content;
}
function getColor() {
if (this.type == "input-password") {
if (this.type == "input-password" || this.type == "custom-os-password") {
return "#AD448E";
}
if (this.type == "success") {

View File

@@ -12,7 +12,7 @@ use clipboard::{
use enigo::{self, Enigo, KeyboardControllable};
use hbb_common::{
allow_err,
config::{self, Config, PeerConfig},
config::{Config, LocalConfig, PeerConfig},
fs, log,
message_proto::{permission_info::Permission, *},
protobuf::Message as _,
@@ -88,6 +88,8 @@ impl Deref for Handler {
}
}
impl FileManager for Handler {}
impl sciter::EventHandler for Handler {
fn get_subscription(&mut self) -> Option<EVENT_GROUPS> {
Some(EVENT_GROUPS::HANDLE_BEHAVIOR_EVENT)
@@ -155,12 +157,15 @@ impl sciter::EventHandler for Handler {
}
sciter::dispatch_script_call! {
fn get_audit_server();
fn send_note(String);
fn is_xfce();
fn get_id();
fn get_default_pi();
fn get_option(String);
fn t(String);
fn set_option(String, String);
fn input_os_password(String, bool);
fn save_close_state(String, String);
fn is_file_transfer();
fn is_port_forward();
@@ -243,6 +248,8 @@ impl Handler {
let mut me = self.clone();
let peer = self.peer_platform();
let is_win = peer == "Windows";
#[cfg(windows)]
crate::platform::windows::enable_lowlevel_keyboard(std::ptr::null_mut() as _);
std::thread::spawn(move || {
// This will block.
std::env::set_var("KEYBOARD_ONLY", "y"); // pass to rdev
@@ -276,6 +283,9 @@ impl Handler {
#[cfg(not(windows))]
let ctrl = get_key_state(enigo::Key::Control);
let shift = get_key_state(enigo::Key::Shift);
#[cfg(windows)]
let command = crate::platform::windows::get_win_key_state();
#[cfg(not(windows))]
let command = get_key_state(enigo::Key::Meta);
let control_key = match key {
Key::Alt => Some(ControlKey::Alt),
@@ -530,6 +540,27 @@ impl Handler {
crate::client::translate(name)
}
fn get_audit_server(&self) -> String {
if self.lc.read().unwrap().conn_id <= 0
|| LocalConfig::get_option("access_token").is_empty()
{
return "".to_owned();
}
crate::get_audit_server(
Config::get_option("api-server"),
Config::get_option("custom-rendezvous-server"),
)
}
fn send_note(&self, note: String) {
let url = self.get_audit_server();
let id = self.id.clone();
let conn_id = self.lc.read().unwrap().conn_id;
std::thread::spawn(move || {
send_note(url, id, conn_id, note);
});
}
fn is_xfce(&self) -> bool {
crate::platform::is_xfce()
}
@@ -659,6 +690,10 @@ impl Handler {
self.lc.write().unwrap().set_option(k, v);
}
fn input_os_password(&mut self, pass: String, activate: bool) {
input_os_password(pass, activate, self.clone());
}
fn save_close_state(&self, k: String, v: String) {
self.write().unwrap().close_state.insert(k, v);
}
@@ -671,38 +706,7 @@ impl Handler {
}
fn get_icon(&mut self) -> String {
config::ICON.to_owned()
}
fn get_home_dir(&mut self) -> String {
fs::get_home_as_string()
}
fn read_dir(&mut self, path: String, include_hidden: bool) -> Value {
match fs::read_dir(&fs::get_path(&path), include_hidden) {
Err(_) => Value::null(),
Ok(fd) => {
let mut m = make_fd(0, &fd.entries.to_vec(), false);
m.set_item("path", path);
m
}
}
}
fn cancel_job(&mut self, id: i32) {
self.send(Data::CancelJob(id));
}
fn read_remote_dir(&mut self, path: String, include_hidden: bool) {
let mut msg_out = Message::new();
let mut file_action = FileAction::new();
file_action.set_read_dir(ReadDir {
path,
include_hidden,
..Default::default()
});
msg_out.set_file_action(file_action);
self.send(Data::Message(msg_out));
crate::get_icon()
}
fn send_chat(&mut self, text: String) {
@@ -727,45 +731,6 @@ impl Handler {
self.send(Data::Message(msg_out));
}
fn remove_file(&mut self, id: i32, path: String, file_num: i32, is_remote: bool) {
self.send(Data::RemoveFile((id, path, file_num, is_remote)));
}
fn remove_dir_all(&mut self, id: i32, path: String, is_remote: bool) {
self.send(Data::RemoveDirAll((id, path, is_remote)));
}
fn confirm_delete_files(&mut self, id: i32, file_num: i32) {
self.send(Data::ConfirmDeleteFiles((id, file_num)));
}
fn set_no_confirm(&mut self, id: i32) {
self.send(Data::SetNoConfirm(id));
}
fn remove_dir(&mut self, id: i32, path: String, is_remote: bool) {
if is_remote {
self.send(Data::RemoveDir((id, path)));
} else {
fs::remove_all_empty_dir(&fs::get_path(&path)).ok();
}
}
fn create_dir(&mut self, id: i32, path: String, is_remote: bool) {
self.send(Data::CreateDir((id, path, is_remote)));
}
fn send_files(
&mut self,
id: i32,
path: String,
to: String,
include_hidden: bool,
is_remote: bool,
) {
self.send(Data::SendFiles((id, path, to, include_hidden, is_remote)));
}
fn is_file_transfer(&self) -> bool {
self.cmd == "--file-transfer"
}
@@ -859,13 +824,6 @@ impl Handler {
fs::get_string(&path)
}
#[inline]
fn send(&mut self, data: Data) {
if let Some(ref sender) = self.read().unwrap().sender {
sender.send(data).ok();
}
}
fn login(&mut self, password: String, remember: bool) {
self.send(Data::Login((password, remember)));
}
@@ -875,12 +833,16 @@ impl Handler {
}
fn enter(&mut self) {
#[cfg(windows)]
crate::platform::windows::stop_system_key_propagate(true);
unsafe {
IS_IN = true;
}
}
fn leave(&mut self) {
#[cfg(windows)]
crate::platform::windows::stop_system_key_propagate(false);
unsafe {
IS_IN = false;
}
@@ -896,28 +858,17 @@ impl Handler {
shift: bool,
command: bool,
) {
let mut msg_out = Message::new();
let mut mouse_event = MouseEvent {
mask,
x,
y,
..Default::default()
};
if alt {
mouse_event.modifiers.push(ControlKey::Alt.into());
#[allow(unused_mut)]
let mut command = command;
#[cfg(windows)]
{
if !command && crate::platform::windows::get_win_key_state() {
command = true;
}
}
if shift {
mouse_event.modifiers.push(ControlKey::Shift.into());
}
if ctrl {
mouse_event.modifiers.push(ControlKey::Control.into());
}
if command {
mouse_event.modifiers.push(ControlKey::Meta.into());
}
msg_out.set_mouse_event(mouse_event);
self.send(Data::Message(msg_out));
// on macos, ctrl + left = right, up wont emit, so we need to
send_mouse(mask, x, y, alt, ctrl, shift, command, self);
// on macos, ctrl + left button down = right button down, up won't emit, so we need to
// emit up myself if peer is not macos
// to-do: how about ctrl + left from win to macos
if cfg!(target_os = "macos") {
@@ -1199,10 +1150,19 @@ async fn start_one_port_forward(
remote_host: String,
remote_port: i32,
receiver: mpsc::UnboundedReceiver<Data>,
key: &str,
token: &str,
) {
handler.lc.write().unwrap().port_forward = (remote_host, remote_port);
if let Err(err) =
crate::port_forward::listen(handler.id.clone(), port, handler.clone(), receiver).await
if let Err(err) = crate::port_forward::listen(
handler.id.clone(),
port,
handler.clone(),
receiver,
key,
token,
)
.await
{
handler.on_error(&format!("Failed to listen on {}: {}", port, err));
}
@@ -1213,9 +1173,28 @@ async fn start_one_port_forward(
async fn io_loop(handler: Handler) {
let (sender, mut receiver) = mpsc::unbounded_channel::<Data>();
handler.write().unwrap().sender = Some(sender.clone());
let mut options = crate::ipc::get_options_async().await;
let mut key = options.remove("key").unwrap_or("".to_owned());
let token = LocalConfig::get_option("access_token");
if key.is_empty() {
key = crate::platform::get_license_key();
}
if handler.is_port_forward() {
if handler.is_rdp() {
start_one_port_forward(handler, 0, "".to_owned(), 3389, receiver).await;
let port = handler
.get_option("rdp_port".to_owned())
.parse::<i32>()
.unwrap_or(3389);
std::env::set_var(
"rdp_username",
handler.get_option("rdp_username".to_owned()),
);
std::env::set_var(
"rdp_password",
handler.get_option("rdp_password".to_owned()),
);
log::info!("Remote rdp port: {}", port);
start_one_port_forward(handler, 0, "".to_owned(), port, receiver, &key, &token).await;
} else if handler.args.len() == 0 {
let pfs = handler.lc.read().unwrap().port_forwards.clone();
let mut queues = HashMap::<i32, mpsc::UnboundedSender<Data>>::new();
@@ -1231,6 +1210,8 @@ async fn io_loop(handler: Handler) {
let (sender, receiver) = mpsc::unbounded_channel::<Data>();
queues.insert(port, sender);
let handler = handler.clone();
let key = key.clone();
let token = token.clone();
tokio::spawn(async move {
start_one_port_forward(
handler,
@@ -1238,6 +1219,8 @@ async fn io_loop(handler: Handler) {
remote_host,
remote_port,
receiver,
&key,
&token,
)
.await;
});
@@ -1268,7 +1251,16 @@ async fn io_loop(handler: Handler) {
}
let remote_host = handler.args[1].clone();
let remote_port = handler.args[2].parse::<i32>().unwrap_or(0);
start_one_port_forward(handler, port, remote_host, remote_port, receiver).await;
start_one_port_forward(
handler,
port,
remote_host,
remote_port,
receiver,
&key,
&token,
)
.await;
}
return;
}
@@ -1296,7 +1288,7 @@ async fn io_loop(handler: Handler) {
#[cfg(windows)]
clipboard_file_context: None,
};
remote.io_loop().await;
remote.io_loop(&key, &token).await;
}
struct RemoveJob {
@@ -1339,7 +1331,7 @@ struct Remote {
}
impl Remote {
async fn io_loop(&mut self) {
async fn io_loop(&mut self, key: &str, token: &str) {
let stop_clipboard = self.start_clipboard();
let mut last_recv_time = Instant::now();
let conn_type = if self.handler.is_file_transfer() {
@@ -1347,7 +1339,7 @@ impl Remote {
} else {
ConnType::default()
};
match Client::start(&self.handler.id, conn_type).await {
match Client::start(&self.handler.id, key, token, conn_type).await {
Ok((mut peer, direct)) => {
unsafe {
SERVER_KEYBOARD_ENABLED = true;
@@ -1934,7 +1926,7 @@ impl Remote {
}
}
fn make_fd(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> Value {
pub fn make_fd(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> Value {
let mut m = Value::map();
m.set_item("id", id);
let mut a = Value::array(0);
@@ -1963,6 +1955,12 @@ fn make_fd(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> Value {
#[async_trait]
impl Interface for Handler {
fn send(&self, data: Data) {
if let Some(ref sender) = self.read().unwrap().sender {
sender.send(data).ok();
}
}
fn msgbox(&self, msgtype: &str, title: &str, text: &str) {
let retry = check_if_retry(msgtype, title, text);
self.call2("msgbox_retry", &make_args!(msgtype, title, text, retry));
@@ -2019,6 +2017,10 @@ impl Interface for Handler {
);
log::info!("[video] initialized: {:?}", ok);
});
let p = self.lc.read().unwrap().should_auto_login();
if !p.is_empty() {
input_os_password(p, true, self.clone());
}
}
self.lc.write().unwrap().handle_peer_info(username, pi);
self.call("updatePi", &make_args!(pi_sciter));
@@ -2031,7 +2033,7 @@ impl Interface for Handler {
{
let mut path = std::env::temp_dir();
path.push(&self.id);
let path = path.with_extension(config::APP_NAME.to_lowercase());
let path = path.with_extension(crate::get_app_name().to_lowercase());
std::fs::File::create(&path).ok();
if let Some(path) = path.to_str() {
crate::platform::windows::add_recent_document(&path);
@@ -2058,3 +2060,9 @@ impl Handler {
self.msgbox("error", "Error", err);
}
}
#[tokio::main(flavor = "current_thread")]
async fn send_note(url: String, id: String, conn_id: i32, note: String) {
let body = serde_json::json!({ "id": id, "Id": conn_id, "note": note });
allow_err!(crate::post_request(url, body.to_string(), "").await);
}

View File

@@ -22,7 +22,7 @@ handler.setDisplay = function(x, y, w, h) {
}
// in case toolbar not shown correclty
view.windowMinSize = (500, 300);
view.windowMinSize = (scaleIt(500), scaleIt(300));
function adaptDisplay() {
var w = display_width;
@@ -43,7 +43,7 @@ function adaptDisplay() {
var (x, y) = view.box(#position, #border, #screen);
// extra for border
var extra = is_win ? 4 : 2;
view.move(x, y, w + extra, h + hh + extra);
view.move(x, y, (w + extra).toInteger(), (h + hh + extra).toInteger());
}
}
}
@@ -67,8 +67,8 @@ function adaptDisplay() {
}
}
handler.style.set {
width: w + "px",
height: h + "px",
width: w / scaleFactor + "px",
height: h / scaleFactor + "px",
};
}
@@ -389,8 +389,8 @@ handler.setCursorPosition = function(x, y) {
cur_y = y - display_origin_y;
var x = cur_x - cur_hotx;
var y = cur_y - cur_hoty;
x *= display_scale;
y *= display_scale;
x *= display_scale / scaleFactor;
y *= display_scale / scaleFactor;
cursor_img.style.set {
left: x + "px",
top: y + "px",
@@ -401,13 +401,8 @@ handler.setCursorPosition = function(x, y) {
}
function self.ready() {
// https://sciter.com/forums/topic/focus_lost-and-focus_got-events/
// not got a good way to detect focus/blur in Sciter
// below not work until I click on toolbar on Mac
self.on("focus", "*", function() { stdout.println(this,"got focus") });
self.on("blur", "*", function() { stdout.println(this,"lost focus") });
var w = 960;
var h = 640;
var w = scaleIt(960);
var h = scaleIt(640);
if (is_file_transfer || is_port_forward) {
var r = handler.get_size();
if (isReasonableSize(r) && r[2] > 0) {