mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
100% open source
This commit is contained in:
411
src/ui/ab.tis
411
src/ui/ab.tis
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
27
src/ui/cm.rs
27
src/ui/cm.rs
@@ -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 application’s name
|
||||
&crate::get_app_name(), // Our application’s name
|
||||
pulse::stream::Direction::Record, // We want a record stream
|
||||
Some(&device), // Use the default device
|
||||
"record", // Description of our stream
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
324
src/ui/index.tis
324
src/ui/index.tis
@@ -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 © 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");
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
div.content {
|
||||
size: *;
|
||||
background: white;
|
||||
padding:2em 10em;
|
||||
padding:2em 8em;
|
||||
border-spacing: 1em;
|
||||
}
|
||||
input {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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") {
|
||||
|
||||
226
src/ui/remote.rs
226
src/ui/remote.rs
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user