Merge branch 'master' into lan_discovery

This commit is contained in:
RustDesk
2022-01-10 17:34:51 +08:00
committed by GitHub
68 changed files with 3775 additions and 1605 deletions

290
src/ui/ab.tis Normal file
View File

@@ -0,0 +1,290 @@
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>;
var clear_icon = <svg viewBox="0 0 478.94 479.03"><path d="M217.488 478.45c-30.264-3.146-55.348-10.265-82.714-23.477C62.54 420.1 14.214 353.763 1.824 272.463c-2.412-15.82-2.434-50.027-.043-66.058 16.004-107.32 97.008-188.28 204.71-204.6 14.33-2.172 49.054-2.447 63-.498C323.95 8.915 371.3 32.2 409.03 69.927c37.697 37.698 61.125 85.349 68.605 139.54 1.943 14.08 1.68 48.804-.478 63-6.616 43.533-24.01 83.859-50.468 117-37.556 47.046-92.812 78.608-153.26 87.54-12.553 1.855-44.144 2.671-55.936 1.445zm42.144-32.045c15.649-1.602 29.895-4.63 44.856-9.531 78.146-25.604 133.49-94.718 141.94-177.26 6.245-60.993-16.1-123.3-59.94-167.14-55.797-55.797-139.4-75.365-213.52-49.98-77.69 26.609-131.51 94.14-140.42 176.19-4.761 43.843 6.392 91.899 30.274 130.44 41.468 66.926 119.01 105.26 196.82 97.29zm-138.69-80.346c-4.096-1.784-8.225-6.874-9.022-11.123-1.676-8.935-3.495-6.761 52.877-63.221l52.17-52.25-52.17-52.25c-56.544-56.632-54.56-54.249-52.834-63.451.924-4.923 6.905-10.904 11.828-11.828 9.201-1.726 6.819-3.71 63.451 52.834l52.25 52.169 52.25-52.169c56.632-56.544 54.25-54.56 63.451-52.834 4.923.923 10.904 6.905 11.828 11.828 1.726 9.201 3.71 6.818-52.834 63.451l-52.169 52.25 52.17 52.25c56.543 56.632 54.56 54.249 52.833 63.451-.923 4.923-6.905 10.904-11.828 11.828-9.201 1.726-6.818 3.71-63.455-52.838l-52.255-52.173-51.745 51.696c-28.496 28.469-53.01 52.166-54.56 52.742-3.766 1.4-8.515 1.26-12.234-.36z"/></svg>;
function getSessionsStyleOption(type) {
return (type || "recent") + "-sessions-style";
}
function getSessionsStyle(type) {
var v = handler.get_local_option(getSessionsStyleOption(type));
if (!v) v = type == "ab" ? "list" : "tile";
return v;
}
var searchPatterns = {};
class SearchBar: Reactor.Component {
this var type = "";
function this(params) {
this.type = (params || {}).type || "";
}
function render() {
var value = searchPatterns[this.type] || "";
var me = this;
self.timer(1ms, function() { me.search_id.value = value; });
return <div .search-id>
<span .search-icon>{search_icon}</span>
<input|text @{this.search_id} novalue={translate("Search ID")} />
{value && <span .clear-input>{clear_icon}</span>}
</div>;
}
event click $(span.clear-input) {
this.onChange('');
}
event change $(input) (_, el) {
this.onChange(el.value.trim());
}
function onChange(v) {
searchPatterns[this.type] = v;
app.multipleSessions.update();
}
}
class SessionStyle: Reactor.Component {
this var type = "";
function this(params) {
this.type = (params || {}).type || "";
}
function render() {
var sessionsStyle = getSessionsStyle(this.type);
return <div .sessions-tab style="margin-left: 0.5em;">
<span class={sessionsStyle == "tile" ? "active" : "inactive"}>{svg_tile}</span>
<span class={sessionsStyle != "tile" ? "active" : "inactive"}>{svg_list}</span>
</div>;
}
event click $(span.inactive) {
var option = getSessionsStyleOption(this.type);
var sessionsStyle = getSessionsStyle(this.type);
handler.set_option(option, sessionsStyle == "tile" ? "list" : "tile");
app.multipleSessions.update();
}
}
class SessionList: Reactor.Component {
this var sessions = [];
this var type = "";
this var style;
function this(params) {
this.sessions = params.sessions;
this.type = params.type || "";
this.style = getSessionsStyle(this.type);
}
function getSessions() {
var p = searchPatterns[this.type];
if (!p) return this.sessions;
var tmp = [];
this.sessions.map(function(s) {
var name = s[4] || s.alias || s[0] || s.id || "";
if (name.indexOf(p) >= 0) tmp.push(s);
});
return tmp;
}
function render() {
var sessions = this.getSessions();
if (sessions.length == 0) {
return <div style="margin: *; font-size: 1.6em;">{translate("Empty")}</div>;
}
var me = this;
sessions = sessions.map(function(x) { return me.getSession(x); });
return <div .recent-sessions-content key={sessions.length}>
<popup>
<menu.context #remote-context>
<li #connect>{translate('Connect')}</li>
<li #transfer>{translate('Transfer File')}</li>
<li #tunnel>{translate('TCP Tunneling')}</li>
<li #rdp>RDP<EditRdpPort /></li>
<div .separator />
<li #rename>{translate('Rename')}</li>
{this.type != "fav" && <li #remove>{translate('Remove')}</li>}
{is_win && <li #shortcut>{translate('Create Desktop Shortcut')}</li>}
<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>}
</menu>
</popup>
{sessions}
</div>;
}
function getSession(s) {
var id = s[0] || s.id || "";
var username = s[1] || s.username || "";
var hostname = s[2] || s.hostname || "";
var platform = s[3] || s.platform || "";
var alias = s[4] || s.alias || "";
if (this.style == "list") {
return <div .remote-session-link .remote-session-list id={id} platform={platform} title={alias ? "ID: " + id : ""}>
<div .platform style={"background:"+string2RGB(id+platform, 0.5)}>
{platform && platformSvg(platform, "white")}
</div>
<div .name>
<div>
<div #alias .ellipsis>{alias ? alias : formatId(id)}</div>
<div .username .ellipsis>{username}@{hostname}</div>
</div>
</div>
<div>
{svg_menu}
</div>
</div>;
}
return <div .remote-session-link .remote-session id={id} platform={platform} title={alias ? "ID: " + id : ""} style={"background:"+string2RGB(id+platform, 0.5)}>
<div .platform>
{platform && platformSvg(platform, "white")}
<div .username .ellipsis>{username}@{hostname}</div>
</div>
<div .text>
<div #alias .ellipsis>{alias ? alias : formatId(id)}</div>
{svg_menu}
</div>
</div>;
}
event dblclick $(div.remote-session-link) (evt, me) {
createNewConnect(me.id, "connect");
}
event click $(#menu) (_, me) {
var id = me.parent.parent.id;
var platform = me.parent.parent.attributes["platform"];
this.$(#rdp).style.set{
display: (platform == "Windows" && is_win) ? "block" : "none",
};
this.$(#forget-password).style.set{
display: handler.peer_has_password(id) ? "block" : "none",
};
if (!this.type || this.type == "fav") {
var in_fav = handler.get_fav().indexOf(id) >= 0;
this.$(#add-fav).style.set{
display: in_fav ? "none" : "block",
};
this.$(#remove-fav).style.set{
display: in_fav ? "block" : "none",
};
}
// https://sciter.com/forums/topic/replacecustomize-context-menu/
var menu = this.$(menu#remote-context);
menu.attributes["remote-id"] = id;
me.popup(menu);
}
event click $(menu#remote-context li) (evt, me) {
var action = me.id;
var id = me.parent.attributes["remote-id"];
if (action == "connect") {
createNewConnect(id, "connect");
} else if (action == "transfer") {
createNewConnect(id, "file-transfer");
} else if (action == "remove") {
if (!this.type) {
handler.remove_peer(id);
app.update();
}
} else if (action == "forget-password") {
handler.forget_password(id);
} else if (action == "shortcut") {
handler.create_shortcut(id);
} else if (action == "rdp") {
createNewConnect(id, "rdp");
} else if (action == "add-fav") {
var favs = handler.get_fav();
if (favs.indexOf(id) < 0) {
favs = [id].concat(favs);
handler.store_fav(favs);
}
app.multipleSessions.update();
app.update();
} else if (action == "remove-fav") {
var favs = handler.get_fav();
var i = favs.indexOf(id);
favs.splice(i, 1);
handler.store_fav(favs);
app.multipleSessions.update();
} else if (action == "tunnel") {
createNewConnect(id, "port-forward");
} else if (action == "rename") {
var old_name = handler.get_peer_option(id, "alias");
msgbox("custom-rename", "Rename", "<div .form> \
<div><input name='name' .outline-focus style='width: *; height: 23px', value='" + old_name + "' /></div> \
</div> \
", function(res=null) {
if (!res) return;
var name = (res.name || "").trim();
if (name != old_name) {
handler.set_peer_option(id, "alias", name);
}
app.update();
});
}
}
}
function getSessionsType() {
return handler.get_local_option("show-sessions-type");
}
class Favorites: Reactor.Component {
function render() {
var sessions = handler.get_fav().map(function(f) {
return handler.get_peer(f);
});
return <SessionList sessions={sessions} type="fav" />;
}
}
class MultipleSessions: Reactor.Component {
function render() {
var type = getSessionsType();
return <div style="size: *">
<div .sessions-bar>
<div style="width:*" .sessions-tab #sessions-type>
<span class={!type ? 'active' : 'inactive'}>{translate('Recent Sessions')}</span>
<span #fav class={type == "fav" ? 'active' : 'inactive'}>{translate('Favorites')}</span>
</div>
{!this.hidden && <SearchBar type={type} />}
{!this.hidden && <SessionStyle type={type} />}
</div>
{!this.hidden &&
((type == "fav" && <Favorites />) ||
<SessionList sessions={handler.get_recent_sessions()} />)}
</div>;
}
function stupidUpdate() {
/* hidden is workaround of stupid sciter bug */
this.hidden = true;
this.update();
var me = this;
self.timer(60ms, function() {
me.hidden = false;
me.update();
});
}
event click $(div#sessions-type span.inactive) (_, el) {
handler.set_option('show-sessions-type', el.id || "");
this.stupidUpdate();
}
function onSize() {
var w = this.$(.sessions-bar).box(#width) - 220;
this.$(#sessions-type span).style.set{
"max-width": (w / 2) + "px",
};
}
}
view.on("size", function() { if (app && app.multipleSessions) app.multipleSessions.onSize(); });

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);
@@ -405,12 +410,14 @@ async fn start_pa() {
break;
}
let spec = pulse::sample::Spec {
format: pulse::sample::Format::F32be,
format: pulse::sample::Format::F32le,
channels: 2,
rate: crate::platform::linux::PA_SAMPLE_RATE,
};
log::info!("pa monitor: {:?}", device);
if let Ok(s) = psimple::Simple::new(
// systemctl --user status pulseaudio.service
let mut buf: Vec<u8> = vec![0; 480 * 4];
match psimple::Simple::new(
None, // Use the default server
APP_NAME, // Our applications name
pulse::stream::Direction::Record, // We want a record stream
@@ -420,22 +427,19 @@ async fn start_pa() {
None, // Use default channel map
None, // Use default buffering attributes
) {
loop {
Ok(s) => loop {
if let Some(Err(_)) = stream.next_timeout2(1).await {
break;
}
let mut out: Vec<u8> = Vec::with_capacity(480 * 4);
unsafe {
out.set_len(out.capacity());
}
if let Ok(_) = s.read(&mut out) {
if out.iter().filter(|x| **x != 0).next().is_some() {
allow_err!(stream.send(&Data::RawMessage(out)).await);
}
if let Ok(_) = s.read(&mut buf) {
allow_err!(
stream.send(&Data::RawMessage(buf.clone())).await
);
}
},
Err(err) => {
log::error!("Could not create simple pulse: {}", err);
}
} else {
log::error!("Could not create simple pulse");
}
}
Err(err) => {

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,9 @@ class Body: Reactor.Component
connection.authorized = true;
body.update();
handler.authorize(cid);
self.timer(30ms, function() {
view.windowState = View.WINDOW_MINIMIZED;
});
});
}

View File

@@ -4,6 +4,7 @@ html {
var(gray-bg): #eee;
var(bg): white;
var(border): #ccc;
var(hover-border): #999;
var(text): #222;
var(placeholder): #aaa;
var(lighter-text): #888;
@@ -52,6 +53,10 @@ button.button:active, button.active {
border-color: color(accent);
}
button.button:hover, button.outline:hover {
border-color: color(hover-border);
}
input[type=text], input[type=password], input[type=number] {
width: *;
font-size: 1.5em;
@@ -156,9 +161,7 @@ header caption {
top: 0;
padding: 0 10px;
width: 22px;
height: *;
position: absolute;
margin: 0;
color: black;
border: none;
background: none;
@@ -321,5 +324,5 @@ menu li.selected span {
menu li.line-through {
text-decoration-line: line-through;
color: grey;
color: red;
}

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,19 @@ function getMsgboxParams() {
return msgbox_params;
}
function msgbox(type, title, text, callback, height, width, retry=0) {
// tmp workaround https://sciter.com/forums/topic/menu-not-be-hidden-when-open-dialog-on-linux/
function msgbox(type, title, text, callback=null, height=180, width=500, retry=0, contentStyle="") {
if (is_linux) { // fix menu not hidden issue
self.timer(1ms,
function() {
msgbox_(type, title, text, callback, height, width, retry, contentStyle);
});
} else {
msgbox_(type, title, text, callback, height, width, retry, contentStyle);
}
}
function msgbox_(type, title, text, callback, height, width, retry, contentStyle) {
var has_msgbox = msgbox_params != null;
if (!has_msgbox && !type) return;
var remember = false;
@@ -217,7 +238,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,8 +261,8 @@ 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();
handler.login(res.password, res.remember);
if (!is_port_forward) msgbox("connecting", "Connecting...", "Logging in...");
} else if (res.reconnect) {
if (!is_port_forward) connecting();
handler.reconnect();
@@ -251,19 +273,13 @@ 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) {
// 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); });
}
handler.block_msgbox = function(type, title, text, callback=null, height=180, width=500, retry=0) {
msgbox(type, title, text, callback, height, width, retry);
handler.msgbox = function(type, title, text, retry=0) {
self.timer(30ms, function() { msgbox(type, title, text, null, 180, 500, retry); });
}
var reconnectTimeout = 1;
handler.msgbox_retry = function(type, title, text, hasRetry, callback=null, height=180, width=500) {
handler.msgbox(type, title, text, callback, height, width, hasRetry ? reconnectTimeout : 0);
handler.msgbox_retry = function(type, title, text, hasRetry) {
handler.msgbox(type, title, text, hasRetry ? reconnectTimeout : 0);
if (hasRetry) {
reconnectTimeout *= 2;
} else {
@@ -311,3 +327,52 @@ 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 });
var me = this;
self.timer(30ms, function() {
var el = me.$(input);
view.focus = el;
el.value = value;
el.xcall(#setSelection, start, end);
});
}
}
function isReasonableSize(r) {
var x = r[0];
var y = r[1];
return !(x < -3200 || x > 3200 || y < -3200 || y > 3200);
}

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> \
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;
}
@@ -576,12 +576,12 @@ handler.jobDone = function(id, file_num = -1) {
handler.jobError = function(id, err, file_num = -1) {
var job = deleting_single_file_jobs[id];
if (job) {
handler.msgbox("custom-error", "Delete File", err);
msgbox("custom-error", "Delete File", err);
return;
}
job = create_dir_jobs[id];
if (job) {
handler.msgbox("custom-error", "Create Folder", err);
msgbox("custom-error", "Create Folder", err);
return;
}
if (file_num < 0) {
@@ -599,8 +599,8 @@ var deleting_single_file_jobs = {};
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> \
msgbox("custom-skip", "Confirm Delete", "<div .form> \
<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) {
@@ -619,11 +619,11 @@ handler.confirmDeleteFiles = function(id, i, name) {
if (i >= n) return;
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> \
msgbox("custom-skip", "Confirm Delete", "<div .form> \
<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

@@ -1,3 +1,12 @@
var last_key_time = 0;
var keymap = {};
for (var (k, v) in Event) {
k = k + ""
if (k[0] == "V" && k[1] == "K") {
keymap[v] = k;
}
}
class Grid: Behavior {
const TABLE_HEADER_CLICK = 0x81;
const TABLE_ROW_CLICK = 0x82;
@@ -144,7 +153,7 @@ class Grid: Behavior {
function onKey(evt)
{
last_key_time = getTime();
if (evt.type != Event.KEY_DOWN)
return false;

View File

@@ -42,6 +42,24 @@ header .remote-id {
margin: * 0;
}
header span:hover {
background: #f7f7f7;
}
@media platform != "OSX" {
header span:hover {
background: #d9d9d9;
}
}
header #screen:hover {
background: #d9d9d9;
}
header #secure:hover {
background: unset;
}
header span:active, header #screen:active {
color: black;
background: color(gray-bg);

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();
@@ -288,9 +277,9 @@ function handle_custom_image_quality() {
var tmp = handler.get_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> \
msgbox("custom", "Custom Image Quality", "<div .form> \
<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;
@@ -408,7 +397,7 @@ function startChat() {
height: h,
client: true,
parameters: { msgs: chat_msgs, callback: sendMsg, icon: icon },
caption: handler.get_id(),
caption: get_id(),
};
var html = handler.get_chatbox();
if (html) params.html = html;

View File

@@ -39,6 +39,77 @@ body {
padding: 20px;
}
div.sessions-bar {
color: color(light-text);
padding-top: 0.5em;
border-top: color(border) solid 1px;
margin-bottom: 1em;
position: relative;
flow: horizontal;
}
div.sessions-tab span {
display: inline-block;
padding: 6px 12px;
cursor: pointer;
text-overflow: ellipsis;
white-space: nowrap;
overflow-x: hidden;
}
div.sessions-tab svg {
size: 14px;
}
div.sessions-tab span.active {
cursor: default;
border-radius: 3px;
background: white;
color: black;
}
div.search-id {
width: 120px;
padding: 0;
position: relative;
display: inline-block;
}
div.search-id input {
font-size: 1em;
height: 20px;
border: none;
padding-left: 26px;
}
div.search-id span {
position: absolute;
top: 0px;
padding: 6px;
color: color(border);
}
div.search-id svg {
size: 14px;
}
span.search-icon {
left: 0px;
}
span.clear-input {
display: none;
right: 0px;
}
div.search-id:hover span.clear-input {
display: inline-block;
}
span.clear-input:hover {
color: black;
}
.your-desktop {
border-spacing: 0.5em;
border-left: color(accent) solid 2px;
@@ -132,13 +203,6 @@ div.recent-sessions-content {
flow: horizontal-flow;
}
div.recent-sessions-title {
color: color(light-text);
padding-top: 0.5em;
border-top: color(border) solid 1px;
margin-bottom: 1em;
}
div.remote-session {
border-radius: 1em;
height: 140px;
@@ -148,7 +212,7 @@ div.remote-session {
border: none;
}
div.remote-session:hover {
div.remote-session:hover, div.remote-session-list:hover {
outline: color(button) solid 2px -2px;
}
@@ -176,6 +240,41 @@ div.remote-session .platform svg {
background: none;
}
div.remote-session-list {
background: white;
width: 220px;
flow: horizontal;
}
div.remote-session-list .platform {
size: 42px;
}
div.remote-session-list .platform svg {
width: 30px;
height: 30px;
background: none;
padding: 6px;
}
div.remote-session-list .name {
size: *;
padding-left: 1em;
}
div.remote-session-list .name >div {
margin-top: *;
margin-bottom: *;
width: *;
}
div.remote-session-list .name .username {
margin-top: 3px;
font-size: 0.8em;
color: color(lighter-text);
overflow: hidden;
}
div.remote-session .text {
background: white;
position: absolute;
@@ -200,20 +299,22 @@ svg#menu {
color: color(light-text);
}
svg#menu:active {
.remote-session-list svg#menu {
margin-right: 0;
}
svg#menu:hover {
color: black;
border-radius: 1em;
background: color(gray-bg);
}
svg#edit:active {
opacity: 0.5;
svg#edit:hover {
color: black;
}
svg#edit {
display: inline-block;
margin-top: 0.25em;
margin-bottom: 0;
}
div.install-me, div.trust-me {

View File

@@ -7,6 +7,7 @@
</style>
<script type="text/tiscript">
include "common.tis";
include "ab.tis";
include "index.tis";
</script>
</head>

View File

@@ -18,112 +18,39 @@ 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", "");
}
}
class RecentSessions: Reactor.Component {
function render() {
var sessions = handler.get_recent_sessions();
if (sessions.length == 0) return <span />;
sessions = sessions.map(this.getSession);
return <div style="width: *">
<div .recent-sessions-title>RECENT SESSIONS</div>
{ false && <button .button #discover>DISCOVER</button>}
<div .recent-sessions-content key={sessions.length}>
{sessions}
</div>
</div>;
}
event click $(button#discover) {
handler.lan_discover();
}
function getSession(s) {
var id = s[0];
var username = s[1];
var hostname = s[2];
var platform = s[3];
var alias = s[4];
return <div .remote-session id={id} platform={platform} style={"background:"+string2RGB(id+platform, 0.5)}>
<div .platform>
{platformSvg(platform, "white")}
<div .username>{username}@{hostname}</div>
</div>
<div .text>
<div #alias>{alias ? alias : formatId(id)}</div>
{svg_menu}
</div>
</div>;
}
event dblclick $(div.remote-session) (evt, me) {
createNewConnect(me.id, "connect");
}
event click $(#menu) (_, me) {
var id = me.parent.parent.id;
var platform = me.parent.parent.attributes["platform"];
$(#rdp).style.set{
display: (platform == "Windows" && is_win) ? "block" : "none",
};
// https://sciter.com/forums/topic/replacecustomize-context-menu/
var menu = $(menu#remote-context);
menu.attributes["remote-id"] = id;
me.popup(menu);
}
}
event click $(menu#remote-context li) (evt, me) {
var action = me.id;
var id = me.parent.attributes["remote-id"];
if (action == "connect") {
createNewConnect(id, "connect");
} else if (action == "transfer") {
createNewConnect(id, "file-transfer");
} else if (action == "remove") {
handler.remove_peer(id);
app.recent_sessions.update();
} else if (action == "shortcut") {
handler.create_shortcut(id);
} else if (action == "rdp") {
createNewConnect(id, "rdp");
} else if (action == "tunnel") {
createNewConnect(id, "port-forward");
} else if (action == "rename") {
var old_name = handler.get_peer_option(id, "alias");
handler.msgbox("custom-rename", "Rename", "<div .form> \
<div><input name='name' style='width: *; height: 23px', value='" + old_name + "' /></div> \
</div> \
", function(res=null) {
if (!res) return;
var name = (res.name || "").trim();
if (name != old_name) handler.set_peer_option(id, "alias", name);
self.select('#' + id).select('#alias').text = name || id;
});
event click $(#start-service) () {
handler.set_option("stop-service", "");
}
}
@@ -131,14 +58,32 @@ function createNewConnect(id, type) {
id = id.replace(/\s/g, "");
app.remote_id.value = formatId(id);
if (!id) return;
if (id == handler.get_id()) {
handler.msgbox("custom-error", "Error", "You cannot connect to your own computer");
if (id == my_id) {
msgbox("custom-error", "Error", "You cannot connect to your own computer");
return;
}
handler.set_remote_id(id);
handler.new_remote(id, type);
}
var direct_server;
class DirectServer: Reactor.Component {
function this() {
direct_server = this;
}
function render() {
var text = translate("Enable Direct IP Access");
var cls = handler.get_option("direct-server") == "Y" ? "selected" : "line-through";
return <li class={cls}><span>{svg_checkmark}</span>{text}</li>;
}
function onClick() {
handler.set_option("direct-server", handler.get_option("direct-server") == "Y" ? "" : "Y");
this.update();
}
}
var myIdMenu;
var audioInputMenu;
class AudioInputs: Reactor.Component {
@@ -154,10 +99,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>;
@@ -195,7 +140,6 @@ class MyIdMenu: Reactor.Component {
}
function render() {
var me = this;
return <div #myid>
{this.renderPop()}
ID{svg_menu}
@@ -205,19 +149,20 @@ 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>
<li #socks5-server>{translate('Socks5 Proxy')}</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>
<DirectServer />
<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>;
}
@@ -225,6 +170,7 @@ class MyIdMenu: Reactor.Component {
event click $(svg#menu) (_, me) {
audioInputMenu.update({ show: true });
this.toggleMenuState();
if (direct_server) direct_server.update();
var menu = $(menu#config-options);
me.popup(menu);
}
@@ -245,8 +191,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>\
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;
@@ -255,7 +202,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");
@@ -265,12 +212,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> \
msgbox("custom-server", "ID/Relay Server", "<div .form> \
<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;
@@ -279,26 +226,46 @@ 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 == "socks5-server") {
var socks5 = handler.get_socks() || {};
var old_proxy = socks5[0] || "";
var old_username = socks5[1] || "";
var old_password = socks5[2] || "";
msgbox("custom-server", "Socks5 Proxy", <div .form .set-password>
<div><span>{translate("Hostname")}</span><input .outline-focus style='width: *' name='proxy' value={old_proxy} /></div>
<div><span>{translate("Username")}</span><input style='width: *' name='username' value={old_username} /></div>
<div><span>{translate("Password")}</span><PasswordComponent value={old_password} /></div>
</div>
, function(res=null) {
if (!res) return;
var proxy = (res.proxy || "").trim();
var username = (res.username || "").trim();
var password = (res.password || "").trim();
if (proxy == old_proxy && username == old_username && password == old_password) return;
if (proxy) {
var err = handler.test_if_valid_server(proxy);
if (err) return translate("Server") + ": " + err;
}
handler.set_socks(proxy, username, password);
}, 240);
} else if (me.id == "stop-service") {
handler.set_option("stop-service", service_stopped ? "" : "Y");
} else if (me.id == "about") {
var name = handler.get_app_name();
handler.msgbox("custom-nocancel-nook-hasclose", "About " + name, "<div style='line-height: 2em'> \
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>\
@@ -322,37 +289,24 @@ class App: Reactor.Component
var is_can_screen_recording = handler.is_can_screen_recording(false);
return
<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>}
</menu>
</popup>
<popup>
<menu.context #edit-password-context>
<li #refresh-password>Refresh random password</li>
<li #set-password>Set your own password</li>
</menu>
</popup>
<div .left-pane>
<popup><menu.context #edit-password-context>
<li #refresh-password>{translate('Refresh random password')}</li>
<li #set-password>{translate('Set your own password')}</li>
</menu></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 />}
@@ -364,14 +318,14 @@ 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} />
<MultipleSessions @{this.multipleSessions} />
</div>
<ConnectStatus @{this.connect_status} />
</div>
@@ -391,11 +345,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>
<div style="text-align: center; margin-top: 1em;"><button #install-me .button>{translate('Install')}</button></div>
</div>;
}
@@ -450,9 +405,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>;
}
@@ -463,9 +418,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;" />
@@ -473,18 +428,20 @@ 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) {
handler.msgbox("custom-error", "Download Error", "Failed to download");
msgbox("custom-error", "Download Error", "Failed to download");
};
var onprogress = function(loaded, total) {
if (!total) total = 5 * 1024 * 1024;
@@ -511,9 +468,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>;
}
@@ -526,9 +483,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>;
}
@@ -541,9 +498,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>;
}
@@ -556,15 +514,16 @@ 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>;
}
event click $(#modify-default-login) {
if (var r = handler.modify_default_login()) {
handler.msgbox("custom-error", "Error", r);
msgbox("custom-error", "Error", r);
}
app.update();
}
@@ -627,19 +586,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> \
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();
@@ -649,7 +608,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())} />;
}
@@ -713,10 +672,10 @@ function self.closing() {
function self.ready() {
var r = handler.get_size();
if (r[2] == 0) {
centerize(800, 600);
} else {
if (isReasonableSize(r) && r[2] > 0) {
view.move(r[0], r[1], r[2], r[3]);
} else {
centerize(800, 600);
}
if (!handler.get_remote_id()) {
view.focus = $(#remote_id);
@@ -740,6 +699,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;
@@ -752,7 +715,7 @@ function checkConnectStatus() {
}
if (handler.recent_sessions_updated()) {
stdout.println("recent sessions updated");
app.recent_sessions.update();
app.update();
}
checkConnectStatus();
});

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

@@ -49,13 +49,16 @@ fn get_key_state(key: enigo::Key) -> bool {
ENIGO.lock().unwrap().get_key_state(key)
}
static mut IS_IN: bool = false;
static mut KEYBOARD_HOOKED: bool = false;
static mut KEYBOARD_ENABLED: bool = true;
#[derive(Default)]
pub struct HandlerInner {
element: Option<Element>,
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)]
@@ -65,7 +68,6 @@ pub struct Handler {
id: String,
args: Vec<String>,
lc: Arc<RwLock<LoginConfigHandler>>,
super_on: bool,
}
impl Deref for Handler {
@@ -147,6 +149,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();
@@ -154,11 +158,9 @@ impl sciter::EventHandler for Handler {
fn login(String, bool);
fn new_rdp();
fn send_mouse(i32, i32, i32, bool, bool, bool, bool);
fn key_down_or_up(bool, String, i32, bool, bool, bool, bool, bool);
fn enter();
fn leave();
fn ctrl_alt_del();
fn ctrl_space();
fn alt_tab();
fn super_x();
fn transfer_file();
fn tunnel();
fn lock_screen();
@@ -218,6 +220,139 @@ impl Handler {
me
}
fn start_keyboard_hook(&self) {
if self.is_port_forward() || self.is_file_transfer() {
return;
}
if unsafe { KEYBOARD_HOOKED } {
return;
}
unsafe {
KEYBOARD_HOOKED = true;
}
log::info!("keyboard hooked");
let mut me = self.clone();
let peer = self.peer_platform();
let is_win = peer == "Windows";
std::thread::spawn(move || {
// This will block.
std::env::set_var("KEYBOARD_ONLY", "y"); // pass to rdev
use rdev::{EventType::*, *};
let func = move |evt: Event| {
if unsafe { !IS_IN || !KEYBOARD_ENABLED } {
return;
}
let (key, down) = match evt.event_type {
KeyPress(k) => (k, 1),
KeyRelease(k) => (k, 0),
_ => return,
};
let alt = get_key_state(enigo::Key::Alt);
let ctrl = get_key_state(enigo::Key::Control);
let shift = get_key_state(enigo::Key::Shift);
let command = get_key_state(enigo::Key::Meta);
let control_key = match key {
Key::Alt => Some(ControlKey::Alt),
Key::AltGr => Some(ControlKey::RAlt),
Key::Backspace => Some(ControlKey::Backspace),
Key::ControlLeft => Some(ControlKey::Control),
Key::ControlRight => Some(ControlKey::RControl),
Key::DownArrow => Some(ControlKey::DownArrow),
Key::Escape => Some(ControlKey::Escape),
Key::F1 => Some(ControlKey::F1),
Key::F10 => Some(ControlKey::F10),
Key::F11 => Some(ControlKey::F11),
Key::F12 => Some(ControlKey::F12),
Key::F2 => Some(ControlKey::F2),
Key::F3 => Some(ControlKey::F3),
Key::F4 => Some(ControlKey::F4),
Key::F5 => Some(ControlKey::F5),
Key::F6 => Some(ControlKey::F6),
Key::F7 => Some(ControlKey::F7),
Key::F8 => Some(ControlKey::F8),
Key::F9 => Some(ControlKey::F9),
Key::LeftArrow => Some(ControlKey::LeftArrow),
Key::MetaLeft => Some(ControlKey::Meta),
Key::MetaRight => Some(ControlKey::RWin),
Key::Return => Some(ControlKey::Return),
Key::RightArrow => Some(ControlKey::RightArrow),
Key::ShiftLeft => Some(ControlKey::Shift),
Key::ShiftRight => Some(ControlKey::RShift),
Key::Space => Some(ControlKey::Space),
Key::Tab => Some(ControlKey::Tab),
Key::UpArrow => Some(ControlKey::UpArrow),
Key::Delete => {
if is_win && ctrl && alt {
me.ctrl_alt_del();
return;
}
Some(ControlKey::Delete)
}
Key::Apps => Some(ControlKey::Apps),
Key::Cancel => Some(ControlKey::Cancel),
Key::Clear => Some(ControlKey::Clear),
Key::Kana => Some(ControlKey::Kana),
Key::Hangul => Some(ControlKey::Hangul),
Key::Junja => Some(ControlKey::Junja),
Key::Final => Some(ControlKey::Final),
Key::Hanja => Some(ControlKey::Hanja),
Key::Hanji => Some(ControlKey::Hanja),
Key::Convert => Some(ControlKey::Convert),
Key::Print => Some(ControlKey::Print),
Key::Select => Some(ControlKey::Select),
Key::Execute => Some(ControlKey::Execute),
Key::PrintScreen => Some(ControlKey::Snapshot),
Key::Help => Some(ControlKey::Help),
Key::Sleep => Some(ControlKey::Sleep),
Key::Separator => Some(ControlKey::Separator),
Key::KpReturn => Some(ControlKey::NumpadEnter),
Key::Kp0 => Some(ControlKey::Numpad0),
Key::Kp1 => Some(ControlKey::Numpad1),
Key::Kp2 => Some(ControlKey::Numpad2),
Key::Kp3 => Some(ControlKey::Numpad3),
Key::Kp4 => Some(ControlKey::Numpad4),
Key::Kp5 => Some(ControlKey::Numpad5),
Key::Kp6 => Some(ControlKey::Numpad6),
Key::Kp7 => Some(ControlKey::Numpad7),
Key::Kp8 => Some(ControlKey::Numpad8),
Key::Kp9 => Some(ControlKey::Numpad9),
Key::KpDivide => Some(ControlKey::Divide),
Key::KpMultiply => Some(ControlKey::Subtract),
Key::KpDecimal => Some(ControlKey::Decimal),
Key::KpMinus => Some(ControlKey::Subtract),
Key::KpPlus => Some(ControlKey::Add),
Key::CapsLock | Key::NumLock | Key::ScrollLock => {
return;
}
_ => None,
};
let mut key_event = KeyEvent::new();
if let Some(k) = control_key {
key_event.set_control_key(k);
} else {
let chr = match evt.name {
Some(ref s) => s.chars().next().unwrap_or('\0'),
_ => '\0',
};
if chr != '\0' {
if chr == 'l' && is_win && command {
me.lock_screen();
return;
}
key_event.set_chr(chr as _);
} else {
log::error!("Unknown key {:?}", evt);
return;
}
}
me.key_down_or_up(down, key_event, alt, ctrl, shift, command);
};
if let Err(error) = rdev::listen(func) {
log::error!("rdev: {:?}", error);
}
});
}
fn get_view_style(&mut self) -> String {
return self.lc.read().unwrap().view_style.clone();
}
@@ -287,6 +422,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()
}
@@ -409,6 +548,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);
}
@@ -535,7 +678,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);
@@ -629,6 +771,18 @@ impl Handler {
self.send(Data::NewRDP);
}
fn enter(&mut self) {
unsafe {
IS_IN = true;
}
}
fn leave(&mut self) {
unsafe {
IS_IN = false;
}
}
fn send_mouse(
&mut self,
mask: i32,
@@ -668,7 +822,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;
}
}
}
@@ -754,6 +907,7 @@ impl Handler {
65300 => key_event.set_control_key(ControlKey::Scroll),
65421 => key_event.set_control_key(ControlKey::NumpadEnter), // numpad enter
65407 => key_event.set_control_key(ControlKey::NumLock),
65515 => key_event.set_control_key(ControlKey::Meta),
65516 => key_event.set_control_key(ControlKey::RWin),
65513 => key_event.set_control_key(ControlKey::Alt),
65514 => key_event.set_control_key(ControlKey::RAlt),
@@ -811,32 +965,20 @@ impl Handler {
fn ctrl_alt_del(&mut self) {
if self.peer_platform() == "Windows" {
let del = "CTRL_ALT_DEL".to_owned();
self.key_down_or_up(1, del, 0, false, false, false, false, false);
let mut key_event = KeyEvent::new();
key_event.set_control_key(ControlKey::CtrlAltDel);
self.key_down_or_up(1, key_event, false, false, false, false);
} else {
let del = "VK_DELETE".to_owned();
self.key_down_or_up(1, del.clone(), 0, true, true, false, false, false);
self.key_down_or_up(0, del, 0, true, true, false, false, false);
let mut key_event = KeyEvent::new();
key_event.set_control_key(ControlKey::Delete);
self.key_down_or_up(3, key_event, true, true, false, false);
}
}
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);
let mut key_event = KeyEvent::new();
key_event.set_control_key(ControlKey::LockScreen);
self.key_down_or_up(1, key_event, false, false, false, false);
}
fn transfer_file(&mut self) {
@@ -858,104 +1000,55 @@ impl Handler {
fn key_down_or_up(
&mut self,
down_or_up: i32,
name: String,
code: i32,
evt: KeyEvent,
alt: bool,
ctrl: bool,
shift: bool,
command: bool,
extended: bool,
) {
// 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!(
"{:?} {} {} {} {} {} {} {} {}",
std::time::SystemTime::now(),
down_or_up,
name,
code,
alt,
ctrl,
shift,
command,
extended,
);
let mut key_event = evt;
let mut command = command;
if self.super_on {
command = true;
if alt
&& !crate::is_control_key(&key_event, &ControlKey::Alt)
&& !crate::is_control_key(&key_event, &ControlKey::RAlt)
{
key_event.modifiers.push(ControlKey::Alt.into());
}
if down_or_up == 0 {
self.super_on = false;
if shift
&& !crate::is_control_key(&key_event, &ControlKey::Shift)
&& !crate::is_control_key(&key_event, &ControlKey::RShift)
{
key_event.modifiers.push(ControlKey::Shift.into());
}
let mut name = name;
#[cfg(target_os = "linux")]
if code == 65383 {
// VK_MENU
name = "Apps".to_owned();
if ctrl
&& !crate::is_control_key(&key_event, &ControlKey::Control)
&& !crate::is_control_key(&key_event, &ControlKey::RControl)
{
key_event.modifiers.push(ControlKey::Control.into());
}
if extended {
match name.as_ref() {
"VK_CONTROL" => name = "RControl".to_owned(),
"VK_MENU" => name = "RAlt".to_owned(),
"VK_SHIFT" => name = "RShift".to_owned(),
_ => {}
if command
&& !crate::is_control_key(&key_event, &ControlKey::Meta)
&& !crate::is_control_key(&key_event, &ControlKey::RWin)
{
key_event.modifiers.push(ControlKey::Meta.into());
}
if get_key_state(enigo::Key::CapsLock) {
key_event.modifiers.push(ControlKey::CapsLock.into());
}
if self.peer_platform() != "Mac OS" {
if get_key_state(enigo::Key::NumLock) && common::valid_for_numlock(&key_event) {
key_event.modifiers.push(ControlKey::NumLock.into());
}
}
if let Some(mut key_event) = self.get_key_event(down_or_up, &name, code) {
if alt
&& !crate::is_control_key(&key_event, &ControlKey::Alt)
&& !crate::is_control_key(&key_event, &ControlKey::RAlt)
{
key_event.modifiers.push(ControlKey::Alt.into());
}
if shift
&& !crate::is_control_key(&key_event, &ControlKey::Shift)
&& !crate::is_control_key(&key_event, &ControlKey::RShift)
{
key_event.modifiers.push(ControlKey::Shift.into());
}
if ctrl
&& !crate::is_control_key(&key_event, &ControlKey::Control)
&& !crate::is_control_key(&key_event, &ControlKey::RControl)
{
key_event.modifiers.push(ControlKey::Control.into());
}
if command
&& !crate::is_control_key(&key_event, &ControlKey::Meta)
&& !crate::is_control_key(&key_event, &ControlKey::RWin)
{
key_event.modifiers.push(ControlKey::Meta.into());
}
if crate::is_control_key(&key_event, &ControlKey::CapsLock) {
return;
} else if get_key_state(enigo::Key::CapsLock) && common::valid_for_capslock(&key_event)
{
key_event.modifiers.push(ControlKey::CapsLock.into());
}
if self.peer_platform() != "Mac OS" {
if crate::is_control_key(&key_event, &ControlKey::NumLock) {
return;
} else if get_key_state(enigo::Key::NumLock)
&& common::valid_for_numlock(&key_event)
{
key_event.modifiers.push(ControlKey::NumLock.into());
}
}
if down_or_up == 1 {
key_event.down = true;
} else if down_or_up == 3 {
key_event.press = true;
}
let mut msg_out = Message::new();
msg_out.set_key_event(key_event);
log::debug!("{:?}", msg_out);
self.send(Data::Message(msg_out));
if down_or_up == 1 {
key_event.down = true;
} else if down_or_up == 3 {
key_event.press = true;
}
let mut msg_out = Message::new();
msg_out.set_key_event(key_event);
log::debug!("{:?}", msg_out);
self.send(Data::Message(msg_out));
}
#[inline]
@@ -1132,6 +1225,9 @@ impl Remote {
};
match Client::start(&self.handler.id, conn_type).await {
Ok((mut peer, direct)) => {
unsafe {
KEYBOARD_ENABLED = true;
}
self.handler
.call("setConnectionType", &make_args!(peer.is_secured(), direct));
loop {
@@ -1191,6 +1287,9 @@ impl Remote {
if let Some(stop) = stop_clipboard {
stop.send(()).ok();
}
unsafe {
KEYBOARD_ENABLED = false;
}
}
fn handle_job_status(&mut self, id: i32, file_num: i32, err: Option<String>) {
@@ -1586,6 +1685,9 @@ impl Remote {
log::info!("Change permission {:?} -> {}", p.permission, p.enabled);
match p.permission.enum_value_or_default() {
Permission::Keyboard => {
unsafe {
KEYBOARD_ENABLED = p.enabled;
}
self.handler
.call("setPermission", &make_args!("keyboard", p.enabled));
}
@@ -1738,6 +1840,7 @@ impl Interface for Handler {
crate::platform::windows::add_recent_document(&path);
}
}
self.start_keyboard_hook();
}
async fn handle_hash(&mut self, hash: Hash, peer: &mut Stream) {

View File

@@ -1,5 +1,4 @@
var cursor_img = $(img#cursor);
var last_key_time = 0;
is_file_transfer = handler.is_file_transfer();
var is_port_forward = handler.is_port_forward();
var display_width = 0;
@@ -72,46 +71,14 @@ function adaptDisplay() {
// https://sciter.com/docs/content/sciter/Event.htm
var entered = false;
var keymap = {};
for (var (k, v) in Event) {
k = k + ""
if (k[0] == "V" && k[1] == "K") {
keymap[v] = k;
if (!is_file_transfer && !is_port_forward) {
self.onKey = function(evt) {
if (!entered) return false;
// so that arrow key not move scrollbar
return true;
}
}
// VK_ENTER = VK_RETURN
// somehow, handler.onKey and view.onKey not working
function self.onKey(evt) {
last_key_time = getTime();
if (is_file_transfer || is_port_forward) return false;
if (!entered) return false;
if (!keyboard_enabled) return false;
switch (evt.type) {
case Event.KEY_DOWN:
handler.key_down_or_up(1, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey,
evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey);
if (is_osx && evt.commandKey) {
handler.key_down_or_up(0, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey,
evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey);
}
break;
case Event.KEY_UP:
handler.key_down_or_up(0, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey,
evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey);
break;
case Event.KEY_CHAR:
// the keypress event is fired when the element receives character value. Event.keyCode is a UNICODE code point of the character
handler.key_down_or_up(2, "", evt.keyCode, evt.altKey,
evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey);
break;
default:
return false;
}
return true;
}
var wait_window_toolbar = false;
var last_mouse_mask;
var acc_wheel_delta_x = 0;
@@ -173,10 +140,14 @@ function handler.onMouse(evt)
wait_window_toolbar = true;
self.timer(300ms, function() {
if (!wait_window_toolbar) return;
var extra = 0;
// workaround for stupid Sciter, without this, click
// event not triggered on top part of buttons on toolbar
if (is_osx) extra = 10;
if (view.windowState == View.WINDOW_FULL_SCREEN) {
$(header).style.set {
display: "block",
padding: (2 * workarea_offset) + "px 0 0 0",
padding: (2 * workarea_offset + extra) + "px 0 0 0",
};
}
wait_window_toolbar = false;
@@ -280,10 +251,12 @@ function handler.onMouse(evt)
case Event.MOUSE_ENTER:
entered = true;
stdout.println("enter");
handler.enter();
return keyboard_enabled;
case Event.MOUSE_LEAVE:
entered = false;
stdout.println("leave");
handler.leave();
return keyboard_enabled;
default:
return false;
@@ -396,7 +369,7 @@ function self.ready() {
var h = 640;
if (is_file_transfer || is_port_forward) {
var r = handler.get_size();
if (r[0] > 0) {
if (isReasonableSize(r) && r[2] > 0) {
view.move(r[0], r[1], r[2], r[3]);
} else {
centerize(w, h);
@@ -418,7 +391,7 @@ handler.adaptSize = function() {
var (fx, fy, fw, fh) = view.screenBox(#frame, #rectw);
if (is_osx) workarea_offset = sy;
var r = handler.get_size();
if (r[2] > 0) {
if (isReasonableSize(r) && r[2] > 0) {
if (r[2] >= fw && r[3] >= fh && !is_linux) {
view.windowState = View.WINDOW_FULL_SCREEN;
stdout.println("Initialize to full screen");