mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
Merge remote-tracking branch 'rustdesk/master' into flutter_desktop
# Conflicts: # .github/workflows/ci.yml # Cargo.lock # Cargo.toml # flutter/lib/common.dart # flutter/lib/mobile/pages/remote_page.dart # flutter/lib/mobile/pages/server_page.dart # flutter/lib/mobile/pages/settings_page.dart # flutter/lib/mobile/widgets/dialog.dart # flutter/lib/models/model.dart # flutter/lib/models/server_model.dart # src/client.rs # src/common.rs # src/ipc.rs # src/mobile_ffi.rs # src/rendezvous_mediator.rs # src/ui.rs
This commit is contained in:
@@ -318,9 +318,10 @@ class SessionList: Reactor.Component {
|
||||
<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>
|
||||
<li #wol>{translate('WOL')}</li>
|
||||
<div .separator />
|
||||
<li #rename>{translate('Rename')}</li>
|
||||
{this.type != "fav" && this.type != "lan" && <li #remove>{translate('Remove')}</li>}
|
||||
{this.type != "lan" && <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>}
|
||||
@@ -419,6 +420,8 @@ class SessionList: Reactor.Component {
|
||||
createNewConnect(id, "connect");
|
||||
} else if (action == "transfer") {
|
||||
createNewConnect(id, "file-transfer");
|
||||
} else if (action == "wol") {
|
||||
handler.send_wol(id);
|
||||
} else if (action == "remove") {
|
||||
if (this.type == "ab") {
|
||||
for (var i = 0; i < ab.peers.length; ++i) {
|
||||
@@ -429,6 +432,9 @@ class SessionList: Reactor.Component {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (this.type == "lan") {
|
||||
handler.remove_discovered(id);
|
||||
app.update();
|
||||
} else {
|
||||
handler.remove_peer(id);
|
||||
app.update();
|
||||
|
||||
@@ -104,6 +104,10 @@ icon.file {
|
||||
background:url('data: image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAUVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////8IN+deAAAAGnRSTlMAH+CAESEN8jyZkcIb5N/ONy3vmHhmiGjUm7UwS+YAAAHZSURBVGje7dnbboMwDIBhBwgQoFAO7Ta//4NOqCAXYZQstatq4r+r5ubrgQSpg8iyC4ZURa+PlIpQYGiwrzyeHtYZjAL8T05O4H8BbbKvFgRa4NoBU8pXeYEkDDgaaLQBcwJrmeErJQB/7wes3QBWGnCIX0+AQycL1PO6BMwPa0nA4ZxbgTvOjUYMGPHRnZkQAY4mxPZBjmy53E7ukSkFKYB/D4XsWZQx64sCeYebOogGsoOBYvv6/UCb8F0IOBZ0TlP6lEYdANY350AJqB9/qPVuOI5evw4A1hgLigAlepnyxW80bcCcwN++A2s82Vcu02ta+ceq9BoL5KGTTRwQPlpqA3gCnwWU2kCDgeWRQPj2jAPCDxgCMjhI6uZnToDpvd/BJeFrJQB/fsAa02gCt3mi1wNuy8GgBNDZlysBNNSrADVSjcJl6vCpUn6jOdx0kz0q6PMhQRa4465SFKhx35cgUCBTwj2/NHwZAb71qR8GEP2H1XcmAtBPTEO67GP6FUUAIKGABbDLQ0EArhN2sAIGesRO+iyy+RMAjckVTlMCKFVAbh/4Af9OPgG61SkDVco3BQGT3GXaDAnTIAcYZDuBTwGsAGDxuBFeAQqIqwoFMlAVLrHr/wId5MPt0nilGgAAAABJRU5ErkJggg==');
|
||||
}
|
||||
|
||||
icon.restart {
|
||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAB7BAAAewQHDaVRTAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAbhJREFUWIXVlrFqFGEUhb+7UYxaWCQKlrKKxaZSQVGDJih2tj6MD2DnMwiWvoAIRnENIpZiYxEro6IooiS7SPwsMgNLkk3mjmYmnmb45/73nMNwz/x/qH3gMu2gH6rAU+Blw+Lngau4jpmGxVF7qp1iPWjaQKnZ2WnXbuP/NqAeUPc3ZkA9XDwvqc+BVWCgPlJ7tRwUKThZce819b46VH+pfXVRXVO/q2cSul3VOgZUl0ejq86r39TXI8mqZKDuDEwCw3IREQvAbWAGmMsQZQ0sAl3gHPB1Q+0e8BuYzRDuy2yOiFVgaUxtRf0ETGc4syk4rc6PqU0Cx9j8Zf6dAeAK8Fi9sUXtFjABvEgxJlNwRP2svlNPjbw/q35U36oTFbnyMSwabxb/gB/qA3VBHagrauV7RW0DRfP1IvMlXqkXkhz1DYyQTKtHa/Z2VVMx3IiI+PI3/bCHjuOpFrSnAMpL6QfgTcMGesDx0kBr2BMzsNyi/vtQu8CJlgwsRbZDnWP90NkKaxHxJMOXMqAeAn5u0ydwMCKGY+qbkB3C2W3EKWoXk5zVoHbUZ+6Mh7tl4G4F8RJ3qvL+AfV3r5Vdpj70AAAAAElFTkSuQmCC');
|
||||
}
|
||||
|
||||
div.buttons {
|
||||
width: *;
|
||||
border-spacing: 0.5em;
|
||||
|
||||
14
src/ui/cm.rs
14
src/ui/cm.rs
@@ -14,7 +14,7 @@ use hbb_common::{
|
||||
fs, get_version_number, log,
|
||||
message_proto::*,
|
||||
protobuf::Message as _,
|
||||
tokio::{self, sync::mpsc, task::spawn_blocking}
|
||||
tokio::{self, sync::mpsc, task::spawn_blocking},
|
||||
};
|
||||
use sciter::{make_args, Element, Value, HELEMENT};
|
||||
use std::{
|
||||
@@ -90,6 +90,7 @@ impl ConnectionManager {
|
||||
clipboard: bool,
|
||||
audio: bool,
|
||||
file: bool,
|
||||
restart: bool,
|
||||
tx: mpsc::UnboundedSender<Data>,
|
||||
) {
|
||||
self.call(
|
||||
@@ -104,7 +105,8 @@ impl ConnectionManager {
|
||||
keyboard,
|
||||
clipboard,
|
||||
audio,
|
||||
file
|
||||
file,
|
||||
restart
|
||||
),
|
||||
);
|
||||
self.write().unwrap().senders.insert(id, tx);
|
||||
@@ -204,7 +206,7 @@ impl ConnectionManager {
|
||||
let mut req = FileTransferSendConfirmRequest {
|
||||
id,
|
||||
file_num,
|
||||
union: Some(file_transfer_send_confirm_request::Union::offset_blk(0)),
|
||||
union: Some(file_transfer_send_confirm_request::Union::OffsetBlk(0)),
|
||||
..Default::default()
|
||||
};
|
||||
let digest = FileTransferDigest {
|
||||
@@ -489,11 +491,11 @@ async fn start_ipc(cm: ConnectionManager) {
|
||||
}
|
||||
Ok(Some(data)) => {
|
||||
match data {
|
||||
Data::Login{id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, file_transfer_enabled} => {
|
||||
Data::Login{id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, file_transfer_enabled, restart} => {
|
||||
log::debug!("conn_id: {}", id);
|
||||
conn_id = id;
|
||||
tx_file.send(ClipboardFileData::Enable((id, file_transfer_enabled))).ok();
|
||||
cm.add_connection(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, tx.clone());
|
||||
cm.add_connection(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart, tx.clone());
|
||||
}
|
||||
Data::Close => {
|
||||
tx_file.send(ClipboardFileData::Enable((conn_id, false))).ok();
|
||||
@@ -590,7 +592,7 @@ async fn start_pa() {
|
||||
} else {
|
||||
buf.clone()
|
||||
};
|
||||
if let Err(err) = stream.send_raw(out).await {
|
||||
if let Err(err) = stream.send_raw(out.into()).await {
|
||||
log::error!("Failed to send audio data:{}", err);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ class Body: Reactor.Component
|
||||
<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 class={!c.file ? "disabled" : ""} title={translate('Allow file copy and paste')}><icon .file /></div>
|
||||
<div class={!c.restart ? "disabled" : ""} title={translate('Allow remote restart')}><icon .restart /></div>
|
||||
</div>}
|
||||
{c.port_forward ? <div>Port Forwarding: {c.port_forward}</div> : ""}
|
||||
<div style="size:*"/>
|
||||
@@ -108,6 +109,15 @@ class Body: Reactor.Component
|
||||
});
|
||||
}
|
||||
|
||||
event click $(icon.restart) {
|
||||
var { cid, connection } = this;
|
||||
checkClickTime(function() {
|
||||
connection.restart = !connection.restart;
|
||||
body.update();
|
||||
handler.switch_permission(cid, "restart", connection.restart);
|
||||
});
|
||||
}
|
||||
|
||||
event click $(button#accept) {
|
||||
var { cid, connection } = this;
|
||||
checkClickTime(function() {
|
||||
@@ -266,7 +276,7 @@ function bring_to_top(idx=-1) {
|
||||
}
|
||||
}
|
||||
|
||||
handler.addConnection = function(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file) {
|
||||
handler.addConnection = function(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart) {
|
||||
stdout.println("new connection #" + id + ": " + peer_id);
|
||||
var conn;
|
||||
connections.map(function(c) {
|
||||
@@ -283,7 +293,7 @@ handler.addConnection = function(id, is_file_transfer, port_forward, peer_id, na
|
||||
port_forward: port_forward,
|
||||
name: name, authorized: authorized, time: new Date(),
|
||||
keyboard: keyboard, clipboard: clipboard, msgs: [], unreaded: 0,
|
||||
audio: audio, file: file
|
||||
audio: audio, file: file, restart: restart
|
||||
});
|
||||
body.cur = connections.length - 1;
|
||||
bring_to_top();
|
||||
|
||||
@@ -120,7 +120,7 @@ textarea:empty {
|
||||
@ELLIPSIS;
|
||||
}
|
||||
|
||||
div.password svg {
|
||||
div.password svg:not(.checkmark) {
|
||||
padding-left: 1em;
|
||||
size: 16px;
|
||||
color: #ddd;
|
||||
|
||||
@@ -141,7 +141,7 @@ function adjustBorder() {
|
||||
if (el) el.attributes.toggleClass("active", view.windowState == View.WINDOW_FULL_SCREEN);
|
||||
}
|
||||
|
||||
var svg_checkmark = <svg viewBox="0 0 492 492"><path d="M484 105l-16-17a27 27 0 00-38 0L204 315 62 173c-5-5-12-7-19-7s-14 2-19 7L8 189a27 27 0 000 38l160 160v1l16 16c5 5 12 8 19 8 8 0 14-3 20-8l16-16v-1l245-244a27 27 0 000-38z"/></svg>;
|
||||
var svg_checkmark = <svg class="checkmark" viewBox="0 0 492 492"><path d="M484 105l-16-17a27 27 0 00-38 0L204 315 62 173c-5-5-12-7-19-7s-14 2-19 7L8 189a27 27 0 000 38l160 160v1l16 16c5 5 12 8 19 8 8 0 14-3 20-8l16-16v-1l245-244a27 27 0 000-38z"/></svg>;
|
||||
var svg_edit = <svg #edit viewBox="0 0 384 384">
|
||||
<path d="M0 304v80h80l236-236-80-80zM378 56L328 6c-8-8-22-8-30 0l-39 39 80 80 39-39c8-8 8-22 0-30z"/>
|
||||
</svg>;
|
||||
|
||||
@@ -94,3 +94,4 @@ span#fullscreen.active {
|
||||
button:disabled {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
|
||||
@@ -145,6 +145,9 @@ class Header: Reactor.Component {
|
||||
}
|
||||
|
||||
function renderDisplayPop() {
|
||||
var codecs = handler.supported_hwcodec();
|
||||
var show_codec = handler.has_hwcodec() && (codecs[0] || codecs[1]);
|
||||
|
||||
return <popup>
|
||||
<menu.context #display-options>
|
||||
<li #adjust-window style="display:none">{translate('Adjust Window')}</li>
|
||||
@@ -157,8 +160,16 @@ class Header: Reactor.Component {
|
||||
<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>
|
||||
{show_codec ? <div>
|
||||
<div .separator />
|
||||
<li #auto type="codec-preference"><span>{svg_checkmark}</span>Auto</li>
|
||||
<li #vp9 type="codec-preference"><span>{svg_checkmark}</span>VP9</li>
|
||||
{codecs[0] ? <li #h264 type="codec-preference"><span>{svg_checkmark}</span>H264</li> : ""}
|
||||
{codecs[1] ? <li #h265 type="codec-preference"><span>{svg_checkmark}</span>H265</li> : ""}
|
||||
</div> : ""}
|
||||
<div .separator />
|
||||
<li #show-remote-cursor .toggle-option><span>{svg_checkmark}</span>{translate('Show remote cursor')}</li>
|
||||
<li #show-quality-monitor .toggle-option><span>{svg_checkmark}</span>{translate('Show quality monitor')}</li>
|
||||
{audio_enabled ? <li #disable-audio .toggle-option><span>{svg_checkmark}</span>{translate('Mute')}</li> : ""}
|
||||
{is_win && pi.platform == 'Windows' && file_enabled ? <li #enable-file-transfer .toggle-option><span>{svg_checkmark}</span>{translate('Allow file copy and paste')}</li> : ""}
|
||||
{keyboard_enabled && clipboard_enabled ? <li #disable-clipboard .toggle-option><span>{svg_checkmark}</span>{translate('Disable clipboard')}</li> : ""}
|
||||
@@ -177,6 +188,7 @@ class Header: Reactor.Component {
|
||||
{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> : ""}
|
||||
{restart_enabled && (pi.platform == "Linux" || pi.platform == "Windows" || pi.platform == "Mac OS") ? <li #restart_remote_device>{translate('Restart Remote Device')}</li> : ""}
|
||||
{keyboard_enabled ? <li #lock-screen>{translate('Insert Lock')}</li> : ""}
|
||||
{keyboard_enabled && pi.platform == "Windows" && pi.sas_enabled ? <li #block-input>{translate("Block user input")}</li> : ""}
|
||||
<li #refresh>{translate('Refresh')}</li>
|
||||
@@ -290,6 +302,12 @@ class Header: Reactor.Component {
|
||||
handler.ctrl_alt_del();
|
||||
}
|
||||
|
||||
event click $(#restart_remote_device) {
|
||||
msgbox("restart-confirmation", translate("Restart Remote Device"), translate("Are you sure you want to restart") + " " + pi.username + "@" + pi.hostname + "(" + get_id() + ") ?", function(res=null) {
|
||||
if (res != null) handler.restart_remote_device();
|
||||
});
|
||||
}
|
||||
|
||||
event click $(#lock-screen) {
|
||||
handler.lock_screen();
|
||||
}
|
||||
@@ -310,12 +328,14 @@ class Header: Reactor.Component {
|
||||
}
|
||||
}
|
||||
|
||||
event click $(menu#display-options>li) (_, me) {
|
||||
event click $(menu#display-options li) (_, me) {
|
||||
if (me.id == "custom") {
|
||||
handle_custom_image_quality();
|
||||
} else if (me.id == "privacy-mode") {
|
||||
togglePrivacyMode(me.id);
|
||||
} else if (me.attributes.hasClass("toggle-option")) {
|
||||
} else if (me.id == "show-quality-monitor") {
|
||||
toggleQualityMonitor(me.id);
|
||||
}else if (me.attributes.hasClass("toggle-option")) {
|
||||
handler.toggle_option(me.id);
|
||||
toggleMenuState();
|
||||
} else if (!me.attributes.hasClass("selected")) {
|
||||
@@ -325,6 +345,9 @@ class Header: Reactor.Component {
|
||||
} else if (type == "view-style") {
|
||||
handler.save_view_style(me.id);
|
||||
adaptDisplay();
|
||||
} else if (type == "codec-preference") {
|
||||
handler.set_option("codec-preference", me.id);
|
||||
handler.change_prefer_codec();
|
||||
}
|
||||
toggleMenuState();
|
||||
}
|
||||
@@ -333,15 +356,13 @@ class Header: Reactor.Component {
|
||||
|
||||
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;
|
||||
var bitrate = (tmp[0] || 50);
|
||||
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><input type=\"hslider\" style=\"width: 50%\" name=\"bitrate\" max=\"100\" min=\"10\" value=\"" + bitrate + "\"/ buddy=\"bitrate-buddy\"><b #bitrate-buddy>x</b>% Bitrate</div> \
|
||||
</div>", function(res=null) {
|
||||
if (!res) return;
|
||||
if (!res.bitrate) return;
|
||||
handler.save_custom_image_quality(res.bitrate, res.quantizer);
|
||||
handler.save_custom_image_quality(res.bitrate);
|
||||
toggleMenuState();
|
||||
});
|
||||
}
|
||||
@@ -354,10 +375,13 @@ function toggleMenuState() {
|
||||
var s = handler.get_view_style();
|
||||
if (!s) s = "original";
|
||||
values.push(s);
|
||||
for (var el in $$(menu#display-options>li)) {
|
||||
var c = handler.get_option("codec-preference");
|
||||
if (!c) c = "auto";
|
||||
values.push(c);
|
||||
for (var el in $$(menu#display-options li)) {
|
||||
el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0);
|
||||
}
|
||||
for (var id in ["show-remote-cursor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end"]) {
|
||||
for (var id in ["show-remote-cursor", "show-quality-monitor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end"]) {
|
||||
var el = self.select('#' + id);
|
||||
if (el) {
|
||||
var value = handler.get_toggle_option(id);
|
||||
@@ -425,6 +449,17 @@ function togglePrivacyMode(privacy_id) {
|
||||
}
|
||||
}
|
||||
|
||||
function toggleQualityMonitor(name) {
|
||||
var show = handler.get_toggle_option(name);
|
||||
if (show) {
|
||||
$(#quality-monitor).style.set{ display: "none" };
|
||||
} else {
|
||||
$(#quality-monitor).style.set{ display: "block" };
|
||||
}
|
||||
handler.toggle_option(name);
|
||||
toggleMenuState();
|
||||
}
|
||||
|
||||
handler.updateBlockInputState = function(input_blocked) {
|
||||
if (!input_blocked) {
|
||||
handler.toggle_option("block-input");
|
||||
|
||||
@@ -403,3 +403,18 @@ div.remote-session svg#menu {
|
||||
background: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
svg#refresh-password {
|
||||
display: inline-block;
|
||||
stroke:#ddd;
|
||||
}
|
||||
|
||||
svg#refresh-password:hover {
|
||||
stroke:color(text);
|
||||
}
|
||||
|
||||
li:disabled, li:disabled:hover {
|
||||
color: color(lighter-text);
|
||||
background: color(menu);
|
||||
}
|
||||
|
||||
|
||||
237
src/ui/index.tis
237
src/ui/index.tis
@@ -20,6 +20,7 @@ var svg_menu = <svg #menu viewBox="0 0 512 512">
|
||||
<circle cx="256" cy="448" r="64"/>
|
||||
<circle cx="256" cy="64" r="64"/>
|
||||
</svg>;
|
||||
var svg_refresh_password = <svg #refresh-password xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 2v6h6M2.66 15.57a10 10 0 1 0 .57-8.38"/></svg>;
|
||||
|
||||
var my_id = "";
|
||||
function get_id() {
|
||||
@@ -163,6 +164,77 @@ class AudioInputs: Reactor.Component {
|
||||
}
|
||||
this.toggleMenuState();
|
||||
}
|
||||
};
|
||||
|
||||
class Languages: Reactor.Component {
|
||||
function render() {
|
||||
var langs = JSON.parse(handler.get_langs());
|
||||
var me = this;
|
||||
self.timer(1ms, function() { me.toggleMenuState() });
|
||||
return <li>{translate('Language')}
|
||||
<menu #languages key={langs.length}>
|
||||
<li id="default"><span>{svg_checkmark}</span>Default</li>
|
||||
<div .separator />
|
||||
{langs.map(function(lang) {
|
||||
return <li id={lang[0]}><span>{svg_checkmark}</span>{lang[1]}</li>;
|
||||
})}
|
||||
</menu>
|
||||
</li>;
|
||||
}
|
||||
|
||||
|
||||
function toggleMenuState() {
|
||||
var cur = handler.get_local_option("lang") || "default";
|
||||
for (var el in this.$$(menu#languages>li)) {
|
||||
var selected = cur == el.id;
|
||||
el.attributes.toggleClass("selected", selected);
|
||||
}
|
||||
}
|
||||
|
||||
event click $(menu#languages>li) (_, me) {
|
||||
var v = me.id;
|
||||
if (v == "default") v = "";
|
||||
handler.set_local_option("lang", v);
|
||||
app.update();
|
||||
this.toggleMenuState();
|
||||
}
|
||||
}
|
||||
|
||||
var enhancementsMenu;
|
||||
class Enhancements: Reactor.Component {
|
||||
function this() {
|
||||
enhancementsMenu = this;
|
||||
}
|
||||
|
||||
function render() {
|
||||
var has_hwcodec = handler.has_hwcodec();
|
||||
var me = this;
|
||||
self.timer(1ms, function() { me.toggleMenuState() });
|
||||
return <li>{translate('Enhancements')}
|
||||
<menu #enhancements-menu>
|
||||
{has_hwcodec ? <li #enable-hwcodec><span>{svg_checkmark}</span>{translate("Hardware Codec")} (beta)</li> : ""}
|
||||
<li #enable-abr><span>{svg_checkmark}</span>{translate("Adaptive Bitrate")} (beta)</li>
|
||||
</menu>
|
||||
</li>;
|
||||
}
|
||||
|
||||
function toggleMenuState() {
|
||||
for (var el in $$(menu#enhancements-menu>li)) {
|
||||
if (el.id && el.id.indexOf("enable-") == 0) {
|
||||
var enabled = handler.get_option(el.id) != "N";
|
||||
el.attributes.toggleClass("selected", enabled);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
event click $(menu#enhancements-menu>li) (_, me) {
|
||||
var v = me.id;
|
||||
if (v.indexOf("enable-") == 0) {
|
||||
handler.set_option(v, handler.get_option(v) != 'N' ? 'N' : '');
|
||||
}
|
||||
this.toggleMenuState();
|
||||
}
|
||||
}
|
||||
|
||||
function getUserName() {
|
||||
@@ -202,8 +274,10 @@ class MyIdMenu: Reactor.Component {
|
||||
<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-remote-restart><span>{svg_checkmark}</span>{translate('Enable Remote Restart')}</li>
|
||||
<li #enable-tunnel><span>{svg_checkmark}</span>{translate('Enable TCP Tunneling')}</li>
|
||||
<AudioInputs />
|
||||
<Enhancements />
|
||||
<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>
|
||||
@@ -222,7 +296,7 @@ class MyIdMenu: Reactor.Component {
|
||||
{handler.is_ok_change_id() && key_confirmed ? <li #change-id>{translate('Change ID')}</li> : ""}
|
||||
<div .separator />
|
||||
<li #allow-darktheme><span>{svg_checkmark}</span>{translate('Dark Theme')}</li>
|
||||
<div .separator />
|
||||
<Languages />
|
||||
<li #about>{translate('About')} {" "}{handler.get_app_name()}</li>
|
||||
</menu>
|
||||
</popup>;
|
||||
@@ -448,10 +522,6 @@ class App: Reactor.Component
|
||||
var is_can_screen_recording = handler.is_can_screen_recording(false);
|
||||
return
|
||||
<div .app>
|
||||
<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>{translate('Your Desktop')}</div>
|
||||
@@ -460,10 +530,7 @@ class App: Reactor.Component
|
||||
<MyIdMenu />
|
||||
{key_confirmed ? <input type="text" readonly value={formatId(get_id())}/> : translate("Generating ...")}
|
||||
</div>
|
||||
<div .your-desktop>
|
||||
<div>{translate('Password')}</div>
|
||||
<Password />
|
||||
</div>
|
||||
<PasswordArea />
|
||||
</div>
|
||||
{!is_win || handler.is_installed() ? "": <InstallMe />}
|
||||
{software_update_url ? <UpdateMe /> : ""}
|
||||
@@ -734,51 +801,116 @@ function watch_screen_recording() {
|
||||
|
||||
class PasswordEyeArea : Reactor.Component {
|
||||
render() {
|
||||
var method = handler.get_option('verification-method');
|
||||
var value = method != 'use-permanent-password' ? password_cache[0] : "-";
|
||||
return
|
||||
<div .eye-area style="width: *">
|
||||
<input|text @{this.input} readonly value="******" />
|
||||
{svg_eye}
|
||||
<input|text @{this.input} readonly value={value} />
|
||||
{svg_refresh_password}
|
||||
</div>;
|
||||
}
|
||||
|
||||
event mouseenter {
|
||||
var me = this;
|
||||
me.leaved = false;
|
||||
me.timer(300ms, function() {
|
||||
if (me.leaved) return;
|
||||
me.input.value = handler.get_password();
|
||||
});
|
||||
}
|
||||
|
||||
event mouseleave {
|
||||
this.leaved = true;
|
||||
this.input.value = "******";
|
||||
event click $(svg#refresh-password) (_, me) {
|
||||
handler.update_temporary_password();
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
class Password: Reactor.Component {
|
||||
var temporaryPasswordLengthMenu;
|
||||
class TemporaryPasswordLengthMenu: Reactor.Component {
|
||||
function this() {
|
||||
temporaryPasswordLengthMenu = this;
|
||||
}
|
||||
|
||||
function render() {
|
||||
return <div .password style="flow:horizontal">
|
||||
<PasswordEyeArea />
|
||||
{svg_edit}
|
||||
if (!this.show) return <li />;
|
||||
var me = this;
|
||||
var method = handler.get_option('verification-method');
|
||||
self.timer(1ms, function() { me.toggleMenuState() });
|
||||
return <li disabled={ method == 'use-permanent-password' ? "true" : "false" }>{translate("Set temporary password length")}
|
||||
<menu #temporary-password-length>
|
||||
<li #temporary-password-length-6><span>{svg_checkmark}</span>6</li>
|
||||
<li #temporary-password-length-8><span>{svg_checkmark}</span>8</li>
|
||||
<li #temporary-password-length-10><span>{svg_checkmark}</span>10</li>
|
||||
</menu>
|
||||
</li>;
|
||||
}
|
||||
|
||||
function toggleMenuState() {
|
||||
var length = handler.get_option("temporary-password-length");
|
||||
var index = ['6', '8', '10'].indexOf(length);
|
||||
if (index < 0) index = 0;
|
||||
for (var (i, el) in this.$$(menu#temporary-password-length>li)) {
|
||||
el.attributes.toggleClass("selected", i == index);
|
||||
}
|
||||
}
|
||||
|
||||
event click $(menu#temporary-password-length>li) (_, me) {
|
||||
var length = me.id.substring('temporary-password-length-'.length);
|
||||
var old_length = handler.get_option('temporary-password-length');
|
||||
if (length != old_length) {
|
||||
handler.set_option('temporary-password-length', length);
|
||||
handler.update_temporary_password();
|
||||
this.toggleMenuState();
|
||||
passwordArea.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var passwordArea;
|
||||
class PasswordArea: Reactor.Component {
|
||||
function this() {
|
||||
passwordArea = this;
|
||||
}
|
||||
|
||||
function render() {
|
||||
var me = this;
|
||||
self.timer(1ms, function() { me.toggleMenuState() });
|
||||
return
|
||||
<div .your-desktop>
|
||||
<div>{translate('Password')}</div>
|
||||
<div .password style="flow:horizontal">
|
||||
{this.renderPop()}
|
||||
<PasswordEyeArea />
|
||||
{svg_edit}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
event click $(svg#edit) (_, me) {
|
||||
var menu = $(menu#edit-password-context);
|
||||
me.popup(menu);
|
||||
function renderPop() {
|
||||
var method = handler.get_option('verification-method');
|
||||
return <popup><menu.context #edit-password-context>
|
||||
<li #use-temporary-password><span>{svg_checkmark}</span>{translate('Use temporary password')}</li>
|
||||
<li #use-permanent-password><span>{svg_checkmark}</span>{translate('Use permanent password')}</li>
|
||||
<li #use-both-passwords><span>{svg_checkmark}</span>{translate('Use both passwords')}</li>
|
||||
<div .separator />
|
||||
<li #set-password disabled={ method == 'use-temporary-password' ? "true" : "false" }>{translate('Set permanent password')}</li>
|
||||
<TemporaryPasswordLengthMenu />
|
||||
</menu></popup>;
|
||||
}
|
||||
|
||||
event click $(li#refresh-password) {
|
||||
handler.update_password("");
|
||||
this.update();
|
||||
function toggleMenuState() {
|
||||
var id = handler.get_option('verification-method');
|
||||
if (id != 'use-temporary-password' && id != 'use-permanent-password')
|
||||
id = 'use-both-passwords';
|
||||
for (var el in [this.$(li#use-temporary-password), this.$(li#use-permanent-password), this.$(li#use-both-passwords)]) {
|
||||
el.attributes.toggleClass("selected", el.id == id);
|
||||
}
|
||||
}
|
||||
|
||||
event click $(svg#edit) (_, me) {
|
||||
temporaryPasswordLengthMenu.update({show: true });
|
||||
var menu = $(menu#edit-password-context);
|
||||
me.popup(menu);
|
||||
}
|
||||
|
||||
event click $(li#set-password) {
|
||||
var me = this;
|
||||
var password = handler.permanent_password();
|
||||
var value_field = password.length == 0 ? "" : "value=" + password;
|
||||
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><span>" + translate('Password') + ":</span><input|password(password) .outline-focus " + value_field + " /></div> \
|
||||
<div><span>" + translate('Confirmation') + ":</span><input|password(confirmation) " + value_field + " /></div> \
|
||||
</div> \
|
||||
", function(res=null) {
|
||||
if (!res) return;
|
||||
@@ -790,12 +922,45 @@ class Password: Reactor.Component {
|
||||
if (p0 != p1) {
|
||||
return translate("The confirmation is not identical.");
|
||||
}
|
||||
handler.update_password(p0);
|
||||
handler.set_permanent_password(p0);
|
||||
me.update();
|
||||
});
|
||||
}
|
||||
|
||||
event click $(menu#edit-password-context>li) (_, me) {
|
||||
if (me.id.indexOf('use-') == 0) {
|
||||
handler.set_option('verification-method', me.id);
|
||||
this.toggleMenuState();
|
||||
passwordArea.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var password_cache = ["","",""];
|
||||
function updatePasswordArea() {
|
||||
self.timer(1s, function() {
|
||||
var temporary_password = handler.temporary_password();
|
||||
var verification_method = handler.get_option('verification-method');
|
||||
var temporary_password_length = handler.get_option('temporary-password-length');
|
||||
var update = false;
|
||||
if (password_cache[0] != temporary_password) {
|
||||
password_cache[0] = temporary_password;
|
||||
update = true;
|
||||
}
|
||||
if (password_cache[1] != verification_method) {
|
||||
password_cache[1] = verification_method;
|
||||
update = true;
|
||||
}
|
||||
if (password_cache[2] != temporary_password_length) {
|
||||
password_cache[2] = temporary_password_length;
|
||||
update = true;
|
||||
}
|
||||
if (update) passwordArea.update();
|
||||
updatePasswordArea();
|
||||
});
|
||||
}
|
||||
updatePasswordArea();
|
||||
|
||||
class ID: Reactor.Component {
|
||||
function render() {
|
||||
return <input type="text" #remote_id .outline-focus novalue={translate("Enter Remote ID")} maxlength="21"
|
||||
|
||||
@@ -192,7 +192,7 @@ pub fn make_menubar(host: Rc<Host>, is_index: bool) {
|
||||
app_menu.addItem_(new_item);
|
||||
} else {
|
||||
// When app launched without argument, is the main panel.
|
||||
let about_item = make_menu_item("About", "a", SHOW_ABOUT_TAG);
|
||||
let about_item = make_menu_item("About", "", SHOW_ABOUT_TAG);
|
||||
app_menu.addItem_(about_item);
|
||||
let separator = NSMenuItem::separatorItem(nil).autorelease();
|
||||
app_menu.addItem_(separator);
|
||||
@@ -258,10 +258,10 @@ pub fn check_main_window() {
|
||||
let app = format!("/Applications/{}.app", crate::get_app_name());
|
||||
let my_uid = sys
|
||||
.process((std::process::id() as i32).into())
|
||||
.map(|x| x.uid)
|
||||
.map(|x| x.user_id())
|
||||
.unwrap_or_default();
|
||||
for (_, p) in sys.processes().iter() {
|
||||
if p.cmd().len() == 1 && p.uid == my_uid && p.cmd()[0].contains(&app) {
|
||||
if p.cmd().len() == 1 && p.user_id() == my_uid && p.cmd()[0].contains(&app) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ class MsgboxComponent: Reactor.Component {
|
||||
var color = this.getColor();
|
||||
var icon = this.getIcon(color);
|
||||
var content = this.getContent();
|
||||
var hasCancel = this.type.indexOf("error") < 0 && this.type.indexOf("nocancel") < 0;
|
||||
var hasCancel = this.type.indexOf("error") < 0 && this.type.indexOf("nocancel") < 0 && this.type != "restarting";
|
||||
var hasOk = this.type != "connecting" && this.type != "success" && this.type.indexOf("nook") < 0;
|
||||
var hasClose = this.type.indexOf("hasclose") >= 0;
|
||||
var show_progress = this.type == "connecting";
|
||||
|
||||
@@ -9,6 +9,16 @@ div#video-wrapper {
|
||||
background: #212121;
|
||||
}
|
||||
|
||||
div#quality-monitor {
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #7571719c;
|
||||
padding: 5px;
|
||||
min-width: 150px;
|
||||
color: azure;
|
||||
border: solid azure;
|
||||
}
|
||||
|
||||
video#handler {
|
||||
behavior: native-remote video;
|
||||
size: *;
|
||||
@@ -24,7 +34,7 @@ img#cursor {
|
||||
}
|
||||
|
||||
.goup {
|
||||
transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
table#remote-folder-view {
|
||||
@@ -33,4 +43,4 @@ table#remote-folder-view {
|
||||
|
||||
table#local-folder-view {
|
||||
context-menu: selector(menu#local-folder-view);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
<html window-resizable window-frame="extended">
|
||||
<head>
|
||||
<style>
|
||||
@import url(common.css);
|
||||
@import url(remote.css);
|
||||
@import url(file_transfer.css);
|
||||
@import url(header.css);
|
||||
</style>
|
||||
<script type="text/tiscript">
|
||||
|
||||
<head>
|
||||
<style>
|
||||
@import url(common.css);
|
||||
@import url(remote.css);
|
||||
@import url(file_transfer.css);
|
||||
@import url(header.css);
|
||||
</style>
|
||||
<script type="text/tiscript">
|
||||
include "common.tis";
|
||||
include "msgbox.tis";
|
||||
include "remote.tis";
|
||||
@@ -15,23 +16,28 @@
|
||||
include "grid.tis";
|
||||
include "header.tis";
|
||||
</script>
|
||||
</head>
|
||||
<header>
|
||||
<div.window-icon role="window-icon"><icon /></div>
|
||||
</head>
|
||||
<header>
|
||||
<div.window-icon role="window-icon">
|
||||
<icon />
|
||||
</div>
|
||||
<caption role="window-caption" />
|
||||
<div.window-toolbar />
|
||||
<div.window-buttons />
|
||||
</header>
|
||||
<body>
|
||||
<div #video-wrapper>
|
||||
<video #handler>
|
||||
<div style="position: relative">
|
||||
<img #cursor src="in-memory:cursor" />
|
||||
</div>
|
||||
</video>
|
||||
</div>
|
||||
<div #file-transfer-wrapper>
|
||||
</div>
|
||||
<div #msgbox />
|
||||
</body>
|
||||
</html>
|
||||
</header>
|
||||
|
||||
<body>
|
||||
<div #video-wrapper>
|
||||
<video #handler>
|
||||
<div #quality-monitor style="position: absolute; display: none" />
|
||||
<div style="position: relative">
|
||||
<img #cursor src="in-memory:cursor" />
|
||||
</div>
|
||||
</video>
|
||||
</div>
|
||||
<div #file-transfer-wrapper>
|
||||
</div>
|
||||
<div #msgbox />
|
||||
</body>
|
||||
|
||||
</html>
|
||||
275
src/ui/remote.rs
275
src/ui/remote.rs
@@ -2,7 +2,7 @@ use std::{
|
||||
collections::HashMap,
|
||||
ops::Deref,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||
Arc, Mutex, RwLock,
|
||||
},
|
||||
};
|
||||
@@ -87,6 +87,7 @@ pub struct Handler {
|
||||
inner: Arc<RwLock<HandlerInner>>,
|
||||
cmd: String,
|
||||
id: String,
|
||||
password: String,
|
||||
args: Vec<String>,
|
||||
lc: Arc<RwLock<LoginConfigHandler>>,
|
||||
}
|
||||
@@ -223,7 +224,7 @@ impl sciter::EventHandler for Handler {
|
||||
fn get_custom_image_quality();
|
||||
fn save_view_style(String);
|
||||
fn save_image_quality(String);
|
||||
fn save_custom_image_quality(i32, i32);
|
||||
fn save_custom_image_quality(i32);
|
||||
fn refresh_video();
|
||||
fn get_toggle_option(String);
|
||||
fn is_privacy_mode_supported();
|
||||
@@ -231,14 +232,28 @@ impl sciter::EventHandler for Handler {
|
||||
fn get_remember();
|
||||
fn peer_platform();
|
||||
fn set_write_override(i32, i32, bool, bool, bool);
|
||||
fn has_hwcodec();
|
||||
fn supported_hwcodec();
|
||||
fn change_prefer_codec();
|
||||
fn restart_remote_device();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct QualityStatus {
|
||||
speed: Option<String>,
|
||||
fps: Option<i32>,
|
||||
delay: Option<i32>,
|
||||
target_bitrate: Option<i32>,
|
||||
codec_format: Option<CodecFormat>,
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
pub fn new(cmd: String, id: String, args: Vec<String>) -> Self {
|
||||
pub fn new(cmd: String, id: String, password: String, args: Vec<String>) -> Self {
|
||||
let me = Self {
|
||||
cmd,
|
||||
id: id.clone(),
|
||||
password: password.clone(),
|
||||
args,
|
||||
..Default::default()
|
||||
};
|
||||
@@ -249,6 +264,21 @@ impl Handler {
|
||||
me
|
||||
}
|
||||
|
||||
fn update_quality_status(&self, status: QualityStatus) {
|
||||
self.call2(
|
||||
"updateQualityStatus",
|
||||
&make_args!(
|
||||
status.speed.map_or(Value::null(), |it| it.into()),
|
||||
status.fps.map_or(Value::null(), |it| it.into()),
|
||||
status.delay.map_or(Value::null(), |it| it.into()),
|
||||
status.target_bitrate.map_or(Value::null(), |it| it.into()),
|
||||
status
|
||||
.codec_format
|
||||
.map_or(Value::null(), |it| it.to_string().into())
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn start_keyboard_hook(&self) {
|
||||
if self.is_port_forward() || self.is_file_transfer() {
|
||||
return;
|
||||
@@ -533,12 +563,12 @@ impl Handler {
|
||||
self.send(Data::Message(LoginConfigHandler::refresh()));
|
||||
}
|
||||
|
||||
fn save_custom_image_quality(&mut self, bitrate: i32, quantizer: i32) {
|
||||
fn save_custom_image_quality(&mut self, custom_image_quality: i32) {
|
||||
let msg = self
|
||||
.lc
|
||||
.write()
|
||||
.unwrap()
|
||||
.save_custom_image_quality(bitrate, quantizer);
|
||||
.save_custom_image_quality(custom_image_quality);
|
||||
self.send(Data::Message(msg));
|
||||
}
|
||||
|
||||
@@ -571,6 +601,52 @@ impl Handler {
|
||||
true
|
||||
}
|
||||
|
||||
fn has_hwcodec(&self) -> bool {
|
||||
#[cfg(not(feature = "hwcodec"))]
|
||||
return false;
|
||||
#[cfg(feature = "hwcodec")]
|
||||
return true;
|
||||
}
|
||||
|
||||
fn supported_hwcodec(&self) -> Value {
|
||||
#[cfg(feature = "hwcodec")]
|
||||
{
|
||||
let mut v = Value::array(0);
|
||||
let decoder = scrap::codec::Decoder::video_codec_state(&self.id);
|
||||
let mut h264 = decoder.score_h264 > 0;
|
||||
let mut h265 = decoder.score_h265 > 0;
|
||||
if let Some((encoding_264, encoding_265)) = self.lc.read().unwrap().supported_encoding {
|
||||
h264 = h264 && encoding_264;
|
||||
h265 = h265 && encoding_265;
|
||||
}
|
||||
v.push(h264);
|
||||
v.push(h265);
|
||||
v
|
||||
}
|
||||
#[cfg(not(feature = "hwcodec"))]
|
||||
{
|
||||
let mut v = Value::array(0);
|
||||
v.push(false);
|
||||
v.push(false);
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
fn change_prefer_codec(&self) {
|
||||
let msg = self.lc.write().unwrap().change_prefer_codec();
|
||||
self.send(Data::Message(msg));
|
||||
}
|
||||
|
||||
fn restart_remote_device(&mut self) {
|
||||
self.lc.write().unwrap().restarting_remote_device = true;
|
||||
let msg = self.lc.write().unwrap().restart_remote_device();
|
||||
self.send(Data::Message(msg));
|
||||
}
|
||||
|
||||
pub fn is_restarting_remote_device(&self) -> bool {
|
||||
self.lc.read().unwrap().restarting_remote_device
|
||||
}
|
||||
|
||||
fn t(&self, name: String) -> String {
|
||||
crate::client::translate(name)
|
||||
}
|
||||
@@ -1043,7 +1119,7 @@ impl Handler {
|
||||
fn get_char(&mut self, name: String, code: i32) -> String {
|
||||
if let Some(key_event) = self.get_key_event(1, &name, code) {
|
||||
match key_event.union {
|
||||
Some(key_event::Union::chr(chr)) => {
|
||||
Some(key_event::Union::Chr(chr)) => {
|
||||
if let Some(chr) = std::char::from_u32(chr as _) {
|
||||
return chr.to_string();
|
||||
}
|
||||
@@ -1074,7 +1150,7 @@ impl Handler {
|
||||
|
||||
fn transfer_file(&mut self) {
|
||||
let id = self.get_id();
|
||||
let args = vec!["--file-transfer", &id];
|
||||
let args = vec!["--file-transfer", &id, &self.password];
|
||||
if let Err(err) = crate::run_me(args) {
|
||||
log::error!("Failed to spawn file transfer: {}", err);
|
||||
}
|
||||
@@ -1082,7 +1158,7 @@ impl Handler {
|
||||
|
||||
fn tunnel(&mut self) {
|
||||
let id = self.get_id();
|
||||
let args = vec!["--port-forward", &id];
|
||||
let args = vec!["--port-forward", &id, &self.password];
|
||||
if let Err(err) = crate::run_me(args) {
|
||||
log::error!("Failed to spawn IP tunneling: {}", err);
|
||||
}
|
||||
@@ -1188,6 +1264,7 @@ async fn start_one_port_forward(
|
||||
handler.lc.write().unwrap().port_forward = (remote_host, remote_port);
|
||||
if let Err(err) = crate::port_forward::listen(
|
||||
handler.id.clone(),
|
||||
handler.password.clone(),
|
||||
port,
|
||||
handler.clone(),
|
||||
receiver,
|
||||
@@ -1296,7 +1373,10 @@ async fn io_loop(handler: Handler) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
let (video_sender, audio_sender) = start_video_audio_threads(|data: &[u8]| {
|
||||
let frame_count = Arc::new(AtomicUsize::new(0));
|
||||
let frame_count_cl = frame_count.clone();
|
||||
let (video_sender, audio_sender) = start_video_audio_threads(move |data: &[u8]| {
|
||||
frame_count_cl.fetch_add(1, Ordering::Relaxed);
|
||||
VIDEO
|
||||
.lock()
|
||||
.unwrap()
|
||||
@@ -1319,6 +1399,9 @@ async fn io_loop(handler: Handler) {
|
||||
first_frame: false,
|
||||
#[cfg(windows)]
|
||||
clipboard_file_context: None,
|
||||
data_count: Arc::new(AtomicUsize::new(0)),
|
||||
frame_count,
|
||||
video_format: CodecFormat::Unknown,
|
||||
};
|
||||
remote.io_loop(&key, &token).await;
|
||||
remote.sync_jobs_status_to_local().await;
|
||||
@@ -1369,6 +1452,9 @@ struct Remote {
|
||||
first_frame: bool,
|
||||
#[cfg(windows)]
|
||||
clipboard_file_context: Option<Box<CliprdrClientContext>>,
|
||||
data_count: Arc<AtomicUsize>,
|
||||
frame_count: Arc<AtomicUsize>,
|
||||
video_format: CodecFormat,
|
||||
}
|
||||
|
||||
impl Remote {
|
||||
@@ -1394,6 +1480,8 @@ impl Remote {
|
||||
#[cfg(windows)]
|
||||
let mut rx_clip_client = get_rx_clip_client().lock().await;
|
||||
|
||||
let mut status_timer = time::interval(Duration::new(1, 0));
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = peer.next() => {
|
||||
@@ -1406,14 +1494,20 @@ impl Remote {
|
||||
}
|
||||
Ok(ref bytes) => {
|
||||
last_recv_time = Instant::now();
|
||||
self.data_count.fetch_add(bytes.len(), Ordering::Relaxed);
|
||||
if !self.handle_msg_from_peer(bytes, &mut peer).await {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::info!("Reset by the peer");
|
||||
self.handler.msgbox("error", "Connection Error", "Reset by the peer");
|
||||
if self.handler.is_restarting_remote_device() {
|
||||
log::info!("Restart remote device");
|
||||
self.handler.msgbox("restarting", "Restarting Remote Device", "remote_restarting_tip");
|
||||
} else {
|
||||
log::info!("Reset by the peer");
|
||||
self.handler.msgbox("error", "Connection Error", "Reset by the peer");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1450,6 +1544,16 @@ impl Remote {
|
||||
self.timer = time::interval_at(Instant::now() + SEC30, SEC30);
|
||||
}
|
||||
}
|
||||
_ = status_timer.tick() => {
|
||||
let speed = self.data_count.swap(0, Ordering::Relaxed);
|
||||
let speed = format!("{:.2}kB/s", speed as f32 / 1024 as f32);
|
||||
let fps = self.frame_count.swap(0, Ordering::Relaxed) as _;
|
||||
self.handler.update_quality_status(QualityStatus {
|
||||
speed:Some(speed),
|
||||
fps:Some(fps),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
log::debug!("Exit io_loop of id={}", self.handler.id);
|
||||
@@ -1584,9 +1688,13 @@ impl Remote {
|
||||
}
|
||||
|
||||
async fn handle_msg_from_ui(&mut self, data: Data, peer: &mut Stream) -> bool {
|
||||
// log::info!("new msg from ui, {}",data);
|
||||
match data {
|
||||
Data::Close => {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_close_reason("".to_owned());
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
allow_err!(peer.send(&msg).await);
|
||||
return false;
|
||||
}
|
||||
Data::Login((password, remember)) => {
|
||||
@@ -1764,9 +1872,9 @@ impl Remote {
|
||||
id,
|
||||
file_num,
|
||||
union: if need_override {
|
||||
Some(file_transfer_send_confirm_request::Union::offset_blk(0))
|
||||
Some(file_transfer_send_confirm_request::Union::OffsetBlk(0))
|
||||
} else {
|
||||
Some(file_transfer_send_confirm_request::Union::skip(true))
|
||||
Some(file_transfer_send_confirm_request::Union::Skip(true))
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
@@ -1782,9 +1890,9 @@ impl Remote {
|
||||
id,
|
||||
file_num,
|
||||
union: if need_override {
|
||||
Some(file_transfer_send_confirm_request::Union::offset_blk(0))
|
||||
Some(file_transfer_send_confirm_request::Union::OffsetBlk(0))
|
||||
} else {
|
||||
Some(file_transfer_send_confirm_request::Union::skip(true))
|
||||
Some(file_transfer_send_confirm_request::Union::Skip(true))
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
@@ -1955,11 +2063,11 @@ impl Remote {
|
||||
let mut config: PeerConfig = self.handler.load_config();
|
||||
let mut transfer_metas = TransferSerde::default();
|
||||
for job in self.read_jobs.iter() {
|
||||
let json_str = serde_json::to_string(&job.gen_meta()).unwrap();
|
||||
let json_str = serde_json::to_string(&job.gen_meta()).unwrap_or_default();
|
||||
transfer_metas.read_jobs.push(json_str);
|
||||
}
|
||||
for job in self.write_jobs.iter() {
|
||||
let json_str = serde_json::to_string(&job.gen_meta()).unwrap();
|
||||
let json_str = serde_json::to_string(&job.gen_meta()).unwrap_or_default();
|
||||
transfer_metas.write_jobs.push(json_str);
|
||||
}
|
||||
log::info!("meta: {:?}", transfer_metas);
|
||||
@@ -1971,24 +2079,34 @@ impl Remote {
|
||||
async fn handle_msg_from_peer(&mut self, data: &[u8], peer: &mut Stream) -> bool {
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&data) {
|
||||
match msg_in.union {
|
||||
Some(message::Union::video_frame(vf)) => {
|
||||
Some(message::Union::VideoFrame(vf)) => {
|
||||
if !self.first_frame {
|
||||
self.first_frame = true;
|
||||
self.handler.call2("closeSuccess", &make_args!());
|
||||
self.handler.call("adaptSize", &make_args!());
|
||||
}
|
||||
let incomming_format = CodecFormat::from(&vf);
|
||||
if self.video_format != incomming_format {
|
||||
self.video_format = incomming_format.clone();
|
||||
self.handler.update_quality_status(QualityStatus {
|
||||
codec_format: Some(incomming_format),
|
||||
..Default::default()
|
||||
})
|
||||
};
|
||||
self.video_sender.send(MediaData::VideoFrame(vf)).ok();
|
||||
}
|
||||
Some(message::Union::hash(hash)) => {
|
||||
self.handler.handle_hash(hash, peer).await;
|
||||
Some(message::Union::Hash(hash)) => {
|
||||
self.handler
|
||||
.handle_hash(&self.handler.password.clone(), hash, peer)
|
||||
.await;
|
||||
}
|
||||
Some(message::Union::login_response(lr)) => match lr.union {
|
||||
Some(login_response::Union::error(err)) => {
|
||||
Some(message::Union::LoginResponse(lr)) => match lr.union {
|
||||
Some(login_response::Union::Error(err)) => {
|
||||
if !self.handler.handle_login_error(&err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Some(login_response::Union::peer_info(pi)) => {
|
||||
Some(login_response::Union::PeerInfo(pi)) => {
|
||||
self.handler.handle_peer_info(pi);
|
||||
self.check_clipboard_file_context();
|
||||
if !(self.handler.is_file_transfer()
|
||||
@@ -2015,22 +2133,22 @@ impl Remote {
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(message::Union::cursor_data(cd)) => {
|
||||
Some(message::Union::CursorData(cd)) => {
|
||||
self.handler.set_cursor_data(cd);
|
||||
}
|
||||
Some(message::Union::cursor_id(id)) => {
|
||||
Some(message::Union::CursorId(id)) => {
|
||||
self.handler.set_cursor_id(id.to_string());
|
||||
}
|
||||
Some(message::Union::cursor_position(cp)) => {
|
||||
Some(message::Union::CursorPosition(cp)) => {
|
||||
self.handler.set_cursor_position(cp);
|
||||
}
|
||||
Some(message::Union::clipboard(cb)) => {
|
||||
Some(message::Union::Clipboard(cb)) => {
|
||||
if !self.handler.lc.read().unwrap().disable_clipboard {
|
||||
update_clipboard(cb, Some(&self.old_clipboard));
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
Some(message::Union::cliprdr(clip)) => {
|
||||
Some(message::Union::Cliprdr(clip)) => {
|
||||
if !self.handler.lc.read().unwrap().disable_clipboard {
|
||||
if let Some(context) = &mut self.clipboard_file_context {
|
||||
if let Some(clip) = msg_2_clip(clip) {
|
||||
@@ -2039,9 +2157,9 @@ impl Remote {
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(message::Union::file_response(fr)) => {
|
||||
Some(message::Union::FileResponse(fr)) => {
|
||||
match fr.union {
|
||||
Some(file_response::Union::dir(fd)) => {
|
||||
Some(file_response::Union::Dir(fd)) => {
|
||||
#[cfg(windows)]
|
||||
let entries = fd.entries.to_vec();
|
||||
#[cfg(not(windows))]
|
||||
@@ -2064,7 +2182,7 @@ impl Remote {
|
||||
job.files = entries;
|
||||
}
|
||||
}
|
||||
Some(file_response::Union::digest(digest)) => {
|
||||
Some(file_response::Union::Digest(digest)) => {
|
||||
if digest.is_upload {
|
||||
if let Some(job) = fs::get_job(digest.id, &mut self.read_jobs) {
|
||||
if let Some(file) = job.files().get(digest.file_num as usize) {
|
||||
@@ -2075,9 +2193,9 @@ impl Remote {
|
||||
id: digest.id,
|
||||
file_num: digest.file_num,
|
||||
union: Some(if overwrite {
|
||||
file_transfer_send_confirm_request::Union::offset_blk(0)
|
||||
file_transfer_send_confirm_request::Union::OffsetBlk(0)
|
||||
} else {
|
||||
file_transfer_send_confirm_request::Union::skip(
|
||||
file_transfer_send_confirm_request::Union::Skip(
|
||||
true,
|
||||
)
|
||||
}),
|
||||
@@ -2110,7 +2228,7 @@ impl Remote {
|
||||
let msg= new_send_confirm(FileTransferSendConfirmRequest {
|
||||
id: digest.id,
|
||||
file_num: digest.file_num,
|
||||
union: Some(file_transfer_send_confirm_request::Union::skip(true)),
|
||||
union: Some(file_transfer_send_confirm_request::Union::Skip(true)),
|
||||
..Default::default()
|
||||
});
|
||||
allow_err!(peer.send(&msg).await);
|
||||
@@ -2122,9 +2240,9 @@ impl Remote {
|
||||
id: digest.id,
|
||||
file_num: digest.file_num,
|
||||
union: Some(if overwrite {
|
||||
file_transfer_send_confirm_request::Union::offset_blk(0)
|
||||
file_transfer_send_confirm_request::Union::OffsetBlk(0)
|
||||
} else {
|
||||
file_transfer_send_confirm_request::Union::skip(true)
|
||||
file_transfer_send_confirm_request::Union::Skip(true)
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -2147,7 +2265,7 @@ impl Remote {
|
||||
FileTransferSendConfirmRequest {
|
||||
id: digest.id,
|
||||
file_num: digest.file_num,
|
||||
union: Some(file_transfer_send_confirm_request::Union::offset_blk(0)),
|
||||
union: Some(file_transfer_send_confirm_request::Union::OffsetBlk(0)),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
@@ -2162,7 +2280,7 @@ impl Remote {
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(file_response::Union::block(block)) => {
|
||||
Some(file_response::Union::Block(block)) => {
|
||||
log::info!(
|
||||
"file response block, file id:{}, file num: {}",
|
||||
block.id,
|
||||
@@ -2175,27 +2293,27 @@ impl Remote {
|
||||
self.update_jobs_status();
|
||||
}
|
||||
}
|
||||
Some(file_response::Union::done(d)) => {
|
||||
Some(file_response::Union::Done(d)) => {
|
||||
if let Some(job) = fs::get_job(d.id, &mut self.write_jobs) {
|
||||
job.modify_time();
|
||||
fs::remove_job(d.id, &mut self.write_jobs);
|
||||
}
|
||||
self.handle_job_status(d.id, d.file_num, None);
|
||||
}
|
||||
Some(file_response::Union::error(e)) => {
|
||||
Some(file_response::Union::Error(e)) => {
|
||||
self.handle_job_status(e.id, e.file_num, Some(e.error));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Some(message::Union::misc(misc)) => match misc.union {
|
||||
Some(misc::Union::audio_format(f)) => {
|
||||
Some(message::Union::Misc(misc)) => match misc.union {
|
||||
Some(misc::Union::AudioFormat(f)) => {
|
||||
self.audio_sender.send(MediaData::AudioFormat(f)).ok();
|
||||
}
|
||||
Some(misc::Union::chat_message(c)) => {
|
||||
Some(misc::Union::ChatMessage(c)) => {
|
||||
self.handler.call("newMessage", &make_args!(c.text));
|
||||
}
|
||||
Some(misc::Union::permission_info(p)) => {
|
||||
Some(misc::Union::PermissionInfo(p)) => {
|
||||
log::info!("Change permission {:?} -> {}", p.permission, p.enabled);
|
||||
match p.permission.enum_value_or_default() {
|
||||
Permission::Keyboard => {
|
||||
@@ -2221,9 +2339,13 @@ impl Remote {
|
||||
self.handler
|
||||
.call2("setPermission", &make_args!("file", p.enabled));
|
||||
}
|
||||
Permission::Restart => {
|
||||
self.handler
|
||||
.call2("setPermission", &make_args!("restart", p.enabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(misc::Union::switch_display(s)) => {
|
||||
Some(misc::Union::SwitchDisplay(s)) => {
|
||||
self.handler.call("switchDisplay", &make_args!(s.display));
|
||||
self.video_sender.send(MediaData::Reset).ok();
|
||||
if s.width > 0 && s.height > 0 {
|
||||
@@ -2239,27 +2361,27 @@ impl Remote {
|
||||
self.handler.set_display(s.x, s.y, s.width, s.height);
|
||||
}
|
||||
}
|
||||
Some(misc::Union::close_reason(c)) => {
|
||||
Some(misc::Union::CloseReason(c)) => {
|
||||
self.handler.msgbox("error", "Connection Error", &c);
|
||||
return false;
|
||||
}
|
||||
Some(misc::Union::back_notification(notification)) => {
|
||||
Some(misc::Union::BackNotification(notification)) => {
|
||||
if !self.handle_back_notification(notification).await {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(message::Union::test_delay(t)) => {
|
||||
Some(message::Union::TestDelay(t)) => {
|
||||
self.handler.handle_test_delay(t, peer).await;
|
||||
}
|
||||
Some(message::Union::audio_frame(frame)) => {
|
||||
Some(message::Union::AudioFrame(frame)) => {
|
||||
if !self.handler.lc.read().unwrap().disable_audio {
|
||||
self.audio_sender.send(MediaData::AudioFrame(frame)).ok();
|
||||
}
|
||||
}
|
||||
Some(message::Union::file_action(action)) => match action.union {
|
||||
Some(file_action::Union::send_confirm(c)) => {
|
||||
Some(message::Union::FileAction(action)) => match action.union {
|
||||
Some(file_action::Union::SendConfirm(c)) => {
|
||||
if let Some(job) = fs::get_job(c.id, &mut self.read_jobs) {
|
||||
job.confirm(&c);
|
||||
}
|
||||
@@ -2274,16 +2396,16 @@ impl Remote {
|
||||
|
||||
async fn handle_back_notification(&mut self, notification: BackNotification) -> bool {
|
||||
match notification.union {
|
||||
Some(back_notification::Union::block_input_state(state)) => {
|
||||
Some(back_notification::Union::BlockInputState(state)) => {
|
||||
self.handle_back_msg_block_input(
|
||||
state.enum_value_or(back_notification::BlockInputState::StateUnknown),
|
||||
state.enum_value_or(back_notification::BlockInputState::BlkStateUnknown),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Some(back_notification::Union::privacy_mode_state(state)) => {
|
||||
Some(back_notification::Union::PrivacyModeState(state)) => {
|
||||
if !self
|
||||
.handle_back_msg_privacy_mode(
|
||||
state.enum_value_or(back_notification::PrivacyModeState::StateUnknown),
|
||||
state.enum_value_or(back_notification::PrivacyModeState::PrvStateUnknown),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -2302,18 +2424,18 @@ impl Remote {
|
||||
|
||||
async fn handle_back_msg_block_input(&mut self, state: back_notification::BlockInputState) {
|
||||
match state {
|
||||
back_notification::BlockInputState::OnSucceeded => {
|
||||
back_notification::BlockInputState::BlkOnSucceeded => {
|
||||
self.update_block_input_state(true);
|
||||
}
|
||||
back_notification::BlockInputState::OnFailed => {
|
||||
back_notification::BlockInputState::BlkOnFailed => {
|
||||
self.handler
|
||||
.msgbox("custom-error", "Block user input", "Failed");
|
||||
self.update_block_input_state(false);
|
||||
}
|
||||
back_notification::BlockInputState::OffSucceeded => {
|
||||
back_notification::BlockInputState::BlkOffSucceeded => {
|
||||
self.update_block_input_state(false);
|
||||
}
|
||||
back_notification::BlockInputState::OffFailed => {
|
||||
back_notification::BlockInputState::BlkOffFailed => {
|
||||
self.handler
|
||||
.msgbox("custom-error", "Unblock user input", "Failed");
|
||||
}
|
||||
@@ -2335,7 +2457,7 @@ impl Remote {
|
||||
state: back_notification::PrivacyModeState,
|
||||
) -> bool {
|
||||
match state {
|
||||
back_notification::PrivacyModeState::OnByOther => {
|
||||
back_notification::PrivacyModeState::PrvOnByOther => {
|
||||
self.handler.msgbox(
|
||||
"error",
|
||||
"Connecting...",
|
||||
@@ -2343,46 +2465,46 @@ impl Remote {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
back_notification::PrivacyModeState::NotSupported => {
|
||||
back_notification::PrivacyModeState::PrvNotSupported => {
|
||||
self.handler
|
||||
.msgbox("custom-error", "Privacy mode", "Unsupported");
|
||||
self.update_privacy_mode(false);
|
||||
}
|
||||
back_notification::PrivacyModeState::OnSucceeded => {
|
||||
back_notification::PrivacyModeState::PrvOnSucceeded => {
|
||||
self.handler
|
||||
.msgbox("custom-nocancel", "Privacy mode", "In privacy mode");
|
||||
self.update_privacy_mode(true);
|
||||
}
|
||||
back_notification::PrivacyModeState::OnFailedDenied => {
|
||||
back_notification::PrivacyModeState::PrvOnFailedDenied => {
|
||||
self.handler
|
||||
.msgbox("custom-error", "Privacy mode", "Peer denied");
|
||||
self.update_privacy_mode(false);
|
||||
}
|
||||
back_notification::PrivacyModeState::OnFailedPlugin => {
|
||||
back_notification::PrivacyModeState::PrvOnFailedPlugin => {
|
||||
self.handler
|
||||
.msgbox("custom-error", "Privacy mode", "Please install plugins");
|
||||
self.update_privacy_mode(false);
|
||||
}
|
||||
back_notification::PrivacyModeState::OnFailed => {
|
||||
back_notification::PrivacyModeState::PrvOnFailed => {
|
||||
self.handler
|
||||
.msgbox("custom-error", "Privacy mode", "Failed");
|
||||
self.update_privacy_mode(false);
|
||||
}
|
||||
back_notification::PrivacyModeState::OffSucceeded => {
|
||||
back_notification::PrivacyModeState::PrvOffSucceeded => {
|
||||
self.handler
|
||||
.msgbox("custom-nocancel", "Privacy mode", "Out privacy mode");
|
||||
self.update_privacy_mode(false);
|
||||
}
|
||||
back_notification::PrivacyModeState::OffByPeer => {
|
||||
back_notification::PrivacyModeState::PrvOffByPeer => {
|
||||
self.handler
|
||||
.msgbox("custom-error", "Privacy mode", "Peer exit");
|
||||
self.update_privacy_mode(false);
|
||||
}
|
||||
back_notification::PrivacyModeState::OffFailed => {
|
||||
back_notification::PrivacyModeState::PrvOffFailed => {
|
||||
self.handler
|
||||
.msgbox("custom-error", "Privacy mode", "Failed to turn off");
|
||||
}
|
||||
back_notification::PrivacyModeState::OffUnknown => {
|
||||
back_notification::PrivacyModeState::PrvOffUnknown => {
|
||||
self.handler
|
||||
.msgbox("custom-error", "Privacy mode", "Turned off");
|
||||
// log::error!("Privacy mode is turned off with unknown reason");
|
||||
@@ -2540,8 +2662,8 @@ impl Interface for Handler {
|
||||
self.start_keyboard_hook();
|
||||
}
|
||||
|
||||
async fn handle_hash(&mut self, hash: Hash, peer: &mut Stream) {
|
||||
handle_hash(self.lc.clone(), hash, self, peer).await;
|
||||
async fn handle_hash(&mut self, pass: &str, hash: Hash, peer: &mut Stream) {
|
||||
handle_hash(self.lc.clone(), pass, hash, self, peer).await;
|
||||
}
|
||||
|
||||
async fn handle_login_from_ui(&mut self, password: String, remember: bool, peer: &mut Stream) {
|
||||
@@ -2549,7 +2671,14 @@ impl Interface for Handler {
|
||||
}
|
||||
|
||||
async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream) {
|
||||
handle_test_delay(t, peer).await;
|
||||
if !t.from_client {
|
||||
self.update_quality_status(QualityStatus {
|
||||
delay: Some(t.last_delay as _),
|
||||
target_bitrate: Some(t.target_bitrate as _),
|
||||
..Default::default()
|
||||
});
|
||||
handle_test_delay(t, peer).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ var keyboard_enabled = true; // server side
|
||||
var clipboard_enabled = true; // server side
|
||||
var audio_enabled = true; // server side
|
||||
var file_enabled = true; // server side
|
||||
var restart_enabled = true; // server side
|
||||
var scroll_body = $(body);
|
||||
|
||||
handler.setDisplay = function(x, y, w, h) {
|
||||
@@ -456,12 +457,56 @@ function self.closing() {
|
||||
if (is_file_transfer || is_port_forward || size_adapted) handler.save_size(x, y, w, h);
|
||||
}
|
||||
|
||||
var qualityMonitor;
|
||||
var qualityMonitorData = [];
|
||||
|
||||
class QualityMonitor: Reactor.Component
|
||||
{
|
||||
function this() {
|
||||
qualityMonitor = this;
|
||||
if (handler.get_toggle_option("show-quality-monitor")) {
|
||||
$(#quality-monitor).style.set{ display: "block" };
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
return <div >
|
||||
<div>
|
||||
Speed: {qualityMonitorData[0]}
|
||||
</div>
|
||||
<div>
|
||||
FPS: {qualityMonitorData[1]}
|
||||
</div>
|
||||
<div>
|
||||
Delay: {qualityMonitorData[2]} ms
|
||||
</div>
|
||||
<div>
|
||||
Target Bitrate: {qualityMonitorData[3]}kb
|
||||
</div>
|
||||
<div>
|
||||
Codec: {qualityMonitorData[4]}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
$(#quality-monitor).content(<QualityMonitor />);
|
||||
handler.updateQualityStatus = function(speed, fps, delay, bitrate, codec_format) {
|
||||
speed ? qualityMonitorData[0] = speed:null;
|
||||
fps ? qualityMonitorData[1] = fps:null;
|
||||
delay ? qualityMonitorData[2] = delay:null;
|
||||
bitrate ? qualityMonitorData[3] = bitrate:null;
|
||||
codec_format ? qualityMonitorData[4] = codec_format:null;
|
||||
qualityMonitor.update();
|
||||
}
|
||||
|
||||
handler.setPermission = function(name, enabled) {
|
||||
self.timer(60ms, function() {
|
||||
if (name == "keyboard") keyboard_enabled = enabled;
|
||||
if (name == "audio") audio_enabled = enabled;
|
||||
if (name == "file") file_enabled = enabled;
|
||||
if (name == "clipboard") clipboard_enabled = enabled;
|
||||
if (name == "restart") restart_enabled = enabled;
|
||||
input_blocked = false;
|
||||
header.update();
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::{
|
||||
use hbb_common::{allow_err, bail, lazy_static, log, tokio, ResultType};
|
||||
use std::{
|
||||
ffi::CString,
|
||||
os::windows::process::CommandExt,
|
||||
sync::Mutex,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
@@ -24,7 +25,9 @@ use winapi::{
|
||||
CreateProcessAsUserW, GetCurrentThreadId, QueueUserAPC, ResumeThread,
|
||||
PROCESS_INFORMATION, STARTUPINFOW,
|
||||
},
|
||||
winbase::{WTSGetActiveConsoleSessionId, CREATE_SUSPENDED, DETACHED_PROCESS},
|
||||
winbase::{
|
||||
WTSGetActiveConsoleSessionId, CREATE_NO_WINDOW, CREATE_SUSPENDED, DETACHED_PROCESS,
|
||||
},
|
||||
winnt::{MEM_COMMIT, PAGE_READWRITE},
|
||||
winuser::*,
|
||||
},
|
||||
@@ -317,6 +320,7 @@ fn wait_find_privacy_hwnd(msecs: u128) -> ResultType<HWND> {
|
||||
pub fn is_process_consent_running() -> ResultType<bool> {
|
||||
let output = std::process::Command::new("cmd")
|
||||
.args(&["/C", "tasklist | findstr consent.exe"])
|
||||
.creation_flags(CREATE_NO_WINDOW)
|
||||
.output()?;
|
||||
Ok(output.status.success() && !output.stdout.is_empty())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user