Merge pull request #628 from fufesou/simple_rc

Fix WCHAR Path & Add Resources Integration Lib
This commit is contained in:
RustDesk 2022-06-02 22:01:01 +08:00 committed by GitHub
commit 720b05874a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 2983 additions and 287 deletions

13
Cargo.lock generated
View File

@ -4093,11 +4093,13 @@ dependencies = [
"serde_derive",
"serde_json 1.0.79",
"sha2",
"simple_rc",
"sys-locale",
"sysinfo",
"tray-item",
"trayicon",
"uuid",
"virtual_display",
"whoami",
"winapi 0.3.9",
"windows-service",
@ -4447,6 +4449,17 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4"
[[package]]
name = "simple_rc"
version = "0.1.0"
dependencies = [
"confy",
"hbb_common",
"serde 1.0.136",
"serde_derive",
"walkdir",
]
[[package]]
name = "siphasher"
version = "0.2.3"

View File

@ -19,6 +19,7 @@ path = "src/naming.rs"
inline = []
hbbs = []
cli = []
with_rc = ["simple_rc"]
use_samplerate = ["samplerate"]
use_rubato = ["rubato"]
use_dasp = ["dasp"]
@ -79,6 +80,7 @@ winit = "0.25"
winapi = { version = "0.3", features = ["winuser"] }
winreg = "0.10"
windows-service = "0.4"
virtual_display = { path = "libs/virtual_display" }
[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2"
@ -103,7 +105,7 @@ jni = "0.19.0"
flutter_rust_bridge = "1.30.0"
[workspace]
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display"]
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/simple_rc"]
[package.metadata.winres]
LegalCopyright = "Copyright © 2022 Purslane, Inc."
@ -117,6 +119,7 @@ winapi = { version = "0.3", features = [ "winnt" ] }
[build-dependencies]
cc = "1.0"
hbb_common = { path = "libs/hbb_common" }
simple_rc = { path = "libs/simple_rc", optional = true }
flutter_rust_bridge_codegen = "1.30.0"
[dev-dependencies]

129
build.py
View File

@ -2,9 +2,11 @@
import os
import platform
import zlib
from shutil import copy2
import zipfile
import urllib.request
import shutil
import hashlib
import argparse
windows = platform.platform().startswith('Windows')
osx = platform.platform().startswith('Darwin') or platform.platform().startswith("macOS")
@ -20,7 +22,93 @@ def get_version():
return ''
def get_features(feature):
available_features = {
'IddDriver': {
'zip_url': 'https://github.com/fufesou/RustDeskIddDriver/releases/download/v0.1/RustDeskIddDriver_x64.zip',
'checksum_url': 'https://github.com/fufesou/RustDeskIddDriver/releases/download/v0.1'
'/RustDeskIddDriver_x64.zip.checksum_md5',
},
'PrivacyMode': {
'zip_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.1'
'/TempTopMostWindow_x64.zip',
'checksum_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.1'
'/TempTopMostWindow_x64.zip.checksum_md5',
}
}
apply_features = {}
if not feature:
return apply_features
elif isinstance(feature, str) and feature.upper() == 'ALL':
return available_features
elif isinstance(feature, list):
for feat in feature:
if isinstance(feat, str) and feat.upper() == 'ALL':
return available_features
if feat in available_features:
apply_features[feat] = available_features[feat]
else:
print(f'Unrecognized feature {feat}')
return apply_features
else:
raise Exception(f'Unsupported features param {feature}')
def make_parser():
parser = argparse.ArgumentParser(description='Build script.')
parser.add_argument(
'-f',
'--feature',
dest='feature',
metavar='N',
type=str,
nargs='+',
default='',
help='Integrate features, windows only.'
'Available: IddDriver, PrivacyMode. Special value is "ALL" and empty "". Default is empty.')
return parser
def download_extract_features(features, res_dir):
for (feat, feat_info) in features.items():
print(f'{feat} download begin')
checksum_md5_response = urllib.request.urlopen(feat_info['checksum_url'])
checksum_md5 = checksum_md5_response.read().decode('utf-8').split()[0]
download_filename = feat_info['zip_url'].split('/')[-1]
filename, _headers = urllib.request.urlretrieve(feat_info['zip_url'], download_filename)
md5 = hashlib.md5(open(filename, 'rb').read()).hexdigest()
if checksum_md5 != md5:
raise Exception(f'{feat} download failed')
print(f'{feat} download end. extract bein')
zip_file = zipfile.ZipFile(filename)
zip_list = zip_file.namelist()
for f in zip_list:
zip_file.extract(f, res_dir)
zip_file.close()
os.remove(download_filename)
print(f'{feat} extract end')
def build_windows(feature):
features = get_features(feature)
if not features:
os.system('cargo build --release --features inline')
else:
print(f'Build with features {list(features.keys())}')
res_dir = 'resources'
if os.path.isdir(res_dir) and not os.path.islink(res_dir):
shutil.rmtree(res_dir)
elif os.path.exists(res_dir):
raise Exception(f'Find file {res_dir}, not a directory')
os.makedirs(res_dir, exist_ok=True)
download_extract_features(features, res_dir)
os.system('cargo build --release --features inline,with_rc')
def main():
parser = make_parser()
args = parser.parse_args()
os.system("cp Cargo.toml Cargo.toml.bk")
os.system("cp src/main.rs src/main.rs.bk")
if windows:
@ -35,39 +123,40 @@ def main():
os.system('git checkout src/ui/common.tis')
version = get_version()
if windows:
os.system('cargo build --release --features inline')
build_windows(args.feature)
# os.system('upx.exe target/release/rustdesk.exe')
os.system('mv target/release/rustdesk.exe target/release/RustDesk.exe')
pa = os.environ.get('P')
if pa:
os.system('signtool sign /a /v /p %s /debug /f .\\cert.pfx /t http://timestamp.digicert.com target\\release\\rustdesk.exe'%pa)
os.system(f'signtool sign /a /v /p {pa} /debug /f .\\cert.pfx /t http://timestamp.digicert.com '
'target\\release\\rustdesk.exe')
else:
print('Not signed')
os.system('cp -rf target/release/RustDesk.exe rustdesk-%s-putes.exe'%version)
print('Not signed')
os.system(f'cp -rf target/release/RustDesk.exe rustdesk-{version}-putes.exe')
elif os.path.isfile('/usr/bin/pacman'):
os.system('cargo build --release --features inline')
os.system('git checkout src/ui/common.tis')
os.system('strip target/release/rustdesk')
os.system("sed -i 's/pkgver=.*/pkgver=%s/g' PKGBUILD"%version)
os.system("sed -i 's/pkgver=.*/pkgver=%s/g' PKGBUILD" % version)
# pacman -S -needed base-devel
os.system('HBB=`pwd` makepkg -f')
os.system('mv rustdesk-%s-0-x86_64.pkg.tar.zst rustdesk-%s-manjaro-arch.pkg.tar.zst'%(version, version))
os.system('mv rustdesk-%s-0-x86_64.pkg.tar.zst rustdesk-%s-manjaro-arch.pkg.tar.zst' % (version, version))
# pacman -U ./rustdesk.pkg.tar.zst
elif os.path.isfile('/usr/bin/yum'):
os.system('cargo build --release --features inline')
os.system('strip target/release/rustdesk')
os.system("sed -i 's/Version: .*/Version: %s/g' rpm.spec"%version)
os.system("sed -i 's/Version: .*/Version: %s/g' rpm.spec" % version)
os.system('HBB=`pwd` rpmbuild -ba rpm.spec')
os.system('mv $HOME/rpmbuild/RPMS/x86_64/rustdesk-%s-0.x86_64.rpm ./rustdesk-%s-fedora28-centos8.rpm'%(version, version))
os.system('mv $HOME/rpmbuild/RPMS/x86_64/rustdesk-%s-0.x86_64.rpm ./rustdesk-%s-fedora28-centos8.rpm' % (
version, version))
# yum localinstall rustdesk.rpm
elif os.path.isfile('/usr/bin/zypper'):
os.system('cargo build --release --features inline')
os.system('strip target/release/rustdesk')
os.system("sed -i 's/Version: .*/Version: %s/g' rpm-suse.spec"%version)
os.system("sed -i 's/Version: .*/Version: %s/g' rpm-suse.spec" % version)
os.system('HBB=`pwd` rpmbuild -ba rpm-suse.spec')
os.system('mv $HOME/rpmbuild/RPMS/x86_64/rustdesk-%s-0.x86_64.rpm ./rustdesk-%s-suse.rpm'%(version, version))
os.system('mv $HOME/rpmbuild/RPMS/x86_64/rustdesk-%s-0.x86_64.rpm ./rustdesk-%s-suse.rpm' % (version, version))
# yum localinstall rustdesk.rpm
else:
os.system('cargo bundle --release --features inline')
if osx:
@ -81,12 +170,12 @@ def main():
txt = open(plist).read()
with open(plist, "wt") as fh:
fh.write(txt.replace("</dict>", """
<key>LSUIElement</key>
<string>1</string>
<key>LSUIElement</key>
<string>1</string>
</dict>"""))
pa = os.environ.get('P')
if pa:
os.system('''
os.system('''
# buggy: rcodesign sign ... path/*, have to sign one by one
#rcodesign sign --p12-file ~/.p12/rustdesk-developer-id.p12 --p12-password-file ~/.p12/.cert-pass --code-signature-flags runtime ./target/release/bundle/osx/RustDesk.app/Contents/MacOS/rustdesk
#rcodesign sign --p12-file ~/.p12/rustdesk-developer-id.p12 --p12-password-file ~/.p12/.cert-pass --code-signature-flags runtime ./target/release/bundle/osx/RustDesk.app/Contents/MacOS/libsciter.dylib
@ -96,9 +185,9 @@ codesign -s "Developer ID Application: {0}" --force --options runtime ./target/
codesign -s "Developer ID Application: {0}" --force --options runtime ./target/release/bundle/osx/RustDesk.app
'''.format(pa))
os.system('create-dmg target/release/bundle/osx/RustDesk.app')
os.rename('RustDesk %s.dmg'%version, 'rustdesk-%s.dmg'%version)
os.rename('RustDesk %s.dmg' % version, 'rustdesk-%s.dmg' % version)
if pa:
os.system('''
os.system('''
#rcodesign sign --p12-file ~/.p12/rustdesk-developer-id.p12 --p12-password-file ~/.p12/.cert-pass --code-signature-flags runtime ./rustdesk-{1}.dmg
codesign -s "Developer ID Application: {0}" --force --options runtime ./rustdesk-{1}.dmg
# https://pyoxidizer.readthedocs.io/en/latest/apple_codesign_rcodesign.html
@ -106,7 +195,7 @@ rcodesign notarize --api-issuer 69a6de7d-2907-47e3-e053-5b8c7c11a4d1 --api-key 9
# verify: spctl -a -t exec -v /Applications/RustDesk.app
'''.format(pa, version))
else:
print('Not signed')
print('Not signed')
else:
os.system('mv target/release/bundle/deb/rustdesk*.deb ./rustdesk.deb')
os.system('dpkg-deb -R rustdesk.deb tmpdeb')
@ -122,7 +211,7 @@ rcodesign notarize --api-issuer 69a6de7d-2907-47e3-e053-5b8c7c11a4d1 --api-key 9
md5_file('usr/share/rustdesk/files/pynput_service.py')
md5_file('usr/lib/rustdesk/libsciter-gtk.so')
os.system('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/')
os.rename('rustdesk.deb', 'rustdesk-%s.deb'%version)
os.rename('rustdesk.deb', 'rustdesk-%s.deb' % version)
os.system("mv Cargo.toml.bk Cargo.toml")
os.system("mv src/main.rs.bk src/main.rs")

View File

@ -27,6 +27,20 @@ fn build_manifest() {
}
}
#[cfg(all(windows, feature = "with_rc"))]
fn build_rc_source() {
use simple_rc::{generate_with_conf, Config, ConfigItem};
generate_with_conf(&Config {
outfile: "src/rc.rs".to_owned(),
confs: vec![ConfigItem {
inc: "resources".to_owned(),
exc: vec![],
suppressed_front: "resources".to_owned(),
}],
})
.unwrap();
}
fn install_oboe() {
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
if target_os != "android" {
@ -87,6 +101,8 @@ fn main() {
gen_flutter_rust_bridge();
return;
}
#[cfg(all(windows, feature = "with_rc"))]
build_rc_source();
#[cfg(all(windows, feature = "inline"))]
build_manifest();
#[cfg(windows)]

View File

@ -63,6 +63,8 @@ extern crate objc;
mod win;
#[cfg(target_os = "windows")]
pub use win::Enigo;
#[cfg(target_os = "windows")]
pub use win::ENIGO_INPUT_EXTRA_VALUE;
#[cfg(target_os = "macos")]
mod macos;

View File

@ -2,3 +2,4 @@ mod win_impl;
pub mod keycodes;
pub use self::win_impl::Enigo;
pub use self::win_impl::ENIGO_INPUT_EXTRA_VALUE;

View File

@ -1,7 +1,7 @@
use winapi;
use self::winapi::ctypes::c_int;
use self::winapi::shared::{minwindef::*, windef::*};
use self::winapi::shared::{basetsd::ULONG_PTR, minwindef::*, windef::*};
use self::winapi::um::winbase::*;
use self::winapi::um::winuser::*;
@ -18,6 +18,9 @@ extern "system" {
pub struct Enigo;
static mut LAYOUT: HKL = std::ptr::null_mut();
/// The dwExtraInfo value in keyboard and mouse structure that used in SendInput()
pub const ENIGO_INPUT_EXTRA_VALUE: ULONG_PTR = 100;
fn mouse_event(flags: u32, data: u32, dx: i32, dy: i32) -> DWORD {
let mut input = INPUT {
type_: INPUT_MOUSE,
@ -28,7 +31,7 @@ fn mouse_event(flags: u32, data: u32, dx: i32, dy: i32) -> DWORD {
mouseData: data,
dwFlags: flags,
time: 0,
dwExtraInfo: 0,
dwExtraInfo: ENIGO_INPUT_EXTRA_VALUE,
})
},
};
@ -56,7 +59,7 @@ fn keybd_event(flags: u32, vk: u16, scan: u16) -> DWORD {
wScan: scan,
dwFlags: flags,
time: 0,
dwExtraInfo: 0,
dwExtraInfo: ENIGO_INPUT_EXTRA_VALUE,
})
},
};

View File

@ -65,6 +65,10 @@ message LoginRequest {
message ChatMessage { string text = 1; }
message Features {
bool privacy_mode = 1;
}
message PeerInfo {
string username = 1;
string hostname = 2;
@ -74,6 +78,7 @@ message PeerInfo {
bool sas_enabled = 6;
string version = 7;
int32 conn_id = 8;
Features features = 9;
}
message LoginResponse {
@ -442,11 +447,6 @@ message OptionMessage {
BoolOption enable_file_transfer = 9;
}
message OptionResponse {
OptionMessage opt = 1;
string error = 2;
}
message TestDelay {
int64 time = 1;
bool from_client = 2;
@ -469,6 +469,44 @@ message AudioFrame {
int64 timestamp = 2;
}
message BackNotification {
// no need to consider block input by someone else
enum BlockInputState {
StateUnknown = 1;
OnSucceeded = 2;
OnFailed = 3;
OffSucceeded = 4;
OffFailed = 5;
}
enum PrivacyModeState {
StateUnknown = 1;
// Privacy mode on by someone else
OnByOther = 2;
// Privacy mode is not supported on the remote side
NotSupported = 3;
// Privacy mode on by self
OnSucceeded = 4;
// Privacy mode on by self, but denied
OnFailedDenied = 5;
// Some plugins are not found
OnFailedPlugin = 6;
// Privacy mode on by self, but failed
OnFailed = 7;
// Privacy mode off by self
OffSucceeded = 8;
// Ctrl + P
OffByPeer = 9;
// Privacy mode off by self, but failed
OffFailed = 10;
OffUnknown = 11;
}
oneof union {
PrivacyModeState privacy_mode_state = 1;
BlockInputState block_input_state = 2;
}
}
message Misc {
oneof union {
ChatMessage chat_message = 4;
@ -478,8 +516,8 @@ message Misc {
AudioFormat audio_format = 8;
string close_reason = 9;
bool refresh_video = 10;
OptionResponse option_response = 11;
bool video_received = 12;
BackNotification back_notification = 13;
}
}

View File

@ -17,11 +17,12 @@ block = "0.1"
cfg-if = "1.0"
libc = "0.2"
num_cpus = "1.13"
lazy_static = "1.4"
[dependencies.winapi]
version = "0.3"
default-features = true
features = ["dxgi", "dxgi1_2", "dxgi1_5", "d3d11", "winuser"]
features = ["dxgi", "dxgi1_2", "dxgi1_5", "d3d11", "winuser", "winerror", "errhandlingapi", "libloaderapi"]
[target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.10"

View File

@ -0,0 +1,105 @@
extern crate repng;
extern crate scrap;
use std::fs::File;
#[cfg(windows)]
use scrap::CapturerMag;
use scrap::{i420_to_rgb, Display};
fn main() {
let n = Display::all().unwrap().len();
for i in 0..n {
#[cfg(windows)]
record(i);
}
}
fn get_display(i: usize) -> Display {
Display::all().unwrap().remove(i)
}
#[cfg(windows)]
fn record(i: usize) {
for d in Display::all().unwrap() {
println!("{:?} {} {}", d.origin(), d.width(), d.height());
}
let display = get_display(i);
let (w, h) = (display.width(), display.height());
{
let mut capture_mag =
CapturerMag::new(display.origin(), display.width(), display.height(), false)
.expect("Couldn't begin capture.");
let wnd_cls = "";
let wnd_name = "RustDeskPrivacyWindow";
if false == capture_mag.exclude(wnd_cls, wnd_name).unwrap() {
println!("No window found for cls {} name {}", wnd_cls, wnd_name);
} else {
println!("Filter window for cls {} name {}", wnd_cls, wnd_name);
}
let frame = capture_mag.frame(0).unwrap();
println!("Capture data len: {}, Saving...", frame.len());
let mut bitflipped = Vec::with_capacity(w * h * 4);
let stride = frame.len() / h;
for y in 0..h {
for x in 0..w {
let i = stride * y + 4 * x;
bitflipped.extend_from_slice(&[frame[i + 2], frame[i + 1], frame[i], 255]);
}
}
// Save the image.
let name = format!("capture_mag_{}_1.png", i);
repng::encode(
File::create(name.clone()).unwrap(),
w as u32,
h as u32,
&bitflipped,
)
.unwrap();
println!("Image saved to `{}`.", name);
}
{
let mut capture_mag =
CapturerMag::new(display.origin(), display.width(), display.height(), true)
.expect("Couldn't begin capture.");
let wnd_cls = "";
let wnd_title = "RustDeskPrivacyWindow";
if false == capture_mag.exclude(wnd_cls, wnd_title).unwrap() {
println!("No window found for cls {} title {}", wnd_cls, wnd_title);
} else {
println!("Filter window for cls {} title {}", wnd_cls, wnd_title);
}
let buffer = capture_mag.frame(0).unwrap();
println!("Capture data len: {}, Saving...", buffer.len());
let mut frame = Default::default();
i420_to_rgb(w, h, &buffer, &mut frame);
let mut bitflipped = Vec::with_capacity(w * h * 4);
let stride = frame.len() / h;
for y in 0..h {
for x in 0..w {
let i = stride * y + 3 * x;
bitflipped.extend_from_slice(&[frame[i], frame[i + 1], frame[i + 2], 255]);
}
}
let name = format!("capture_mag_{}_2.png", i);
repng::encode(
File::create(name.clone()).unwrap(),
w as u32,
h as u32,
&bitflipped,
)
.unwrap();
println!("Image saved to `{}`.", name);
}
}

View File

@ -46,8 +46,7 @@ fn record(i: usize) {
}
}
};
println!("Captured! Saving...");
println!("Captured data len: {}, Saving...", buffer.len());
// Flip the BGRA image into a RGBA image.
@ -96,8 +95,7 @@ fn record(i: usize) {
}
}
};
println!("Captured! Saving...");
println!("Captured data len: {}, Saving...", buffer.len());
let mut frame = Default::default();
i420_to_rgb(w, h, &buffer, &mut frame);

View File

@ -111,3 +111,32 @@ impl Display {
self.origin() == (0, 0)
}
}
pub struct CapturerMag {
inner: dxgi::mag::CapturerMag,
data: Vec<u8>,
}
impl CapturerMag {
pub fn is_supported() -> bool {
dxgi::mag::CapturerMag::is_supported()
}
pub fn new(origin: (i32, i32), width: usize, height: usize, use_yuv: bool) -> io::Result<Self> {
Ok(CapturerMag {
inner: dxgi::mag::CapturerMag::new(origin, width, height, use_yuv)?,
data: Vec::new(),
})
}
pub fn exclude(&mut self, cls: &str, name: &str) -> io::Result<bool> {
self.inner.exclude(cls, name)
}
// ((x, y), w, h)
pub fn get_rect(&self) -> ((i32, i32), usize, usize) {
self.inner.get_rect()
}
pub fn frame<'a>(&'a mut self, _timeout_ms: u32) -> io::Result<Frame<'a>> {
self.inner.frame(&mut self.data)?;
Ok(Frame(&self.data))
}
}

662
libs/scrap/src/dxgi/mag.rs Normal file
View File

@ -0,0 +1,662 @@
// logic from webrtc -- https://github.com/shiguredo/libwebrtc/blob/main/modules/desktop_capture/win/screen_capturer_win_magnifier.cc
use lazy_static;
use std::{
ffi::CString,
io::{Error, ErrorKind, Result},
mem::size_of,
sync::Mutex,
};
use winapi::{
shared::{
basetsd::SIZE_T,
guiddef::{IsEqualGUID, GUID},
minwindef::{BOOL, DWORD, FALSE, FARPROC, HINSTANCE, HMODULE, HRGN, TRUE, UINT},
ntdef::{LONG, NULL},
windef::{HWND, RECT},
winerror::ERROR_CLASS_ALREADY_EXISTS,
},
um::{
errhandlingapi::GetLastError,
libloaderapi::{FreeLibrary, GetModuleHandleExA, GetProcAddress, LoadLibraryExA},
winuser::*,
},
};
pub const MW_FILTERMODE_EXCLUDE: u32 = 0;
pub const MW_FILTERMODE_INCLUDE: u32 = 1;
pub const GET_MODULE_HANDLE_EX_FLAG_PIN: u32 = 1;
pub const GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT: u32 = 2;
pub const GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS: u32 = 4;
pub const LOAD_LIBRARY_AS_DATAFILE: u32 = 2;
pub const LOAD_WITH_ALTERED_SEARCH_PATH: u32 = 8;
pub const LOAD_IGNORE_CODE_AUTHZ_LEVEL: u32 = 16;
pub const LOAD_LIBRARY_AS_IMAGE_RESOURCE: u32 = 32;
pub const LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE: u32 = 64;
pub const LOAD_LIBRARY_REQUIRE_SIGNED_TARGET: u32 = 128;
pub const LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR: u32 = 256;
pub const LOAD_LIBRARY_SEARCH_APPLICATION_DIR: u32 = 512;
pub const LOAD_LIBRARY_SEARCH_USER_DIRS: u32 = 1024;
pub const LOAD_LIBRARY_SEARCH_SYSTEM32: u32 = 2048;
pub const LOAD_LIBRARY_SEARCH_DEFAULT_DIRS: u32 = 4096;
pub const LOAD_LIBRARY_SAFE_CURRENT_DIRS: u32 = 8192;
pub const LOAD_LIBRARY_SEARCH_SYSTEM32_NO_FORWARDER: u32 = 16384;
pub const LOAD_LIBRARY_OS_INTEGRITY_CONTINUITY: u32 = 32768;
extern "C" {
pub static GUID_WICPixelFormat32bppRGBA: GUID;
}
lazy_static::lazy_static! {
static ref MAG_BUFFER: Mutex<(bool, Vec<u8>)> = Default::default();
}
pub type REFWICPixelFormatGUID = *const GUID;
pub type WICPixelFormatGUID = GUID;
#[allow(non_snake_case)]
#[repr(C)]
#[derive(Copy, Clone)]
pub struct tagMAGIMAGEHEADER {
pub width: UINT,
pub height: UINT,
pub format: WICPixelFormatGUID,
pub stride: UINT,
pub offset: UINT,
pub cbSize: SIZE_T,
}
pub type MAGIMAGEHEADER = tagMAGIMAGEHEADER;
pub type PMAGIMAGEHEADER = *mut tagMAGIMAGEHEADER;
// Function types
pub type MagImageScalingCallback = ::std::option::Option<
unsafe extern "C" fn(
hwnd: HWND,
srcdata: *mut ::std::os::raw::c_void,
srcheader: MAGIMAGEHEADER,
destdata: *mut ::std::os::raw::c_void,
destheader: MAGIMAGEHEADER,
unclipped: RECT,
clipped: RECT,
dirty: HRGN,
) -> BOOL,
>;
extern "C" {
pub fn MagShowSystemCursor(fShowCursor: BOOL) -> BOOL;
}
pub type MagInitializeFunc = ::std::option::Option<unsafe extern "C" fn() -> BOOL>;
pub type MagUninitializeFunc = ::std::option::Option<unsafe extern "C" fn() -> BOOL>;
pub type MagSetWindowSourceFunc =
::std::option::Option<unsafe extern "C" fn(hwnd: HWND, rect: RECT) -> BOOL>;
pub type MagSetWindowFilterListFunc = ::std::option::Option<
unsafe extern "C" fn(
hwnd: HWND,
dwFilterMode: DWORD,
count: ::std::os::raw::c_int,
pHWND: *mut HWND,
) -> BOOL,
>;
pub type MagSetImageScalingCallbackFunc = ::std::option::Option<
unsafe extern "C" fn(hwnd: HWND, callback: MagImageScalingCallback) -> BOOL,
>;
#[repr(C)]
#[derive(Debug, Clone)]
struct MagInterface {
init_succeeded: bool,
lib_handle: HINSTANCE,
pub mag_initialize_func: MagInitializeFunc,
pub mag_uninitialize_func: MagUninitializeFunc,
pub set_window_source_func: MagSetWindowSourceFunc,
pub set_window_filter_list_func: MagSetWindowFilterListFunc,
pub set_image_scaling_callback_func: MagSetImageScalingCallbackFunc,
}
// NOTE: MagInitialize and MagUninitialize should not be called in global init and uninit.
// If so, strange errors occur.
impl MagInterface {
fn new() -> Result<Self> {
let mut s = MagInterface {
init_succeeded: false,
lib_handle: NULL as _,
mag_initialize_func: None,
mag_uninitialize_func: None,
set_window_source_func: None,
set_window_filter_list_func: None,
set_image_scaling_callback_func: None,
};
s.init_succeeded = false;
unsafe {
if GetSystemMetrics(SM_CMONITORS) != 1 {
// Do not try to use the magnifier in multi-screen setup (where the API
// crashes sometimes).
return Err(Error::new(
ErrorKind::Other,
"Magnifier capturer cannot work on multi-screen system.",
));
}
// load lib
let lib_file_name = "Magnification.dll";
let lib_file_name_c = CString::new(lib_file_name).unwrap();
s.lib_handle = LoadLibraryExA(
lib_file_name_c.as_ptr() as _,
NULL,
LOAD_WITH_ALTERED_SEARCH_PATH,
);
if s.lib_handle.is_null() {
return Err(Error::new(
ErrorKind::Other,
format!(
"Failed to LoadLibraryExA {}, error: {}",
lib_file_name,
GetLastError()
),
));
};
// load functions
s.mag_initialize_func = Some(std::mem::transmute(Self::load_func(
s.lib_handle,
"MagInitialize",
)?));
s.mag_uninitialize_func = Some(std::mem::transmute(Self::load_func(
s.lib_handle,
"MagUninitialize",
)?));
s.set_window_source_func = Some(std::mem::transmute(Self::load_func(
s.lib_handle,
"MagSetWindowSource",
)?));
s.set_window_filter_list_func = Some(std::mem::transmute(Self::load_func(
s.lib_handle,
"MagSetWindowFilterList",
)?));
s.set_image_scaling_callback_func = Some(std::mem::transmute(Self::load_func(
s.lib_handle,
"MagSetImageScalingCallback",
)?));
// MagInitialize
if let Some(init_func) = s.mag_initialize_func {
if FALSE == init_func() {
return Err(Error::new(
ErrorKind::Other,
format!("Failed to MagInitialize, error: {}", GetLastError()),
));
} else {
s.init_succeeded = true;
}
} else {
return Err(Error::new(
ErrorKind::Other,
"Unreachable, mag_initialize_func should not be none",
));
}
}
Ok(s)
}
unsafe fn load_func(lib_module: HMODULE, func_name: &str) -> Result<FARPROC> {
let func_name_c = CString::new(func_name).unwrap();
let func = GetProcAddress(lib_module, func_name_c.as_ptr() as _);
if func.is_null() {
return Err(Error::new(
ErrorKind::Other,
format!(
"Failed to GetProcAddress {}, error: {}",
func_name,
GetLastError()
),
));
}
Ok(func)
}
pub(super) fn uninit(&mut self) {
if self.init_succeeded {
if let Some(uninit_func) = self.mag_uninitialize_func {
unsafe {
if FALSE == uninit_func() {
println!("Failed MagUninitialize {}", GetLastError())
}
}
}
if !self.lib_handle.is_null() {
unsafe {
if FALSE == FreeLibrary(self.lib_handle) {
println!("Failed FreeLibrary {}", GetLastError())
}
}
self.lib_handle = NULL as _;
}
}
self.init_succeeded = false;
}
}
impl Drop for MagInterface {
fn drop(&mut self) {
self.uninit();
}
}
pub struct CapturerMag {
mag_interface: MagInterface,
host_window: HWND,
magnifier_window: HWND,
magnifier_host_class: CString,
host_window_name: CString,
magnifier_window_class: CString,
magnifier_window_name: CString,
rect: RECT,
width: usize,
height: usize,
use_yuv: bool,
data: Vec<u8>,
}
impl Drop for CapturerMag {
fn drop(&mut self) {
self.destroy_windows();
self.mag_interface.uninit();
}
}
impl CapturerMag {
pub(crate) fn is_supported() -> bool {
MagInterface::new().is_ok()
}
pub(crate) fn new(
origin: (i32, i32),
width: usize,
height: usize,
use_yuv: bool,
) -> Result<Self> {
unsafe {
let x = GetSystemMetrics(SM_XVIRTUALSCREEN);
let y = GetSystemMetrics(SM_YVIRTUALSCREEN);
let w = GetSystemMetrics(SM_CXVIRTUALSCREEN);
let h = GetSystemMetrics(SM_CYVIRTUALSCREEN);
if !(origin.0 == x as _ && origin.1 == y as _ && width == w as _ && height == h as _) {
return Err(Error::new(
ErrorKind::Other,
format!(
"Failed Check screen rect ({}, {}, {} , {}) to ({}, {}, {}, {})",
origin.0,
origin.1,
origin.0 + width as i32,
origin.1 + height as i32,
x,
y,
x + w,
y + h
),
));
}
}
let mut s = Self {
mag_interface: MagInterface::new()?,
host_window: 0 as _,
magnifier_window: 0 as _,
magnifier_host_class: CString::new("ScreenCapturerWinMagnifierHost")?,
host_window_name: CString::new("MagnifierHost")?,
magnifier_window_class: CString::new("Magnifier")?,
magnifier_window_name: CString::new("MagnifierWindow")?,
rect: RECT {
left: origin.0 as _,
top: origin.1 as _,
right: origin.0 + width as LONG,
bottom: origin.1 + height as LONG,
},
width,
height,
use_yuv,
data: Vec::new(),
};
unsafe {
let mut instance = 0 as HMODULE;
if 0 == GetModuleHandleExA(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
| GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
DefWindowProcA as _,
&mut instance as _,
) {
return Err(Error::new(
ErrorKind::Other,
format!("Failed to GetModuleHandleExA, error: {}", GetLastError()),
));
}
// Register the host window class. See the MSDN documentation of the
// Magnification API for more infomation.
let wcex = WNDCLASSEXA {
cbSize: size_of::<WNDCLASSEXA>() as _,
style: 0,
lpfnWndProc: Some(DefWindowProcA),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: instance,
hIcon: 0 as _,
hCursor: LoadCursorA(NULL as _, IDC_ARROW as _),
hbrBackground: 0 as _,
lpszClassName: s.magnifier_host_class.as_ptr() as _,
lpszMenuName: 0 as _,
hIconSm: 0 as _,
};
// Ignore the error which may happen when the class is already registered.
if 0 == RegisterClassExA(&wcex) {
let code = GetLastError();
if code != ERROR_CLASS_ALREADY_EXISTS {
return Err(Error::new(
ErrorKind::Other,
format!("Failed to RegisterClassExA, error: {}", code),
));
}
}
// Create the host window.
s.host_window = CreateWindowExA(
WS_EX_LAYERED,
s.magnifier_host_class.as_ptr(),
s.host_window_name.as_ptr(),
WS_POPUP,
0,
0,
0,
0,
NULL as _,
NULL as _,
instance,
NULL,
);
if s.host_window.is_null() {
return Err(Error::new(
ErrorKind::Other,
format!(
"Failed to CreateWindowExA host_window, error: {}",
GetLastError()
),
));
}
// Create the magnifier control.
s.magnifier_window = CreateWindowExA(
0,
s.magnifier_window_class.as_ptr(),
s.magnifier_window_name.as_ptr(),
WS_CHILD | WS_VISIBLE,
0,
0,
0,
0,
s.host_window,
NULL as _,
instance,
NULL,
);
if s.magnifier_window.is_null() {
return Err(Error::new(
ErrorKind::Other,
format!(
"Failed CreateWindowA magnifier_window, error: {}",
GetLastError()
),
));
}
// Hide the host window.
let _ = ShowWindow(s.host_window, SW_HIDE);
// Set the scaling callback to receive captured image.
if let Some(set_callback_func) = s.mag_interface.set_image_scaling_callback_func {
if FALSE
== set_callback_func(
s.magnifier_window,
Some(Self::on_gag_image_scaling_callback),
)
{
return Err(Error::new(
ErrorKind::Other,
format!(
"Failed to MagSetImageScalingCallback, error: {}",
GetLastError()
),
));
}
} else {
return Err(Error::new(
ErrorKind::Other,
"Unreachable, set_image_scaling_callback_func should not be none",
));
}
}
Ok(s)
}
pub(crate) fn exclude(&mut self, cls: &str, name: &str) -> Result<bool> {
let name_c = CString::new(name).unwrap();
unsafe {
let mut hwnd = if cls.len() == 0 {
FindWindowExA(NULL as _, NULL as _, NULL as _, name_c.as_ptr())
} else {
let cls_c = CString::new(cls).unwrap();
FindWindowExA(NULL as _, NULL as _, cls_c.as_ptr(), name_c.as_ptr())
};
if hwnd.is_null() {
return Ok(false);
}
if let Some(set_window_filter_list_func) =
self.mag_interface.set_window_filter_list_func
{
if FALSE
== set_window_filter_list_func(
self.magnifier_window,
MW_FILTERMODE_EXCLUDE,
1,
&mut hwnd,
)
{
return Err(Error::new(
ErrorKind::Other,
format!(
"Failed MagSetWindowFilterList for cls {} name {}, err: {}",
cls,
name,
GetLastError()
),
));
}
} else {
return Err(Error::new(
ErrorKind::Other,
"Unreachable, MagSetWindowFilterList should not be none",
));
}
}
Ok(true)
}
pub(crate) fn get_rect(&self) -> ((i32, i32), usize, usize) {
(
(self.rect.left as _, self.rect.top as _),
self.width as _,
self.height as _,
)
}
fn clear_data() {
let mut lock = MAG_BUFFER.lock().unwrap();
lock.0 = false;
lock.1.clear();
}
pub(crate) fn frame(&mut self, data: &mut Vec<u8>) -> Result<()> {
Self::clear_data();
unsafe {
let x = GetSystemMetrics(SM_XVIRTUALSCREEN);
let y = GetSystemMetrics(SM_YVIRTUALSCREEN);
let w = GetSystemMetrics(SM_CXVIRTUALSCREEN);
let h = GetSystemMetrics(SM_CYVIRTUALSCREEN);
if !(self.rect.left == x as _
&& self.rect.top == y as _
&& self.rect.right == (x + w) as _
&& self.rect.bottom == (y + h) as _)
{
return Err(Error::new(
ErrorKind::Other,
format!(
"Failed Check screen rect ({}, {}, {} , {}) to ({}, {}, {}, {})",
self.rect.left,
self.rect.top,
self.rect.right,
self.rect.bottom,
x,
y,
x + w,
y + h
),
));
}
if FALSE
== SetWindowPos(
self.magnifier_window,
HWND_TOP,
self.rect.left,
self.rect.top,
self.rect.right,
self.rect.bottom,
0,
)
{
return Err(Error::new(
ErrorKind::Other,
format!(
"Failed SetWindowPos (x, y, w , h) - ({}, {}, {}, {}), error {}",
self.rect.left,
self.rect.top,
self.rect.right,
self.rect.bottom,
GetLastError()
),
));
}
// on_gag_image_scaling_callback will be called and fill in the
// frame before set_window_source_func_ returns.
if let Some(set_window_source_func) = self.mag_interface.set_window_source_func {
if FALSE == set_window_source_func(self.magnifier_window, self.rect) {
return Err(Error::new(
ErrorKind::Other,
format!("Failed to MagSetWindowSource, error: {}", GetLastError()),
));
}
} else {
return Err(Error::new(
ErrorKind::Other,
"Unreachable, set_window_source_func should not be none",
));
}
}
let mut lock = MAG_BUFFER.lock().unwrap();
if !lock.0 {
return Err(Error::new(
ErrorKind::Other,
"No data captured by magnifier",
));
}
if self.use_yuv {
self.data.resize(lock.1.len(), 0);
unsafe {
std::ptr::copy_nonoverlapping(&mut lock.1[0], &mut self.data[0], self.data.len());
}
crate::common::bgra_to_i420(
self.width as usize,
self.height as usize,
&self.data,
data,
);
} else {
data.resize(lock.1.len(), 0);
unsafe {
std::ptr::copy_nonoverlapping(&mut lock.1[0], &mut data[0], data.len());
}
}
Ok(())
}
fn destroy_windows(&mut self) {
if !self.magnifier_window.is_null() {
unsafe {
if FALSE == DestroyWindow(self.magnifier_window) {
//
println!("Failed DestroyWindow magnifier window {}", GetLastError())
}
}
}
self.magnifier_window = NULL as _;
if !self.host_window.is_null() {
unsafe {
if FALSE == DestroyWindow(self.host_window) {
//
println!("Failed DestroyWindow host window {}", GetLastError())
}
}
}
self.host_window = NULL as _;
}
unsafe extern "C" fn on_gag_image_scaling_callback(
_hwnd: HWND,
srcdata: *mut ::std::os::raw::c_void,
srcheader: MAGIMAGEHEADER,
_destdata: *mut ::std::os::raw::c_void,
_destheader: MAGIMAGEHEADER,
_unclipped: RECT,
_clipped: RECT,
_dirty: HRGN,
) -> BOOL {
Self::clear_data();
if !IsEqualGUID(&srcheader.format, &GUID_WICPixelFormat32bppRGBA) {
// log warning?
return FALSE;
}
let mut lock = MAG_BUFFER.lock().unwrap();
lock.1.resize(srcheader.cbSize, 0);
std::ptr::copy_nonoverlapping(srcdata as _, &mut lock.1[0], srcheader.cbSize);
lock.0 = true;
TRUE
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let mut capture_mag = CapturerMag::new((0, 0), 1920, 1080, false).unwrap();
capture_mag.exclude("", "RustDeskPrivacyWindow").unwrap();
std::thread::sleep(std::time::Duration::from_millis(1000 * 10));
let mut data = Vec::new();
capture_mag.frame(&mut data).unwrap();
println!("capture data len: {}", data.len());
}
}

View File

@ -1,6 +1,7 @@
use std::{io, mem, ptr, slice};
pub mod gdi;
pub use gdi::CapturerGDI;
pub mod mag;
use winapi::{
shared::{

13
libs/simple_rc/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "simple_rc"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde_derive = "1.0"
serde = "1.0"
walkdir = "2"
confy = { git = "https://github.com/open-trade/confy" }
hbb_common = { path = "../hbb_common" }

View File

@ -0,0 +1,23 @@
extern crate simple_rc;
use simple_rc::*;
fn main() {
{
const CONF_FILE: &str = "simple_rc.toml";
generate(CONF_FILE).unwrap();
}
{
generate_with_conf(&Config {
outfile: "src/rc.rs".to_owned(),
confs: vec![ConfigItem {
inc: "D:/projects/windows/RustDeskTempTopMostWindow/x64/Release/xxx".to_owned(),
// exc: vec!["*.dll".to_owned(), "*.exe".to_owned()],
exc: vec![],
suppressed_front: "D:/projects/windows".to_owned(),
}],
})
.unwrap();
}
}

View File

@ -0,0 +1,12 @@
# The output source file
outfile = "src/rc.rs"
# The resource config list.
[[confs]]
# The file or director to integrate.
inc = "D:/projects/windows/RustDeskTempTopMostWindow/x64/Release/xxx"
# The exclusions.
exc = ["*.dll", "*.exe"]
# The front path that will ignore for extracting.
# The following config will make base output path to be "RustDeskTempTopMostWindow/x64/Release/xxx".
suppressed_front = "D:/projects/windows"

208
libs/simple_rc/src/lib.rs Normal file
View File

@ -0,0 +1,208 @@
use hbb_common::{bail, ResultType};
use serde_derive::{Deserialize, Serialize};
use std::{collections::HashMap, fs::File, io::prelude::*, path::Path};
use walkdir::WalkDir;
//mod rc;
#[derive(Debug, Default, PartialEq, Serialize, Deserialize, Clone)]
pub struct ConfigItem {
// include directory or file
pub inc: String,
// exclude files
pub exc: Vec<String>,
// out_path = origin_path - suppressed_front
pub suppressed_front: String,
}
#[derive(Debug, Default, PartialEq, Serialize, Deserialize, Clone)]
pub struct Config {
// output source file
pub outfile: String,
// config items
pub confs: Vec<ConfigItem>,
}
pub fn get_outin_files<'a>(item: &'a ConfigItem) -> ResultType<HashMap<String, String>> {
let mut outin_filemap = HashMap::new();
for entry in WalkDir::new(&item.inc).follow_links(true) {
let path = entry?.into_path();
if path.is_file() {
let mut exclude = false;
for excfile in item.exc.iter() {
if excfile.starts_with("*.") {
if let Some(ext) = path.extension().and_then(|x| x.to_str()) {
if excfile.ends_with(&format!(".{}", ext)) {
exclude = true;
break;
}
}
} else {
if path.ends_with(Path::new(excfile)) {
exclude = true;
break;
}
}
}
if exclude {
continue;
}
let mut suppressed_front = item.suppressed_front.clone();
if !suppressed_front.is_empty() && suppressed_front.ends_with('/') {
suppressed_front.push('/');
}
let outpath = path.strip_prefix(Path::new(&suppressed_front))?;
let outfile = if outpath.is_absolute() {
match outpath
.file_name()
.and_then(|f| f.to_str())
.map(|f| f.to_string())
{
None => {
bail!("Failed to get filename of {}", outpath.display());
}
Some(s) => s,
}
} else {
match outpath.to_str() {
None => {
bail!("Failed to convert {} to string", outpath.display());
}
// Simple replace \ to / here.
// A better way is to use lib [path-slash](https://github.com/rhysd/path-slash)
Some(s) => s.to_string().replace("\\", "/"),
}
};
let infile = match path.canonicalize()?.to_str() {
None => {
bail!("Failed to get file path of {}", path.display());
}
Some(s) => s.to_string(),
};
if let Some(_) = outin_filemap.insert(outfile.clone(), infile) {
bail!("outfile {} is set before", outfile);
}
}
}
Ok(outin_filemap)
}
pub fn generate(conf_file: &str) -> ResultType<()> {
let conf = confy::load_path(conf_file)?;
generate_with_conf(&conf)?;
Ok(())
}
pub fn generate_with_conf<'a>(conf: &'a Config) -> ResultType<()> {
let mut outfile = File::create(&conf.outfile)?;
outfile.write(
br##"use hbb_common::{bail, ResultType};
use std::{
fs::{self, File},
io::prelude::*,
path::Path,
};
"##,
)?;
outfile.write(b"#[allow(dead_code)]\n")?;
outfile.write(b"pub fn extract_resources(root_path: &str) -> ResultType<()> {\n")?;
outfile.write(b" let mut resources: Vec<(&str, &[u8])> = Vec::new();\n")?;
let mut outin_files = HashMap::new();
for item in conf.confs.iter() {
for (o, i) in get_outin_files(item)?.into_iter() {
if let Some(_) = outin_files.insert(o.clone(), i) {
bail!("outfile {} is set before", o);
}
}
}
let mut count = 1;
for (o, i) in outin_files.iter() {
let mut infile = File::open(&i)?;
let mut buffer = Vec::<u8>::new();
infile.read_to_end(&mut buffer)?;
let var_outfile = format!("outfile_{}", count);
let var_outdata = format!("outdata_{}", count);
write!(outfile, " let {} = \"{}\";\n", var_outfile, o)?;
write!(outfile, " let {}: &[u8] = &[\n ", var_outdata)?;
let mut line_num = 20;
for v in buffer {
if line_num == 0 {
write!(outfile, "\n ")?;
line_num = 20;
}
write!(outfile, "{:#04x}, ", v)?;
line_num -= 1;
}
write!(outfile, "\n ];\n")?;
write!(
outfile,
" resources.push(({}, &{}));\n",
var_outfile, var_outdata
)?;
count += 1;
}
outfile.write(b" do_extract(root_path, resources)?;\n")?;
outfile.write(b" Ok(())\n")?;
outfile.write(b"}\n")?;
outfile.write(
br##"
#[allow(dead_code)]
fn do_extract(root_path: &str, resources: Vec<(&str, &[u8])>) -> ResultType<()> {
let mut root_path = root_path.replace("\\", "/");
if !root_path.ends_with('/') {
root_path.push('/');
}
let root_path = Path::new(&root_path);
for (outfile, data) in resources {
let outfile_path = root_path.join(outfile);
match outfile_path.parent().and_then(|p| p.to_str()) {
None => {
bail!("Failed to get parent of {}", outfile_path.display());
}
Some(p) => {
fs::create_dir_all(p)?;
let mut of = File::create(outfile_path)?;
of.write_all(data)?;
of.flush()?;
}
}
}
Ok(())
}
"##,
)?;
outfile.flush()?;
Ok(())
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
// #[test]
// fn test_extract() {
// use super::*;
// rc::extract_resources("D:").unwrap();
// }
}

View File

@ -1,12 +1,14 @@
#[cfg(windows)]
use virtual_display::win10::{idd, DRIVER_INSTALL_PATH};
#[cfg(windows)]
use std::{
ffi::{CStr, CString},
ffi::CStr,
io::{self, Read},
path::Path,
};
#[cfg(windows)]
fn prompt_input() -> u8 {
println!("Press key execute:");
println!(" 1. 'x' 1. exit");
@ -24,6 +26,7 @@ fn prompt_input() -> u8 {
.unwrap_or(0)
}
#[cfg(windows)]
unsafe fn plug_in(index: idd::UINT, edid: idd::UINT) {
println!("Plug in monitor begin");
if idd::FALSE == idd::MonitorPlugIn(index, edid, 25) {
@ -48,6 +51,7 @@ unsafe fn plug_in(index: idd::UINT, edid: idd::UINT) {
}
}
#[cfg(windows)]
unsafe fn plug_out(index: idd::UINT) {
println!("Plug out monitor begin");
if idd::FALSE == idd::MonitorPlugOut(index) {
@ -58,81 +62,91 @@ unsafe fn plug_out(index: idd::UINT) {
}
fn main() {
let abs_path = Path::new(DRIVER_INSTALL_PATH).canonicalize().unwrap();
let full_inf_path = abs_path.to_str().unwrap();
#[cfg(windows)]
{
let abs_path = Path::new(DRIVER_INSTALL_PATH).canonicalize().unwrap();
unsafe {
let invalid_device = 0 as idd::HSWDEVICE;
let mut h_sw_device = invalid_device;
let full_inf_path = CString::new(full_inf_path).unwrap().into_raw();
loop {
match prompt_input() as char {
'x' => break,
'i' => {
println!("Install or update driver begin");
let mut reboot_required = idd::FALSE;
if idd::InstallUpdate(full_inf_path, &mut reboot_required) == idd::FALSE {
println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap());
} else {
println!(
"Install or update driver done, reboot is {} required",
if reboot_required == idd::FALSE {
"not"
} else {
""
}
);
unsafe {
let invalid_device = 0 as idd::HSWDEVICE;
let mut h_sw_device = invalid_device;
let full_inf_path: Vec<u16> = abs_path
.to_string_lossy()
.as_ref()
.encode_utf16()
.chain(Some(0).into_iter())
.collect();
loop {
match prompt_input() as char {
'x' => break,
'i' => {
println!("Install or update driver begin, {}", abs_path.display());
let mut reboot_required = idd::FALSE;
if idd::InstallUpdate(full_inf_path.as_ptr() as _, &mut reboot_required)
== idd::FALSE
{
println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap());
} else {
println!(
"Install or update driver done, reboot is {} required",
if reboot_required == idd::FALSE {
"not"
} else {
""
}
);
}
}
}
'u' => {
println!("Uninstall driver begin");
let mut reboot_required = idd::FALSE;
if idd::Uninstall(full_inf_path, &mut reboot_required) == idd::FALSE {
println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap());
} else {
println!(
"Uninstall driver done, reboot is {} required",
if reboot_required == idd::FALSE {
"not"
} else {
""
}
);
'u' => {
println!("Uninstall driver begin {}", abs_path.display());
let mut reboot_required = idd::FALSE;
if idd::Uninstall(full_inf_path.as_ptr() as _, &mut reboot_required)
== idd::FALSE
{
println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap());
} else {
println!(
"Uninstall driver done, reboot is {} required",
if reboot_required == idd::FALSE {
"not"
} else {
""
}
);
}
}
}
'c' => {
println!("Create device begin");
if h_sw_device != invalid_device {
println!("Device created before");
continue;
'c' => {
println!("Create device begin");
if h_sw_device != invalid_device {
println!("Device created before");
continue;
}
if idd::FALSE == idd::DeviceCreate(&mut h_sw_device) {
println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap());
idd::DeviceClose(h_sw_device);
h_sw_device = invalid_device;
} else {
println!("Create device done");
}
}
if idd::FALSE == idd::DeviceCreate(&mut h_sw_device) {
println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap());
'd' => {
println!("Close device begin");
idd::DeviceClose(h_sw_device);
h_sw_device = invalid_device;
} else {
println!("Create device done");
println!("Close device done");
}
'1' => plug_in(0, 0),
'2' => plug_in(1, 0),
'3' => plug_in(2, 0),
'4' => plug_out(0),
'5' => plug_out(1),
'6' => plug_out(2),
_ => {}
}
'd' => {
println!("Close device begin");
idd::DeviceClose(h_sw_device);
h_sw_device = invalid_device;
println!("Close device done");
}
'1' => plug_in(0, 0),
'2' => plug_in(1, 0),
'3' => plug_in(2, 0),
'4' => plug_out(0),
'5' => plug_out(1),
'6' => plug_out(2),
_ => {}
}
}
if !full_inf_path.is_null() {
let _ = CString::from_raw(full_inf_path);
}
idd::DeviceClose(h_sw_device);
idd::DeviceClose(h_sw_device);
}
}
}

View File

@ -2,7 +2,7 @@
pub mod win10;
use hbb_common::{bail, lazy_static, ResultType};
use std::{ffi::CString, path::Path, sync::Mutex};
use std::{path::Path, sync::Mutex};
lazy_static::lazy_static! {
// If device is uninstalled though "Device Manager" Window.
@ -33,16 +33,24 @@ pub fn install_update_driver(_reboot_required: &mut bool) -> ResultType<()> {
bail!("{} not exists", install_path)
}
let _full_install_path = match abs_path.to_str() {
Some(p) => CString::new(p)?.into_raw(),
None => bail!("{} not exists", install_path),
};
#[cfg(windows)]
unsafe {
{
// Device must be created before install driver.
// https://github.com/fufesou/RustDeskIddDriver/issues/1
if let Err(e) = create_device() {
bail!("{}", e);
}
let full_install_path: Vec<u16> = abs_path
.to_string_lossy()
.as_ref()
.encode_utf16()
.chain(Some(0).into_iter())
.collect();
let mut reboot_required_tmp = win10::idd::FALSE;
if win10::idd::InstallUpdate(_full_install_path, &mut reboot_required_tmp)
if win10::idd::InstallUpdate(full_install_path.as_ptr() as _, &mut reboot_required_tmp)
== win10::idd::FALSE
{
bail!("{}", win10::get_last_msg()?);
@ -65,16 +73,18 @@ pub fn uninstall_driver(_reboot_required: &mut bool) -> ResultType<()> {
bail!("{} not exists", install_path)
}
let _full_install_path = match abs_path.to_str() {
Some(p) => CString::new(p)?.into_raw(),
None => bail!("{} not exists", install_path),
};
#[cfg(windows)]
unsafe {
{
let full_install_path: Vec<u16> = abs_path
.to_string_lossy()
.as_ref()
.encode_utf16()
.chain(Some(0).into_iter())
.collect();
let mut reboot_required_tmp = win10::idd::FALSE;
if win10::idd::Uninstall(_full_install_path, &mut reboot_required_tmp)
if win10::idd::Uninstall(full_install_path.as_ptr() as _, &mut reboot_required_tmp)
== win10::idd::FALSE
{
bail!("{}", win10::get_last_msg()?);

View File

@ -64,14 +64,14 @@ const char* GetLastMsg()
return g_lastMsg;
}
BOOL InstallUpdate(LPCTSTR fullInfPath, PBOOL rebootRequired)
BOOL InstallUpdate(LPCWSTR fullInfPath, PBOOL rebootRequired)
{
SetLastMsg("Sucess");
// UpdateDriverForPlugAndPlayDevices may return FALSE while driver was successfully installed...
if (FALSE == UpdateDriverForPlugAndPlayDevices(
// UpdateDriverForPlugAndPlayDevicesW may return FALSE while driver was successfully installed...
if (FALSE == UpdateDriverForPlugAndPlayDevicesW(
NULL,
_T("RustDeskIddDriver"), // match hardware id in the inf file
L"RustDeskIddDriver", // match hardware id in the inf file
fullInfPath,
INSTALLFLAG_FORCE
// | INSTALLFLAG_NONINTERACTIVE // INSTALLFLAG_NONINTERACTIVE may cause error 0xe0000247
@ -82,7 +82,7 @@ BOOL InstallUpdate(LPCTSTR fullInfPath, PBOOL rebootRequired)
DWORD error = GetLastError();
if (error != 0)
{
SetLastMsg("UpdateDriverForPlugAndPlayDevices failed, last error 0x%x\n", error);
SetLastMsg("UpdateDriverForPlugAndPlayDevicesW failed, last error 0x%x\n", error);
if (g_printMsg)
{
printf(g_lastMsg);
@ -94,11 +94,11 @@ BOOL InstallUpdate(LPCTSTR fullInfPath, PBOOL rebootRequired)
return TRUE;
}
BOOL Uninstall(LPCTSTR fullInfPath, PBOOL rebootRequired)
BOOL Uninstall(LPCWSTR fullInfPath, PBOOL rebootRequired)
{
SetLastMsg("Sucess");
if (FALSE == DiUninstallDriver(
if (FALSE == DiUninstallDriverW(
NULL,
fullInfPath,
0,
@ -108,7 +108,7 @@ BOOL Uninstall(LPCTSTR fullInfPath, PBOOL rebootRequired)
DWORD error = GetLastError();
if (error != 0)
{
SetLastMsg("DiUninstallDriver failed, last error 0x%x\n", error);
SetLastMsg("DiUninstallDriverW failed, last error 0x%x\n", error);
if (g_printMsg)
{
printf(g_lastMsg);

View File

@ -773,6 +773,7 @@ pub struct LoginConfigHandler {
pub port_forward: (String, i32),
pub version: i64,
pub conn_id: i32,
features: Option<Features>,
}
impl Deref for LoginConfigHandler {
@ -866,11 +867,11 @@ impl LoginConfigHandler {
})
.into();
} else if name == "privacy-mode" {
config.privacy_mode = !config.privacy_mode;
// try toggle privacy mode
option.privacy_mode = (if config.privacy_mode {
BoolOption::Yes
} else {
BoolOption::No
} else {
BoolOption::Yes
})
.into();
} else if name == "enable-file-transfer" {
@ -992,6 +993,14 @@ impl LoginConfigHandler {
}
}
pub fn is_privacy_mode_supported(&self) -> bool {
if let Some(features) = &self.features {
features.privacy_mode
} else {
false
}
}
pub fn refresh() -> Message {
let mut misc = Misc::new();
misc.set_refresh_video(true);
@ -1064,6 +1073,7 @@ impl LoginConfigHandler {
if !pi.version.is_empty() {
self.version = hbb_common::get_version_number(&pi.version);
}
self.features = pi.features.into_option();
let serde = PeerInfoSerde {
username,
hostname: pi.hostname.clone(),

View File

@ -594,3 +594,14 @@ pub async fn post_request(url: String, body: String, header: &str) -> ResultType
pub async fn post_request_sync(url: String, body: String, header: &str) -> ResultType<String> {
post_request(url, body, header).await
}
#[inline]
pub fn make_privacy_mode_msg(state: back_notification::PrivacyModeState) -> Message {
let mut misc = Misc::new();
let mut back_notification = BackNotification::new();
back_notification.set_privacy_mode_state(state);
misc.set_back_notification(back_notification);
let mut msg_out = Message::new();
msg_out.set_misc(misc);
msg_out
}

View File

@ -20,6 +20,16 @@ use std::{collections::HashMap, sync::atomic::Ordering};
#[cfg(not(windows))]
use std::{fs::File, io::prelude::*};
// State with timestamp, because std::time::Instant cannot be serialized
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
#[serde(tag = "t", content = "c")]
pub enum PrivacyModeState {
OffSucceeded,
OffFailed,
OffByPeer,
OffUnknown,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "t", content = "c")]
pub enum FS {
@ -116,6 +126,7 @@ pub enum Data {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
ClipbaordFile(ClipbaordFile),
ClipboardFileEnabled(bool),
PrivacyModeState((i32, PrivacyModeState)),
}
#[tokio::main(flavor = "current_thread")]

View File

@ -265,10 +265,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_version_audio_tip", "当前安卓版本不支持音频录制请升级至安卓10或更高。"),
("android_start_service_tip", "点击 [启动服务] 或打开 [屏幕录制] 权限开启手机屏幕共享服务。"),
("Account", "账号"),
("Quit", "退出"),
("Overwrite", "覆盖"),
("This file exists, skip or overwrite this file?", "这个文件/文件夹已存在,跳过/覆盖?"),
("Quit", "退出"),
("doc_mac_permission", "https://rustdesk.com/docs/zh-cn/manual/mac/#启用权限"),
("Help", "帮助"),
("Failed", "失败"),
("Succeeded", "成功"),
("Someone turns on privacy mode, exit", "其他用户使用隐私模式,退出"),
("Unsupported", "不支持"),
("Peer denied", "被控端拒绝"),
("Please install plugins", "请安装插件"),
("Peer exit", "被控端退出"),
("Failed to turn off", "退出失败"),
("Turned off", "退出"),
("In privacy mode", "进入隐私模式"),
("Out privacy mode", "退出隐私模式"),
].iter().cloned().collect();
}

View File

@ -270,5 +270,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Quit", "Ukončit"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("Help", "Nápověda"),
("Failed", "Nepodařilo se"),
("Succeeded", "Uspěl"),
("Someone turns on privacy mode, exit", "Někdo zapne režim soukromí, ukončete ho"),
("Unsupported", "Nepodporováno"),
("Peer denied", "Peer popřel"),
("Please install plugins", "Nainstalujte si prosím pluginy"),
("Peer exit", "Peer exit"),
("Failed to turn off", "Nepodařilo se vypnout"),
("Turned off", "Vypnutý"),
("In privacy mode", "v režimu soukromí"),
("Out privacy mode", "mimo režim soukromí"),
].iter().cloned().collect();
}

View File

@ -265,8 +265,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_version_audio_tip", "Die aktuelle Android-Version unterstützt keine Audioaufnahme, bitte aktualisieren Sie auf Android 10 oder höher."),
("android_start_service_tip", "Tippen Sie auf [Dienst starten] oder ÖFFNEN Sie die Berechtigung [Bildschirmaufnahme], um den Bildschirmfreigabedienst zu starten."),
("Account", "Konto"),
("Quit", "Ausgang"),
("Overwrite", "Überschreiben"),
("This file exists, skip or overwrite this file?", "Diese Datei existiert, diese Datei überspringen oder überschreiben?"),
("Quit", "Aufhören"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("Help", "Hilfe"),
("Failed", "Gescheitert"),
("Succeeded", "Erfolgreich"),
("Someone turns on privacy mode, exit", "Jemand aktiviert den Datenschutzmodus, beenden"),
("Unsupported", "Nicht unterstützt"),
("Peer denied", "Peer verweigert"),
("Please install plugins", "Bitte installieren Sie Plugins"),
("Peer exit", "Peer-Ausgang"),
("Failed to turn off", "Ausschalten fehlgeschlagen"),
("Turned off", "Ausgeschaltet"),
("In privacy mode", "im Datenschutzmodus"),
("Out privacy mode", "Datenschutzmodus aus"),
].iter().cloned().collect();
}

View File

@ -265,8 +265,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_version_audio_tip", ""),
("android_start_service_tip", ""),
("Account", ""),
("Overwrite", ""),
("This file exists, skip or overwrite this file?", ""),
("Quit", ""),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("Help", ""),
("Failed", ""),
("Succeeded", ""),
("Someone turns on privacy mode, exit", ""),
("Unsupported", ""),
("Peer denied", ""),
("Please install plugins", ""),
("Peer exit", ""),
("Failed to turn off", ""),
("Turned off", ""),
("In privacy mode", ""),
("Out privacy mode", ""),
].iter().cloned().collect();
}

View File

@ -265,8 +265,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_version_audio_tip", "La version actuelle d'Android ne prend pas en charge la capture audio, veuillez passer à Android 10 ou supérieur."),
("android_start_service_tip", "Appuyez sur [Démarrer le service] ou sur l'autorisation OUVRIR [Capture d'écran] pour démarrer le service de partage d'écran."),
("Account", "Compte"),
("Overwrite", "Écraser"),
("This file exists, skip or overwrite this file?", "Ce fichier existe, ignorer ou écraser ce fichier ?"),
("Quit", "Quitter"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("Help", "Aider"),
("Failed", "échouer"),
("Succeeded", "Succès"),
("Someone turns on privacy mode, exit", "Quelqu'un active le mode de confidentialité, quittez"),
("Unsupported", "Non pris en charge"),
("Peer denied", "Pair refusé"),
("Please install plugins", "Veuillez installer les plugins"),
("Peer exit", "Sortie des pairs"),
("Failed to turn off", "Échec de la désactivation"),
("Turned off", "Éteindre"),
("In privacy mode", "en mode privé"),
("Out privacy mode", "hors mode de confidentialité"),
].iter().cloned().collect();
}

View File

@ -265,8 +265,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_version_audio_tip", "Versi Android saat ini tidak mendukung pengambilan audio, harap tingkatkan ke Android 10 atau lebih tinggi."),
("android_start_service_tip", "Ketuk izin [Mulai Layanan] atau BUKA [Tangkapan Layar] untuk memulai layanan berbagi layar."),
("Account", "Akun"),
("Overwrite", "Timpa"),
("This file exists, skip or overwrite this file?", ""),
("Quit", "Keluar"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("Help", "Bantuan"),
("Failed", "Gagal"),
("Succeeded", "Berhasil"),
("Someone turns on privacy mode, exit", "Seseorang mengaktifkan mode privasi, keluar"),
("Unsupported", "Tidak didukung"),
("Peer denied", "Rekan ditolak"),
("Please install plugins", "Silakan instal plugin"),
("Peer exit", "keluar rekan"),
("Failed to turn off", "Gagal mematikan"),
("Turned off", "Matikan"),
("In privacy mode", "Dalam mode privasi"),
("Out privacy mode", "Keluar dari mode privasi"),
].iter().cloned().collect();
}

View File

@ -265,8 +265,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_version_audio_tip", "L'attuale versione di Android non supporta l'acquisizione audio, esegui l'upgrade ad Android 10 o versioni successive."),
("android_start_service_tip", "Toccare [Avvia servizio] o APRI l'autorizzazione [Cattura schermo] per avviare il servizio di condivisione dello schermo."),
("Account", "Account"),
("Overwrite", "Sovrascrivi"),
("This file exists, skip or overwrite this file?", "Questo file esiste, saltare o sovrascrivere questo file?"),
("Quit", "Esci"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("Help", "Aiuto"),
("Failed", "Fallito"),
("Succeeded", "Successo"),
("Someone turns on privacy mode, exit", "Qualcuno attiva la modalità privacy, esci"),
("Unsupported", "Non supportato"),
("Peer denied", "Pari negato"),
("Please install plugins", "Si prega di installare i plugin"),
("Peer exit", "Uscita tra pari"),
("Failed to turn off", "Impossibile spegnere"),
("Turned off", "Spegni"),
("In privacy mode", "In modalità privacy"),
("Out privacy mode", "Fuori modalità privacy"),
].iter().cloned().collect();
}

View File

@ -265,8 +265,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_version_audio_tip", "A versão atual do Android não suporta captura de áudio, por favor atualize para o Android 10 ou maior."),
("android_start_service_tip", "Toque [Iniciar Serviço] ou abra a permissão [Captura de Tela] para iniciar o serviço de compartilhamento de tela."),
("Account", "Conta"),
("Overwrite", "Substituir"),
("This file exists, skip or overwrite this file?", "Este arquivo existe, pular ou substituir este arquivo?"),
("Quit", "Saída"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("Help", "Ajuda"),
("Failed", "Falhou"),
("Succeeded", "Conseguiu"),
("Someone turns on privacy mode, exit", "Alguém liga o modo de privacidade, saia"),
("Unsupported", "Sem suporte"),
("Peer denied", "Par negado"),
("Please install plugins", "Por favor instale plugins"),
("Peer exit", "Saída de pares"),
("Failed to turn off", "Falha ao desligar"),
("Turned off", "Desligado"),
("In privacy mode", "No modo de privacidade"),
("Out privacy mode", "Fora do modo de privacidade"),
].iter().cloned().collect();
}

View File

@ -270,5 +270,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This file exists, skip or overwrite this file?", "Этот файл существует, пропустить или перезаписать этот файл?"),
("doc_mac_permission", "https://rustdesk.com/docs/ru/manual/mac/#включение-разрешений"),
("Help", "Помощь"),
("Failed", "Неуспешный"),
("Succeeded", "Успешно"),
("Someone turns on privacy mode, exit", "Кто-то включает режим конфиденциальности, выйдите"),
("Unsupported", "Не поддерживается"),
("Peer denied", "Отказано в пире"),
("Please install plugins", "Пожалуйста, установите плагины"),
("Peer exit", "Одноранговый выход"),
("Failed to turn off", "Не удалось отключить"),
("Turned off", "Выключен"),
("In privacy mode", "В режиме конфиденциальности"),
("Out privacy mode", "Выход из режима конфиденциальности"),
].iter().cloned().collect();
}

View File

@ -270,5 +270,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Quit", "Ukončiť"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("Help", "Nápoveda"),
("Failed", "Nepodarilo sa"),
("Succeeded", "Podarilo sa"),
("Someone turns on privacy mode, exit", "Niekto zapne režim súkromia, ukončite ho"),
("Unsupported", "Nepodporované"),
("Peer denied", "Peer poprel"),
("Please install plugins", "Nainštalujte si prosím pluginy"),
("Peer exit", "Peer exit"),
("Failed to turn off", "Nepodarilo sa vypnúť"),
("Turned off", "Vypnutý"),
("In privacy mode", "V režime súkromia"),
("Out privacy mode", "Mimo režimu súkromia"),
].iter().cloned().collect();
}

View File

@ -270,5 +270,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Quit", ""),
("doc_mac_permission", ""),
("Help", ""),
("Failed", ""),
("Succeeded", ""),
("Someone turns on privacy mode, exit", ""),
("Unsupported", ""),
("Peer denied", ""),
("Please install plugins", ""),
("Peer exit", ""),
("Failed to turn off", ""),
("Turned off", ""),
("In privacy mode", ""),
("Out privacy mode", ""),
].iter().cloned().collect();
}

View File

@ -265,8 +265,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_version_audio_tip", "Mevcut Android sürümü ses yakalamayı desteklemiyor, lütfen Android 10 veya sonraki bir sürüme yükseltin."),
("android_start_service_tip", "Ekran paylaşım hizmetini başlatmak için [Hizmeti Başlat] veya AÇ [Ekran Yakalama] iznine dokunun."),
("Account", "Hesap"),
("Overwrite", "üzerine yaz"),
("This file exists, skip or overwrite this file?", "Bu dosya var, bu dosya atlansın veya üzerine yazılsın mı?"),
("Quit", "Çıkış"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("Help", "Yardım"),
("Failed", "Arızalı"),
("Succeeded", "başarılı"),
("Someone turns on privacy mode, exit", "Birisi gizlilik modunu açar, çık"),
("Unsupported", "desteklenmiyor"),
("Peer denied", "akran reddedildi"),
("Please install plugins", "Lütfen eklentileri yükleyin"),
("Peer exit", "akran çıkışı"),
("Failed to turn off", "kapatılamadı"),
("Turned off", "Kapalı"),
("In privacy mode", "Gizlilik modunda"),
("Out privacy mode", "Gizlilik modu dışında"),
].iter().cloned().collect();
}

View File

@ -270,5 +270,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This file exists, skip or overwrite this file?", "此檔案/資料夾已存在,要跳過或是覆寫此檔案嗎?"),
("doc_mac_permission", "https://rustdesk.com/docs/zh-tw/manual/mac/#啟用權限"),
("Help", "幫助"),
("Account", "帳戶"),
("Failed", "失敗"),
("Succeeded", "成功"),
("Someone turns on privacy mode, exit", "其他用戶開啟隱私模式,退出"),
("Unsupported", "不支持"),
("Peer denied", "被控端拒絕"),
("Please install plugins", "請安裝插件"),
("Peer exit", "被控端退出"),
("Failed to turn off", "退出失敗"),
("Turned off", "退出"),
("In privacy mode", "開啟隱私模式"),
("Out privacy mode", "退出隱私模式"),
].iter().cloned().collect();
}

View File

@ -42,3 +42,6 @@ mod tray;
#[cfg(windows)]
pub mod clipboard_file;
#[cfg(all(windows, feature = "with_rc"))]
pub mod rc;

View File

@ -104,6 +104,10 @@ fn main() {
"".to_owned()
));
return;
} else if args[0] == "--extract" {
#[cfg(feature = "with_rc")]
hbb_common::allow_err!(crate::rc::extract_resources(&args[1]));
return;
}
}
if args[0] == "--remove" {

View File

@ -9,7 +9,7 @@ use hbb_common::{
use std::io::prelude::*;
use std::{
ffi::OsString,
io, mem,
fs, io, mem,
sync::{Arc, Mutex},
time::{Duration, Instant},
};
@ -404,6 +404,7 @@ extern "C" {
fn has_rdp_service() -> BOOL;
fn get_current_session(rdp: BOOL) -> DWORD;
fn LaunchProcessWin(cmd: *const u16, session_id: DWORD, as_user: BOOL) -> HANDLE;
fn GetSessionUserTokenWin(lphUserToken: LPHANDLE, dwSessionId: DWORD, as_user: BOOL) -> BOOL;
fn selectInputDesktop() -> BOOL;
fn inputDesktopSelected() -> BOOL;
fn is_windows_server() -> BOOL;
@ -558,7 +559,7 @@ async fn launch_server(session_id: DWORD, close_first: bool) -> ResultType<HANDL
let wstr = wstr.as_ptr();
let h = unsafe { LaunchProcessWin(wstr, session_id, FALSE) };
if h.is_null() {
log::error!("Failed to luanch server: {}", get_error());
log::error!("Failed to launch server: {}", get_error());
}
Ok(h)
}
@ -796,6 +797,49 @@ fn get_default_install_path() -> String {
format!("{}\\{}", pf, crate::get_app_name())
}
pub fn check_update_broker_process() -> ResultType<()> {
// let (_, path, _, _) = get_install_info();
let process_exe = crate::ui::win_privacy::INJECTED_PROCESS_EXE;
let origin_process_exe = crate::ui::win_privacy::ORIGIN_PROCESS_EXE;
let exe_file = std::env::current_exe()?;
if exe_file.parent().is_none() {
bail!("Cannot get parent of current exe file");
}
let cur_dir = exe_file.parent().unwrap();
let cur_exe = cur_dir.join(process_exe);
let ori_modified = fs::metadata(origin_process_exe)?.modified()?;
if let Ok(metadata) = fs::metadata(&cur_exe) {
if let Ok(cur_modified) = metadata.modified() {
if cur_modified == ori_modified {
return Ok(());
} else {
log::info!(
"broker process updated, modify time from {:?} to {:?}",
cur_modified,
ori_modified
);
}
}
}
// Force update broker exe if failed to check modified time.
let cmds = format!(
"
chcp 65001
taskkill /F /IM {broker_exe}
copy /Y \"{origin_process_exe}\" \"{cur_exe}\"
",
broker_exe = process_exe,
origin_process_exe = origin_process_exe,
cur_exe = cur_exe.to_string_lossy().to_string(),
);
run_cmds(cmds, false)?;
Ok(())
}
fn get_install_info_with_subkey(subkey: String) -> (String, String, String, String) {
let mut path = get_reg_of(&subkey, "InstallLocation");
if path.is_empty() {
@ -811,19 +855,23 @@ fn get_install_info_with_subkey(subkey: String) -> (String, String, String, Stri
}
pub fn update_me() -> ResultType<()> {
let (_, _, _, exe) = get_install_info();
let (_, path, _, exe) = get_install_info();
let src_exe = std::env::current_exe()?.to_str().unwrap_or("").to_owned();
let cmds = format!(
"
chcp 65001
sc stop {app_name}
taskkill /F /IM {broker_exe}
taskkill /F /IM {app_name}.exe
copy /Y \"{src_exe}\" \"{exe}\"
\"{src_exe}\" --extract \"{path}\"
sc start {app_name}
{lic}
",
src_exe = src_exe,
exe = exe,
broker_exe = crate::ui::win_privacy::INJECTED_PROCESS_EXE,
path = path,
app_name = crate::get_app_name(),
lic = register_licence(),
);
@ -975,6 +1023,8 @@ copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{start_menu}\\\"
chcp 65001
md \"{path}\"
copy /Y \"{src_exe}\" \"{exe}\"
copy /Y \"{ORIGIN_PROCESS_EXE}\" \"{path}\\{broker_exe}\"
\"{src_exe}\" --extract \"{path}\"
reg add {subkey} /f
reg add {subkey} /f /v DisplayIcon /t REG_SZ /d \"{exe}\"
reg add {subkey} /f /v DisplayName /t REG_SZ /d \"{app_name}\"
@ -1010,6 +1060,8 @@ sc delete {app_name}
path=path,
src_exe=std::env::current_exe()?.to_str().unwrap_or(""),
exe=exe,
ORIGIN_PROCESS_EXE = crate::ui::win_privacy::ORIGIN_PROCESS_EXE,
broker_exe=crate::ui::win_privacy::INJECTED_PROCESS_EXE,
subkey=subkey,
app_name=crate::get_app_name(),
version=crate::VERSION,
@ -1051,11 +1103,13 @@ fn get_before_uninstall() -> String {
chcp 65001
sc stop {app_name}
sc delete {app_name}
taskkill /F /IM {broker_exe}
taskkill /F /IM {app_name}.exe
reg delete HKEY_CLASSES_ROOT\\.{ext} /f
netsh advfirewall firewall delete rule name=\"{app_name} Service\"
",
app_name = app_name,
broker_exe = crate::ui::win_privacy::INJECTED_PROCESS_EXE,
ext = ext
)
}
@ -1326,3 +1380,20 @@ pub fn quit_gui() {
std::process::exit(0);
// unsafe { PostQuitMessage(0) }; // some how not work
}
pub fn get_user_token(session_id: u32, as_user: bool) -> HANDLE {
let mut token = NULL as HANDLE;
unsafe {
if FALSE
== GetSessionUserTokenWin(
&mut token as _,
session_id,
if as_user { TRUE } else { FALSE },
)
{
NULL as _
} else {
token
}
}
}

38
src/rc.rs Normal file
View File

@ -0,0 +1,38 @@
use hbb_common::{bail, ResultType};
use std::{
fs::{self, File},
io::prelude::*,
path::Path,
};
#[allow(dead_code)]
pub fn extract_resources(root_path: &str) -> ResultType<()> {
let mut resources: Vec<(&str, &[u8])> = Vec::new();
resources.push((outfile_4, &outdata_4));
do_extract(root_path, resources)?;
Ok(())
}
#[allow(dead_code)]
fn do_extract(root_path: &str, resources: Vec<(&str, &[u8])>) -> ResultType<()> {
let mut root_path = root_path.replace("\\", "/");
if !root_path.ends_with('/') {
root_path.push('/');
}
let root_path = Path::new(&root_path);
for (outfile, data) in resources {
let outfile_path = root_path.join(outfile);
match outfile_path.parent().and_then(|p| p.to_str()) {
None => {
bail!("Failed to get parent of {}", outfile_path.display());
}
Some(p) => {
fs::create_dir_all(p)?;
let mut of = File::create(outfile_path)?;
of.write_all(data)?;
of.flush()?;
}
}
}
Ok(())
}

View File

@ -91,6 +91,15 @@ async fn accept_connection_(server: ServerPtr, socket: Stream, secure: bool) ->
Ok(())
}
async fn check_privacy_mode_on(stream: &mut Stream) -> ResultType<()> {
if video_service::get_privacy_mode_conn_id() > 0 {
let msg_out =
crate::common::make_privacy_mode_msg(back_notification::PrivacyModeState::OnByOther);
timeout(CONNECT_TIMEOUT, stream.send(&msg_out)).await??;
}
Ok(())
}
pub async fn create_tcp_connection(
server: ServerPtr,
stream: Stream,
@ -98,6 +107,8 @@ pub async fn create_tcp_connection(
secure: bool,
) -> ResultType<()> {
let mut stream = stream;
check_privacy_mode_on(&mut stream).await?;
let id = {
let mut w = server.write().unwrap();
w.id_count += 1;

View File

@ -50,8 +50,6 @@ enum MessageInput {
Key((KeyEvent, bool)),
BlockOn,
BlockOff,
PrivacyOn,
PrivacyOff,
}
pub struct Connection {
@ -74,7 +72,6 @@ pub struct Connection {
image_quality: i32,
lock_after_session_end: bool,
show_remote_cursor: bool, // by peer
privacy_mode: bool,
ip: String,
disable_clipboard: bool, // by peer
disable_audio: bool, // by peer
@ -160,7 +157,6 @@ impl Connection {
image_quality: ImageQuality::Balanced.value(),
lock_after_session_end: false,
show_remote_cursor: false,
privacy_mode: false,
ip: "".to_owned(),
disable_audio: false,
enable_file_transfer: false,
@ -281,6 +277,34 @@ impl Connection {
allow_err!(conn.stream.send(&clip_2_msg(_clip)).await);
}
}
ipc::Data::PrivacyModeState((_, state)) => {
let msg_out = match state {
ipc::PrivacyModeState::OffSucceeded => {
video_service::set_privacy_mode_conn_id(0);
crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::OffSucceeded,
)
}
ipc::PrivacyModeState::OffFailed => {
crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::OffFailed,
)
}
ipc::PrivacyModeState::OffByPeer => {
video_service::set_privacy_mode_conn_id(0);
crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::OffByPeer,
)
}
ipc::PrivacyModeState::OffUnknown => {
video_service::set_privacy_mode_conn_id(0);
crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::OffUnknown,
)
}
};
conn.send(msg_out).await;
}
_ => {}
}
},
@ -362,9 +386,16 @@ impl Connection {
}
}
let video_privacy_conn_id = video_service::get_privacy_mode_conn_id();
if video_privacy_conn_id == id {
video_service::set_privacy_mode_conn_id(0);
let _ = privacy_mode::turn_off_privacy(id);
} else if video_privacy_conn_id == 0 {
let _ = privacy_mode::turn_off_privacy(0);
}
video_service::notify_video_frame_feched(id, None);
super::video_service::update_test_latency(id, 0);
super::video_service::update_image_quality(id, None);
video_service::update_test_latency(id, 0);
video_service::update_image_quality(id, None);
if let Err(err) = conn.try_port_forward_loop(&mut rx_from_cm).await {
conn.on_close(&err.to_string(), false);
}
@ -378,9 +409,6 @@ impl Connection {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn handle_input(receiver: std_mpsc::Receiver<MessageInput>, tx: Sender) {
let mut block_input_mode = false;
let (tx_blank, rx_blank) = std_mpsc::channel();
std::thread::spawn(|| Self::handle_blank(rx_blank));
loop {
match receiver.recv_timeout(std::time::Duration::from_millis(500)) {
@ -402,28 +430,22 @@ impl Connection {
if crate::platform::block_input(true) {
block_input_mode = true;
} else {
Self::send_option_error(&tx, "Failed to turn on block input mode");
Self::send_block_input_error(
&tx,
back_notification::BlockInputState::OnFailed,
);
}
}
MessageInput::BlockOff => {
if crate::platform::block_input(false) {
block_input_mode = false;
} else {
Self::send_option_error(&tx, "Failed to turn off block input mode");
Self::send_block_input_error(
&tx,
back_notification::BlockInputState::OffFailed,
);
}
}
MessageInput::PrivacyOn => {
if crate::platform::block_input(true) {
block_input_mode = true;
}
tx_blank.send(MessageInput::PrivacyOn).ok();
}
MessageInput::PrivacyOff => {
if crate::platform::block_input(false) {
block_input_mode = false;
}
tx_blank.send(MessageInput::PrivacyOff).ok();
}
},
Err(err) => {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@ -439,35 +461,6 @@ impl Connection {
log::info!("Input thread exited");
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn handle_blank(receiver: std_mpsc::Receiver<MessageInput>) {
let mut last_privacy = false;
loop {
match receiver.recv_timeout(std::time::Duration::from_millis(500)) {
Ok(v) => match v {
MessageInput::PrivacyOn => {
crate::platform::toggle_blank_screen(true);
last_privacy = true;
}
MessageInput::PrivacyOff => {
crate::platform::toggle_blank_screen(false);
last_privacy = false;
}
_ => break,
},
Err(err) => {
if last_privacy {
crate::platform::toggle_blank_screen(true);
}
if std_mpsc::RecvTimeoutError::Disconnected == err {
break;
}
}
}
}
log::info!("Blank thread exited");
}
async fn try_port_forward_loop(
&mut self,
rx_from_cm: &mut mpsc::UnboundedReceiver<Data>,
@ -657,8 +650,20 @@ impl Connection {
}
}
self.authorized = true;
pi.username = username;
pi.sas_enabled = sas_enabled;
let mut pi = PeerInfo {
hostname: whoami::hostname(),
username,
platform: whoami::platform().to_string(),
version: crate::VERSION.to_owned(),
sas_enabled,
features: Some(Features {
privacy_mode: video_service::is_privacy_mode_supported(),
..Default::default()
})
.into(),
..Default::default()
};
let mut sub_service = false;
if self.file_transfer.is_some() {
res.set_peer_info(pi);
@ -755,13 +760,13 @@ impl Connection {
self.send(msg_out).await;
}
fn send_option_error<T: std::string::ToString>(s: &Sender, err: T) {
let mut msg_out = Message::new();
let mut res = OptionResponse::new();
#[inline]
pub fn send_block_input_error(s: &Sender, state: back_notification::BlockInputState) {
let mut misc = Misc::new();
res.error = err.to_string();
misc.set_option_response(res);
let mut back_notification = BackNotification::new();
back_notification.set_block_input_state(state);
misc.set_back_notification(back_notification);
let mut msg_out = Message::new();
msg_out.set_misc(misc);
s.send((Instant::now(), Arc::new(msg_out))).ok();
}
@ -1162,12 +1167,55 @@ impl Connection {
if self.keyboard {
match q {
BoolOption::Yes => {
self.privacy_mode = true;
self.tx_input.send(MessageInput::PrivacyOn).ok();
let msg_out = if !video_service::is_privacy_mode_supported() {
crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::NotSupported,
)
} else {
match privacy_mode::turn_on_privacy(self.inner.id) {
Ok(true) => {
if video_service::test_create_capturer(self.inner.id, 5_000) {
video_service::set_privacy_mode_conn_id(self.inner.id);
crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::OnSucceeded,
)
} else {
log::error!(
"Wait privacy mode timeout, turn off privacy mode"
);
video_service::set_privacy_mode_conn_id(0);
let _ = privacy_mode::turn_off_privacy(self.inner.id);
crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::OnFailed,
)
}
}
Ok(false) => crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::OnFailedPlugin,
),
Err(e) => {
log::error!("Failed to turn on privacy mode. {}", e);
if video_service::get_privacy_mode_conn_id() == 0 {
let _ = privacy_mode::turn_off_privacy(0);
}
crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::OnFailed,
)
}
}
};
self.send(msg_out).await;
}
BoolOption::No => {
self.privacy_mode = false;
self.tx_input.send(MessageInput::PrivacyOff).ok();
let msg_out = if !video_service::is_privacy_mode_supported() {
crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::NotSupported,
)
} else {
video_service::set_privacy_mode_conn_id(0);
privacy_mode::turn_off_privacy(self.inner.id)
};
self.send(msg_out).await;
}
_ => {}
}
@ -1318,3 +1366,43 @@ fn try_activate_screen() {
mouse_move_relative(6, 6);
});
}
mod privacy_mode {
use super::*;
pub(super) fn turn_off_privacy(_conn_id: i32) -> Message {
#[cfg(windows)]
{
use crate::ui::win_privacy::*;
let res = turn_off_privacy(_conn_id, None);
match res {
Ok(_) => crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::OffSucceeded,
),
Err(e) => {
log::error!("Failed to turn off privacy mode {}", e);
crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::OffFailed,
)
}
}
}
#[cfg(not(windows))]
{
crate::common::make_privacy_mode_msg(back_notification::PrivacyModeState::OffFailed)
}
}
pub(super) fn turn_on_privacy(_conn_id: i32) -> ResultType<bool> {
#[cfg(windows)]
{
let plugin_exitst = crate::ui::win_privacy::turn_on_privacy(_conn_id)?;
Ok(plugin_exitst)
}
#[cfg(not(windows))]
{
Ok(true)
}
}
}

View File

@ -148,6 +148,16 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
}
}
pub fn send_to_others(&self, msg: Message, id: i32) {
let msg = Arc::new(msg);
let mut lock = self.0.write().unwrap();
for (sid, s) in lock.subscribes.iter_mut() {
if *sid != id {
s.send(msg.clone());
}
}
}
pub fn send_shared(&self, msg: Arc<Message>) {
let mut lock = self.0.write().unwrap();
for s in lock.subscribes.values_mut() {

View File

@ -19,19 +19,18 @@
// https://slhck.info/video/2017/03/01/rate-control.html
use super::*;
use hbb_common::tokio::{
runtime::Runtime,
sync::{
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
Mutex as TokioMutex,
},
use hbb_common::tokio::sync::{
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
Mutex as TokioMutex,
};
use scrap::{Capturer, Config, Display, EncodeFrame, Encoder, VideoCodecId, STRIDE_ALIGN};
use scrap::{Capturer, Config, Display, EncodeFrame, Encoder, Frame, VideoCodecId, STRIDE_ALIGN};
use std::{
collections::HashSet,
io::ErrorKind::WouldBlock,
io::{ErrorKind::WouldBlock, Result},
time::{self, Duration, Instant},
};
#[cfg(windows)]
use virtual_display;
pub const NAME: &'static str = "video";
@ -45,16 +44,39 @@ lazy_static::lazy_static! {
let (tx, rx) = unbounded_channel();
(tx, Arc::new(TokioMutex::new(rx)))
};
static ref PRIVACY_MODE_CONN_ID: Mutex<i32> = Mutex::new(0);
static ref IS_CAPTURER_MAGNIFIER_SUPPORTED: bool = is_capturer_mag_supported();
}
fn is_capturer_mag_supported() -> bool {
#[cfg(windows)]
return scrap::CapturerMag::is_supported();
#[cfg(not(windows))]
false
}
pub fn notify_video_frame_feched(conn_id: i32, frame_tm: Option<Instant>) {
FRAME_FETCHED_NOTIFIER.0.send((conn_id, frame_tm)).unwrap()
}
pub fn set_privacy_mode_conn_id(conn_id: i32) {
*PRIVACY_MODE_CONN_ID.lock().unwrap() = conn_id
}
pub fn get_privacy_mode_conn_id() -> i32 {
*PRIVACY_MODE_CONN_ID.lock().unwrap()
}
pub fn is_privacy_mode_supported() -> bool {
#[cfg(windows)]
return *IS_CAPTURER_MAGNIFIER_SUPPORTED;
#[cfg(not(windows))]
return false;
}
struct VideoFrameController {
cur: Instant,
send_conn_ids: HashSet<i32>,
rt: Runtime,
}
impl VideoFrameController {
@ -62,7 +84,6 @@ impl VideoFrameController {
Self {
cur: Instant::now(),
send_conn_ids: HashSet::new(),
rt: Runtime::new().unwrap(),
}
}
@ -77,46 +98,69 @@ impl VideoFrameController {
}
}
fn blocking_wait_next(&mut self, timeout_millis: u128) {
#[tokio::main(flavor = "current_thread")]
async fn try_wait_next(&mut self, fetched_conn_ids: &mut HashSet<i32>, timeout_millis: u64) {
if self.send_conn_ids.is_empty() {
return;
}
let send_conn_ids = self.send_conn_ids.clone();
self.rt.block_on(async move {
let mut fetched_conn_ids = HashSet::new();
let begin = Instant::now();
while begin.elapsed().as_millis() < timeout_millis {
let timeout_dur =
Duration::from_millis((timeout_millis - begin.elapsed().as_millis()) as u64);
match tokio::time::timeout(
timeout_dur,
FRAME_FETCHED_NOTIFIER.1.lock().await.recv(),
)
.await
{
Err(_) => {
// break if timeout
// log::error!("blocking wait frame receiving timeout {}", timeout_millis);
break;
}
Ok(Some((id, instant))) => {
if let Some(tm) = instant {
log::trace!("Channel recv latency: {}", tm.elapsed().as_secs_f32());
}
fetched_conn_ids.insert(id);
// break if all connections have received current frame
if fetched_conn_ids.len() >= send_conn_ids.len() {
break;
}
}
Ok(None) => {
// this branch would nerver be reached
}
}
let timeout_dur = Duration::from_millis(timeout_millis as u64);
match tokio::time::timeout(timeout_dur, FRAME_FETCHED_NOTIFIER.1.lock().await.recv()).await
{
Err(_) => {
// break if timeout
// log::error!("blocking wait frame receiving timeout {}", timeout_millis);
}
});
Ok(Some((id, instant))) => {
if let Some(tm) = instant {
log::trace!("Channel recv latency: {}", tm.elapsed().as_secs_f32());
}
fetched_conn_ids.insert(id);
}
Ok(None) => {
// this branch would nerver be reached
}
}
}
}
trait TraitCapturer {
fn frame<'a>(&'a mut self, timeout_ms: u32) -> Result<Frame<'a>>;
#[cfg(windows)]
fn is_gdi(&self) -> bool;
#[cfg(windows)]
fn set_gdi(&mut self) -> bool;
}
impl TraitCapturer for Capturer {
fn frame<'a>(&'a mut self, timeout_ms: u32) -> Result<Frame<'a>> {
self.frame(timeout_ms)
}
#[cfg(windows)]
fn is_gdi(&self) -> bool {
self.is_gdi()
}
#[cfg(windows)]
fn set_gdi(&mut self) -> bool {
self.set_gdi()
}
}
#[cfg(windows)]
impl TraitCapturer for scrap::CapturerMag {
fn frame<'a>(&'a mut self, _timeout_ms: u32) -> Result<Frame<'a>> {
self.frame(_timeout_ms)
}
fn is_gdi(&self) -> bool {
false
}
fn set_gdi(&mut self) -> bool {
false
}
}
@ -132,7 +176,7 @@ fn check_display_changed(
last_width: usize,
last_hegiht: usize,
) -> bool {
let displays = match Display::all() {
let displays = match try_get_displays() {
Ok(d) => d,
_ => return false,
};
@ -156,7 +200,126 @@ fn check_display_changed(
return false;
}
// Capturer object is expensive, avoiding to create it frequently.
fn create_capturer(privacy_mode_id: i32, display: Display) -> ResultType<Box<dyn TraitCapturer>> {
let use_yuv = true;
#[cfg(not(windows))]
let c: Option<Box<dyn TraitCapturer>> = None;
#[cfg(windows)]
let mut c: Option<Box<dyn TraitCapturer>> = None;
if privacy_mode_id > 0 {
#[cfg(windows)]
{
use crate::ui::win_privacy::*;
match scrap::CapturerMag::new(
display.origin(),
display.width(),
display.height(),
use_yuv,
) {
Ok(mut c1) => {
let mut ok = false;
let check_begin = Instant::now();
while check_begin.elapsed().as_secs() < 5 {
match c1.exclude("", PRIVACY_WINDOW_NAME) {
Ok(false) => {
ok = false;
std::thread::sleep(std::time::Duration::from_millis(500));
}
Err(e) => {
bail!(
"Failed to exclude privacy window {} - {}, err: {}",
"",
PRIVACY_WINDOW_NAME,
e
);
}
_ => {
ok = true;
break;
}
}
}
if !ok {
bail!(
"Failed to exclude privacy window {} - {} ",
"",
PRIVACY_WINDOW_NAME
);
}
log::debug!("Create maginifier capture for {}", privacy_mode_id);
c = Some(Box::new(c1));
}
Err(e) => {
bail!(format!("Failed to create magnifier capture {}", e));
}
}
}
}
let c = match c {
Some(c1) => c1,
None => {
let c1 =
Capturer::new(display, use_yuv).with_context(|| "Failed to create capturer")?;
log::debug!("Create capturer dxgi|gdi");
Box::new(c1)
}
};
Ok(c)
}
#[cfg(windows)]
fn ensure_close_virtual_device() -> ResultType<()> {
let num_displays = Display::all()?.len();
if num_displays == 0 {
// Device may sometimes be uninstalled by user in "Device Manager" Window.
// Closing device will clear the instance data.
virtual_display::close_device();
} else if num_displays > 1 {
// Try close device, if display device changed.
if virtual_display::is_device_created() {
virtual_display::close_device();
}
}
Ok(())
}
pub fn test_create_capturer(privacy_mode_id: i32, timeout_millis: u64) -> bool {
let test_begin = Instant::now();
while test_begin.elapsed().as_millis() < timeout_millis as _ {
if let Ok((_, _, display)) = get_current_display() {
if let Ok(_) = create_capturer(privacy_mode_id, display) {
return true;
}
}
std::thread::sleep(Duration::from_millis(300));
}
false
}
#[cfg(windows)]
fn check_uac_switch(privacy_mode_id: i32, captuerer_privacy_mode_id: i32) -> ResultType<()> {
if captuerer_privacy_mode_id != 0 {
if privacy_mode_id != captuerer_privacy_mode_id {
if !crate::ui::win_privacy::is_process_consent_running()? {
bail!("consent.exe is running");
}
}
if crate::ui::win_privacy::is_process_consent_running()? {
bail!("consent.exe is running");
}
}
Ok(())
}
fn run(sp: GenericService) -> ResultType<()> {
#[cfg(windows)]
ensure_close_virtual_device()?;
let fps = 30;
let wait = 1000 / fps;
let spf = time::Duration::from_secs_f32(1. / (fps as f32));
@ -172,8 +335,27 @@ fn run(sp: GenericService) -> ResultType<()> {
num_cpus::get_physical(),
num_cpus::get(),
);
// Capturer object is expensive, avoiding to create it frequently.
let mut c = Capturer::new(display, true).with_context(|| "Failed to create capturer")?;
let privacy_mode_id = *PRIVACY_MODE_CONN_ID.lock().unwrap();
#[cfg(not(windows))]
let captuerer_privacy_mode_id = privacy_mode_id;
#[cfg(windows)]
let mut captuerer_privacy_mode_id = privacy_mode_id;
#[cfg(windows)]
if crate::ui::win_privacy::is_process_consent_running()? {
captuerer_privacy_mode_id = 0;
}
log::debug!(
"Try create capturer with captuerer privacy mode id {}",
captuerer_privacy_mode_id,
);
if privacy_mode_id != captuerer_privacy_mode_id {
log::info!("In privacy mode, but show UAC prompt window for now");
} else {
log::info!("In privacy mode, the peer side cannot watch the screen");
}
let mut c = create_capturer(captuerer_privacy_mode_id, display)?;
let q = get_image_quality();
let (bitrate, rc_min_quantizer, rc_max_quantizer, speed) = get_quality(width, height, q);
@ -220,6 +402,9 @@ fn run(sp: GenericService) -> ResultType<()> {
#[cfg(windows)]
log::info!("gdi: {}", c.is_gdi());
while sp.ok() {
#[cfg(windows)]
check_uac_switch(privacy_mode_id, captuerer_privacy_mode_id)?;
if *SWITCH.lock().unwrap() {
bail!("SWITCH");
}
@ -227,6 +412,7 @@ fn run(sp: GenericService) -> ResultType<()> {
*SWITCH.lock().unwrap() = true;
bail!("SWITCH");
}
check_privacy_mode_changed(&sp, privacy_mode_id)?;
if get_image_quality() != q {
bail!("SWITCH");
}
@ -245,12 +431,13 @@ fn run(sp: GenericService) -> ResultType<()> {
bail!("SWITCH");
}
}
*LAST_ACTIVE.lock().unwrap() = now;
frame_controller.reset();
#[cfg(any(target_os = "android", target_os = "ios"))]
let res = match c.frame(wait as _) {
let res = match (*c).frame(wait as _) {
Ok(frame) => {
let time = now - start;
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
@ -273,7 +460,7 @@ fn run(sp: GenericService) -> ResultType<()> {
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let res = match c.frame(wait as _) {
let res = match (*c).frame(wait as _) {
Ok(frame) => {
let time = now - start;
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
@ -320,8 +507,19 @@ fn run(sp: GenericService) -> ResultType<()> {
_ => {}
}
// i love 3, 6, 8
frame_controller.blocking_wait_next(3_000);
let mut fetched_conn_ids = HashSet::new();
let timeout_millis = 3_000u64;
let wait_begin = Instant::now();
while wait_begin.elapsed().as_millis() < timeout_millis as _ {
check_privacy_mode_changed(&sp, privacy_mode_id)?;
#[cfg(windows)]
check_uac_switch(privacy_mode_id, captuerer_privacy_mode_id)?;
frame_controller.try_wait_next(&mut fetched_conn_ids, 300);
// break if all connections have received current frame
if fetched_conn_ids.len() >= frame_controller.send_conn_ids.len() {
break;
}
}
let elapsed = now.elapsed();
// may need to enable frame(timeout)
@ -333,6 +531,21 @@ fn run(sp: GenericService) -> ResultType<()> {
Ok(())
}
#[inline]
fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> ResultType<()> {
let privacy_mode_id_2 = *PRIVACY_MODE_CONN_ID.lock().unwrap();
if privacy_mode_id != privacy_mode_id_2 {
if privacy_mode_id_2 != 0 {
let msg_out = crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::OnByOther,
);
sp.send_to_others(msg_out, privacy_mode_id_2);
}
bail!("SWITCH");
}
Ok(())
}
#[inline]
fn create_msg(vp9s: Vec<VP9>) -> Message {
let mut msg_out = Message::new();
@ -416,7 +629,7 @@ pub fn handle_one_frame_encoded(
}
fn get_display_num() -> usize {
if let Ok(d) = Display::all() {
if let Ok(d) = try_get_displays() {
d.len()
} else {
0
@ -430,7 +643,7 @@ pub fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
}
let mut displays = Vec::new();
let mut primary = 0;
for (i, d) in Display::all()?.iter().enumerate() {
for (i, d) in try_get_displays()?.iter().enumerate() {
if d.is_primary() {
primary = i;
}
@ -467,7 +680,7 @@ pub fn refresh() {
}
fn get_primary() -> usize {
if let Ok(all) = Display::all() {
if let Ok(all) = try_get_displays() {
for (i, d) in all.iter().enumerate() {
if d.is_primary() {
return i;
@ -481,9 +694,44 @@ pub fn switch_to_primary() {
switch_display(get_primary() as _);
}
#[cfg(not(windows))]
fn try_get_displays() -> ResultType<Vec<Display>> {
Ok(Display::all()?)
}
#[cfg(windows)]
fn try_get_displays() -> ResultType<Vec<Display>> {
let mut displays = Display::all()?;
if displays.len() == 0 {
log::debug!("no displays, create virtual display");
// Try plugin monitor
if !virtual_display::is_device_created() {
if let Err(e) = virtual_display::create_device() {
log::debug!("Create device failed {}", e);
}
}
if virtual_display::is_device_created() {
if let Err(e) = virtual_display::plug_in_monitor() {
log::debug!("Plug in monitor failed {}", e);
} else {
if let Err(e) = virtual_display::update_monitor_modes() {
log::debug!("Update monitor modes failed {}", e);
}
}
}
displays = Display::all()?;
} else if displays.len() > 1 {
// If more than one displays exists, close RustDeskVirtualDisplay
if virtual_display::is_device_created() {
virtual_display::close_device()
}
}
Ok(displays)
}
fn get_current_display() -> ResultType<(usize, usize, Display)> {
let mut current = *CURRENT_DISPLAY.lock().unwrap() as usize;
let mut displays = Display::all()?;
let mut displays = try_get_displays()?;
if displays.len() == 0 {
bail!("No displays");
}

View File

@ -3,6 +3,8 @@ mod cm;
mod inline;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "windows")]
pub mod win_privacy;
pub mod remote;
use crate::common::SOFTWARE_UPDATE_URL;
use crate::ipc;

View File

@ -399,6 +399,13 @@ impl ConnectionManager {
}
}
fn send_data(&self, id: i32, data: Data) {
let lock = self.read().unwrap();
if let Some(s) = lock.senders.get(&id) {
allow_err!(s.send(data));
}
}
fn authorize(&self, id: i32) {
let lock = self.read().unwrap();
if let Some(s) = lock.senders.get(&id) {
@ -442,6 +449,21 @@ async fn start_ipc(cm: ConnectionManager) {
#[cfg(windows)]
std::thread::spawn(move || start_clipboard_file(cm_clip, _rx_file));
#[cfg(windows)]
std::thread::spawn(move || {
log::info!("try create privacy mode window");
#[cfg(windows)]
{
if let Err(e) = crate::platform::windows::check_update_broker_process() {
log::warn!(
"Failed to check update broker process. Privacy mode may not work properly. {}",
e
);
}
}
allow_err!(crate::ui::win_privacy::start());
});
match new_listener("_cm").await {
Ok(mut incoming) => {
while let Some(result) = incoming.next().await {
@ -452,6 +474,8 @@ async fn start_ipc(cm: ConnectionManager) {
let cm = cm.clone();
let tx_file = tx_file.clone();
tokio::spawn(async move {
// for tmp use, without real conn id
let conn_id_tmp = -1;
let mut conn_id: i32 = 0;
let (tx, mut rx) = mpsc::unbounded_channel::<Data>();
let mut write_jobs: Vec<fs::TransferJob> = Vec::new();
@ -476,6 +500,10 @@ async fn start_ipc(cm: ConnectionManager) {
log::info!("cm ipc connection closed from connection request");
break;
}
Data::PrivacyModeState((id, _)) => {
conn_id = conn_id_tmp;
cm.send_data(id, data)
}
_ => {
cm.handle_data(conn_id, data, &tx_file, &mut write_jobs, &mut stream).await;
}
@ -491,7 +519,9 @@ async fn start_ipc(cm: ConnectionManager) {
}
}
}
cm.remove_connection(conn_id);
if conn_id != conn_id_tmp {
cm.remove_connection(conn_id);
}
});
}
Err(err) => {

View File

@ -126,6 +126,7 @@ class Header: Reactor.Component {
updateWindowToolbarPosition();
var style = "flow:horizontal;";
if (is_osx) style += "margin:*";
self.timer(1ms, updatePrivacyMode);
self.timer(1ms, toggleMenuState);
return <div style={style}>
{is_osx || is_xfce ? "" : <span #fullscreen>{svg_fullscreen}</span>}
@ -162,7 +163,7 @@ class Header: Reactor.Component {
{is_win && pi.platform == 'Windows' && file_enabled ? <li #enable-file-transfer .toggle-option><span>{svg_checkmark}</span>{translate('File transfer')}</li> : ""}
{keyboard_enabled && clipboard_enabled ? <li #disable-clipboard .toggle-option><span>{svg_checkmark}</span>{translate('Disable clipboard')}</li> : ""}
{keyboard_enabled ? <li #lock-after-session-end .toggle-option><span>{svg_checkmark}</span>{translate('Lock after session end')}</li> : ""}
{false && keyboard_enabled && pi.platform == "Windows" ? <li #privacy-mode .toggle-option><span>{svg_checkmark}</span>{translate('Privacy mode')}</li> : ""}
{keyboard_enabled && pi.platform == "Windows" ? <li #privacy-mode><span>{svg_checkmark}</span>{translate('Privacy mode')}</li> : ""}
</menu>
</popup>;
}
@ -312,6 +313,8 @@ class Header: Reactor.Component {
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")) {
handler.toggle_option(me.id);
toggleMenuState();
@ -354,17 +357,11 @@ function toggleMenuState() {
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", "privacy-mode"]) {
for (var id in ["show-remote-cursor", "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);
el.attributes.toggleClass("selected", value);
if (id == "privacy-mode") {
var el = $(li#block-input);
if (el) {
el.state.disabled = value;
}
}
}
}
}
@ -400,6 +397,46 @@ handler.updatePi = function(v) {
}
}
function updatePrivacyMode() {
var el = $(li#privacy-mode);
if (el) {
var supported = handler.is_privacy_mode_supported();
if (!supported) {
// el.attributes.toggleClass("line-through", true);
el.style["display"]="none";
} else {
var value = handler.get_toggle_option("privacy-mode");
el.attributes.toggleClass("selected", value);
var el = $(li#block-input);
if (el) {
el.state.disabled = value;
}
}
}
}
handler.updatePrivacyMode = updatePrivacyMode;
function togglePrivacyMode(privacy_id) {
var supported = handler.is_privacy_mode_supported();
if (!supported) {
msgbox("nocancel", translate("Privacy mode"), translate("Unsupported"), function() { });
} else {
handler.toggle_option(privacy_id);
}
}
handler.updateBlockInputState = function(input_blocked) {
if (!input_blocked) {
handler.toggle_option("block-input");
input_blocked = true;
$(#block-input).text = translate("Unblock user input");
} else {
handler.toggle_option("unblock-input");
input_blocked = false;
$(#block-input).text = translate("Block user input");
}
}
handler.switchDisplay = function(i) {
pi.current_display = i;
header.update();

View File

@ -24,8 +24,8 @@ use clipboard::{
};
use enigo::{self, Enigo, KeyboardControllable};
use hbb_common::fs::{
can_enable_overwrite_detection, get_string, new_send_confirm,
DigestCheckResult, RemoveJobMeta, get_job,
can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult,
RemoveJobMeta,
};
use hbb_common::{
allow_err,
@ -48,7 +48,7 @@ use hbb_common::{config::TransferSerde, fs::TransferJobMeta};
use crate::clipboard_file::*;
use crate::{
client::*,
common::{self, check_clipboard, update_clipboard, ClipboardContext, CLIPBOARD_INTERVAL}
common::{self, check_clipboard, update_clipboard, ClipboardContext, CLIPBOARD_INTERVAL},
};
type Video = AssetPtr<video_destination>;
@ -226,6 +226,7 @@ impl sciter::EventHandler for Handler {
fn save_custom_image_quality(i32, i32);
fn refresh_video();
fn get_toggle_option(String);
fn is_privacy_mode_supported();
fn toggle_option(String);
fn get_remember();
fn peer_platform();
@ -266,7 +267,8 @@ impl Handler {
std::env::set_var("KEYBOARD_ONLY", "y"); // pass to rdev
use rdev::{EventType::*, *};
let func = move |evt: Event| {
if !IS_IN.load(Ordering::SeqCst) || !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst) {
if !IS_IN.load(Ordering::SeqCst) || !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst)
{
return;
}
let (key, down) = match evt.event_type {
@ -496,7 +498,7 @@ impl Handler {
}
#[inline]
fn save_config(&self, config: PeerConfig) {
pub(super) fn save_config(&self, config: PeerConfig) {
self.lc.write().unwrap().save_config(config);
}
@ -505,7 +507,7 @@ impl Handler {
}
#[inline]
fn load_config(&self) -> PeerConfig {
pub(super) fn load_config(&self) -> PeerConfig {
load_config(&self.id)
}
@ -523,6 +525,10 @@ impl Handler {
self.lc.read().unwrap().get_toggle_option(&name)
}
fn is_privacy_mode_supported(&self) -> bool {
self.lc.read().unwrap().is_privacy_mode_supported()
}
fn refresh_video(&mut self) {
self.send(Data::Message(LoginConfigHandler::refresh()));
}
@ -1655,7 +1661,12 @@ impl Remote {
Data::AddJob((id, path, to, file_num, include_hidden, is_remote)) => {
let od = can_enable_overwrite_detection(self.handler.lc.read().unwrap().version);
if is_remote {
log::debug!("new write waiting job {}, write to {} from remote {}", id, to, path);
log::debug!(
"new write waiting job {}, write to {} from remote {}",
id,
to,
path
);
let mut job = fs::TransferJob::new_write(
id,
path.clone(),
@ -1703,15 +1714,27 @@ impl Remote {
if let Some(job) = get_job(id, &mut self.write_jobs) {
job.is_last_job = false;
allow_err!(
peer.send(&fs::new_send(id, job.remote.clone(), job.file_num, job.show_hidden))
peer.send(&fs::new_send(
id,
job.remote.clone(),
job.file_num,
job.show_hidden
))
.await
);
}
} else {
if let Some(job) = get_job(id, &mut self.read_jobs) {
job.is_last_job = false;
allow_err!(peer.send(&fs::new_receive(id, job.path.to_string_lossy().to_string(),
job.file_num, job.files.clone())).await);
allow_err!(
peer.send(&fs::new_receive(
id,
job.path.to_string_lossy().to_string(),
job.file_num,
job.files.clone()
))
.await
);
}
}
}
@ -2019,6 +2042,9 @@ impl Remote {
Some(message::Union::file_response(fr)) => {
match fr.union {
Some(file_response::Union::dir(fd)) => {
#[cfg(windows)]
let entries = fd.entries.to_vec();
#[cfg(not(windows))]
let mut entries = fd.entries.to_vec();
#[cfg(not(windows))]
{
@ -2217,9 +2243,10 @@ impl Remote {
self.handler.msgbox("error", "Connection Error", &c);
return false;
}
Some(misc::Union::option_response(resp)) => {
self.handler
.msgbox("custom-error", "Option Error", &resp.error);
Some(misc::Union::back_notification(notification)) => {
if !self.handle_back_notification(notification).await {
return false;
}
}
_ => {}
},
@ -2245,6 +2272,127 @@ impl Remote {
true
}
async fn handle_back_notification(&mut self, notification: BackNotification) -> bool {
match notification.union {
Some(back_notification::Union::block_input_state(state)) => {
self.handle_back_msg_block_input(
state.enum_value_or(back_notification::BlockInputState::StateUnknown),
)
.await;
}
Some(back_notification::Union::privacy_mode_state(state)) => {
if !self
.handle_back_msg_privacy_mode(
state.enum_value_or(back_notification::PrivacyModeState::StateUnknown),
)
.await
{
return false;
}
}
_ => {}
}
true
}
#[inline(always)]
fn update_block_input_state(&mut self, on: bool) {
self.handler.call("updateBlockInputState", &make_args!(on));
}
async fn handle_back_msg_block_input(&mut self, state: back_notification::BlockInputState) {
match state {
back_notification::BlockInputState::OnSucceeded => {
self.update_block_input_state(true);
}
back_notification::BlockInputState::OnFailed => {
self.handler
.msgbox("custom-error", "Block user input", "Failed");
self.update_block_input_state(false);
}
back_notification::BlockInputState::OffSucceeded => {
self.update_block_input_state(false);
}
back_notification::BlockInputState::OffFailed => {
self.handler
.msgbox("custom-error", "Unblock user input", "Failed");
}
_ => {}
}
}
#[inline(always)]
fn update_privacy_mode(&mut self, on: bool) {
let mut config = self.handler.load_config();
config.privacy_mode = on;
self.handler.save_config(config);
self.handler.call("updatePrivacyMode", &[]);
}
async fn handle_back_msg_privacy_mode(
&mut self,
state: back_notification::PrivacyModeState,
) -> bool {
match state {
back_notification::PrivacyModeState::OnByOther => {
self.handler.msgbox(
"error",
"Connecting...",
"Someone turns on privacy mode, exit",
);
return false;
}
back_notification::PrivacyModeState::NotSupported => {
self.handler
.msgbox("custom-error", "Privacy mode", "Unsupported");
self.update_privacy_mode(false);
}
back_notification::PrivacyModeState::OnSucceeded => {
self.handler
.msgbox("custom-nocancel", "Privacy mode", "In privacy mode");
self.update_privacy_mode(true);
}
back_notification::PrivacyModeState::OnFailedDenied => {
self.handler
.msgbox("custom-error", "Privacy mode", "Peer denied");
self.update_privacy_mode(false);
}
back_notification::PrivacyModeState::OnFailedPlugin => {
self.handler
.msgbox("custom-error", "Privacy mode", "Please install plugins");
self.update_privacy_mode(false);
}
back_notification::PrivacyModeState::OnFailed => {
self.handler
.msgbox("custom-error", "Privacy mode", "Failed");
self.update_privacy_mode(false);
}
back_notification::PrivacyModeState::OffSucceeded => {
self.handler
.msgbox("custom-nocancel", "Privacy mode", "Out privacy mode");
self.update_privacy_mode(false);
}
back_notification::PrivacyModeState::OffByPeer => {
self.handler
.msgbox("custom-error", "Privacy mode", "Peer exit");
self.update_privacy_mode(false);
}
back_notification::PrivacyModeState::OffFailed => {
self.handler
.msgbox("custom-error", "Privacy mode", "Failed to turn off");
}
back_notification::PrivacyModeState::OffUnknown => {
self.handler
.msgbox("custom-error", "Privacy mode", "Turned off");
// log::error!("Privacy mode is turned off with unknown reason");
self.update_privacy_mode(false);
}
_ => {}
}
true
}
fn check_clipboard_file_context(&mut self) {
#[cfg(windows)]
{
@ -2333,6 +2481,7 @@ impl Interface for Handler {
} else if !self.is_port_forward() {
if pi.displays.is_empty() {
self.lc.write().unwrap().handle_peer_info(username, pi);
self.call("updatePrivacyMode", &[]);
self.msgbox("error", "Remote Error", "No Display");
return;
}
@ -2371,6 +2520,7 @@ impl Interface for Handler {
}
}
self.lc.write().unwrap().handle_peer_info(username, pi);
self.call("updatePrivacyMode", &[]);
self.call("updatePi", &make_args!(pi_sciter));
if self.is_file_transfer() {
self.call2("closeSuccess", &make_args!());

571
src/ui/win_privacy.rs Normal file
View File

@ -0,0 +1,571 @@
use crate::{
ipc::{connect, Data, PrivacyModeState},
platform::windows::get_user_token,
};
use hbb_common::{allow_err, bail, lazy_static, log, tokio, ResultType};
use std::{
ffi::CString,
sync::Mutex,
time::{Duration, Instant},
};
use winapi::{
ctypes::c_int,
shared::{
minwindef::{DWORD, FALSE, HMODULE, LOBYTE, LPARAM, LRESULT, UINT, WPARAM},
ntdef::{HANDLE, NULL},
windef::{HHOOK, HWND, POINT},
},
um::{
errhandlingapi::GetLastError,
handleapi::CloseHandle,
libloaderapi::{GetModuleHandleA, GetModuleHandleExA, GetProcAddress},
memoryapi::{VirtualAllocEx, WriteProcessMemory},
processthreadsapi::{
CreateProcessAsUserW, GetCurrentThreadId, QueueUserAPC, ResumeThread,
PROCESS_INFORMATION, STARTUPINFOW,
},
winbase::{WTSGetActiveConsoleSessionId, CREATE_SUSPENDED, DETACHED_PROCESS},
winnt::{MEM_COMMIT, PAGE_READWRITE},
winuser::*,
},
};
pub const ORIGIN_PROCESS_EXE: &'static str = "C:\\Windows\\System32\\RuntimeBroker.exe";
pub const INJECTED_PROCESS_EXE: &'static str = "RuntimeBroker_rustdesk.exe";
pub const PRIVACY_WINDOW_NAME: &'static str = "RustDeskPrivacyWindow";
pub const GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT: u32 = 2;
pub const GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS: u32 = 4;
const WM_USER_EXIT_HOOK: u32 = WM_USER + 1;
lazy_static::lazy_static! {
static ref DLL_FOUND: Mutex<bool> = Mutex::new(false);
static ref CONN_ID: Mutex<i32> = Mutex::new(0);
static ref CUR_HOOK_THREAD_ID: Mutex<DWORD> = Mutex::new(0);
static ref WND_HANDLERS: Mutex<WindowHandlers> = Mutex::new(WindowHandlers{hthread: 0, hprocess: 0});
}
struct WindowHandlers {
hthread: u64,
hprocess: u64,
}
impl Drop for WindowHandlers {
fn drop(&mut self) {
unsafe {
if self.hthread != 0 {
CloseHandle(self.hthread as _);
}
self.hthread = 0;
if self.hprocess != 0 {
CloseHandle(self.hprocess as _);
}
self.hprocess = 0;
}
}
}
pub fn turn_on_privacy(conn_id: i32) -> ResultType<bool> {
let exe_file = std::env::current_exe()?;
if let Some(cur_dir) = exe_file.parent() {
if !cur_dir.join("WindowInjection.dll").exists() {
return Ok(false);
}
} else {
bail!(
"Invalid exe parent for {}",
exe_file.to_string_lossy().as_ref()
);
}
if !*DLL_FOUND.lock().unwrap() {
log::info!("turn_on_privacy, dll not found when started, try start");
start()?;
std::thread::sleep(std::time::Duration::from_millis(1_000));
}
let pre_conn_id = *CONN_ID.lock().unwrap();
if pre_conn_id == conn_id {
return Ok(true);
}
if pre_conn_id != 0 {
bail!("Privacy occupied by another one");
}
let hwnd = wait_find_privacy_hwnd(0)?;
if hwnd.is_null() {
bail!("No privacy window created");
}
privacy_hook::hook()?;
unsafe {
ShowWindow(hwnd as _, SW_SHOW);
}
*CONN_ID.lock().unwrap() = conn_id;
Ok(true)
}
pub fn turn_off_privacy(conn_id: i32, state: Option<PrivacyModeState>) -> ResultType<()> {
let pre_conn_id = *CONN_ID.lock().unwrap();
if pre_conn_id != 0 && conn_id != 0 && pre_conn_id != conn_id {
bail!("Failed to turn off privacy mode that belongs to someone else")
}
privacy_hook::unhook()?;
unsafe {
let hwnd = wait_find_privacy_hwnd(0)?;
if !hwnd.is_null() {
ShowWindow(hwnd, SW_HIDE);
}
}
if pre_conn_id != 0 {
if let Some(state) = state {
allow_err!(set_privacy_mode_state(pre_conn_id, state, 1_000));
}
*CONN_ID.lock().unwrap() = 0;
}
Ok(())
}
pub fn start() -> ResultType<()> {
let mut wnd_handlers = WND_HANDLERS.lock().unwrap();
if wnd_handlers.hprocess != 0 {
return Ok(());
}
let exe_file = std::env::current_exe()?;
if exe_file.parent().is_none() {
bail!("Cannot get parent of current exe file");
}
let cur_dir = exe_file.parent().unwrap();
let dll_file = cur_dir.join("WindowInjection.dll");
if !dll_file.exists() {
bail!(
"Failed to find required file {}",
dll_file.to_string_lossy().as_ref()
);
}
*DLL_FOUND.lock().unwrap() = true;
let hwnd = wait_find_privacy_hwnd(1_000)?;
if !hwnd.is_null() {
log::info!("Privacy window is ready");
return Ok(());
}
// let cmdline = cur_dir.join("MiniBroker.exe").to_string_lossy().to_string();
let cmdline = cur_dir
.join(INJECTED_PROCESS_EXE)
.to_string_lossy()
.to_string();
unsafe {
let cmd_utf16: Vec<u16> = cmdline.encode_utf16().chain(Some(0).into_iter()).collect();
let mut start_info = STARTUPINFOW {
cb: 0,
lpReserved: NULL as _,
lpDesktop: NULL as _,
lpTitle: NULL as _,
dwX: 0,
dwY: 0,
dwXSize: 0,
dwYSize: 0,
dwXCountChars: 0,
dwYCountChars: 0,
dwFillAttribute: 0,
dwFlags: 0,
wShowWindow: 0,
cbReserved2: 0,
lpReserved2: NULL as _,
hStdInput: NULL as _,
hStdOutput: NULL as _,
hStdError: NULL as _,
};
let mut proc_info = PROCESS_INFORMATION {
hProcess: NULL as _,
hThread: NULL as _,
dwProcessId: 0,
dwThreadId: 0,
};
let session_id = WTSGetActiveConsoleSessionId();
let token = get_user_token(session_id, true);
if token.is_null() {
bail!("Failed to get token of current user");
}
let create_res = CreateProcessAsUserW(
token,
NULL as _,
cmd_utf16.as_ptr() as _,
NULL as _,
NULL as _,
FALSE,
CREATE_SUSPENDED | DETACHED_PROCESS,
NULL,
NULL as _,
&mut start_info,
&mut proc_info,
);
CloseHandle(token);
if 0 == create_res {
bail!(
"Failed to create privacy window process {}, code {}",
cmdline,
GetLastError()
);
};
inject_dll(
proc_info.hProcess,
proc_info.hThread,
dll_file.to_string_lossy().as_ref(),
)?;
if 0xffffffff == ResumeThread(proc_info.hThread) {
// CloseHandle
CloseHandle(proc_info.hThread);
CloseHandle(proc_info.hProcess);
bail!(
"Failed to create privacy window process, {}",
GetLastError()
);
}
wnd_handlers.hthread = proc_info.hThread as _;
wnd_handlers.hprocess = proc_info.hProcess as _;
let hwnd = wait_find_privacy_hwnd(1_000)?;
if hwnd.is_null() {
bail!("Failed to get hwnd after started");
}
}
Ok(())
}
unsafe fn inject_dll<'a>(hproc: HANDLE, hthread: HANDLE, dll_file: &'a str) -> ResultType<()> {
let dll_file_utf16: Vec<u16> = dll_file.encode_utf16().chain(Some(0).into_iter()).collect();
let buf = VirtualAllocEx(
hproc,
NULL as _,
dll_file_utf16.len() * 2,
MEM_COMMIT,
PAGE_READWRITE,
);
if buf.is_null() {
bail!("Failed VirtualAllocEx");
}
let mut written: usize = 0;
if 0 == WriteProcessMemory(
hproc,
buf,
dll_file_utf16.as_ptr() as _,
dll_file_utf16.len() * 2,
&mut written,
) {
bail!("Failed WriteProcessMemory");
}
let kernel32_modulename = CString::new("kernel32")?;
let hmodule = GetModuleHandleA(kernel32_modulename.as_ptr() as _);
if hmodule.is_null() {
bail!("Failed GetModuleHandleA");
}
let load_librarya_name = CString::new("LoadLibraryW")?;
let load_librarya = GetProcAddress(hmodule, load_librarya_name.as_ptr() as _);
if load_librarya.is_null() {
bail!("Failed GetProcAddress of LoadLibraryW");
}
if 0 == QueueUserAPC(Some(std::mem::transmute(load_librarya)), hthread, buf as _) {
bail!("Failed QueueUserAPC");
}
Ok(())
}
fn wait_find_privacy_hwnd(msecs: u128) -> ResultType<HWND> {
let tm_begin = Instant::now();
let wndname = CString::new(PRIVACY_WINDOW_NAME)?;
loop {
unsafe {
let hwnd = FindWindowA(NULL as _, wndname.as_ptr() as _);
if !hwnd.is_null() {
return Ok(hwnd);
}
}
if msecs == 0 || tm_begin.elapsed().as_millis() > msecs {
return Ok(NULL as _);
}
std::thread::sleep(Duration::from_millis(100));
}
}
pub fn is_process_consent_running() -> ResultType<bool> {
let output = std::process::Command::new("cmd")
.args(&["/C", "tasklist | findstr consent.exe"])
.output()?;
Ok(output.status.success() && !output.stdout.is_empty())
}
#[tokio::main(flavor = "current_thread")]
async fn set_privacy_mode_state(
conn_id: i32,
state: PrivacyModeState,
ms_timeout: u64,
) -> ResultType<()> {
let mut c = connect(ms_timeout, "_cm").await?;
c.send(&Data::PrivacyModeState((conn_id, state))).await
}
pub(super) mod privacy_hook {
use super::*;
use std::sync::mpsc::{channel, Sender};
fn do_hook(tx: Sender<String>) -> ResultType<(HHOOK, HHOOK)> {
let invalid_ret = (0 as HHOOK, 0 as HHOOK);
let mut cur_hook_thread_id = CUR_HOOK_THREAD_ID.lock().unwrap();
if *cur_hook_thread_id != 0 {
// unreachable!
tx.send("Already hooked".to_owned())?;
return Ok(invalid_ret);
}
unsafe {
let mut hm_keyboard = 0 as HMODULE;
if 0 == GetModuleHandleExA(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
| GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
DefWindowProcA as _,
&mut hm_keyboard as _,
) {
tx.send(format!(
"Failed to GetModuleHandleExA, error: {}",
GetLastError()
))?;
return Ok(invalid_ret);
}
let mut hm_mouse = 0 as HMODULE;
if 0 == GetModuleHandleExA(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
| GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
DefWindowProcA as _,
&mut hm_mouse as _,
) {
tx.send(format!(
"Failed to GetModuleHandleExA, error: {}",
GetLastError()
))?;
return Ok(invalid_ret);
}
let hook_keyboard = SetWindowsHookExA(
WH_KEYBOARD_LL,
Some(privacy_mode_hook_keyboard),
hm_keyboard,
0,
);
if hook_keyboard.is_null() {
tx.send(format!(" SetWindowsHookExA keyboard {}", GetLastError()))?;
return Ok(invalid_ret);
}
let hook_mouse =
SetWindowsHookExA(WH_MOUSE_LL, Some(privacy_mode_hook_mouse), hm_mouse, 0);
if hook_mouse.is_null() {
if FALSE == UnhookWindowsHookEx(hook_keyboard) {
// Fatal error
log::error!(" UnhookWindowsHookEx keyboard {}", GetLastError());
}
tx.send(format!(" SetWindowsHookExA mouse {}", GetLastError()))?;
return Ok(invalid_ret);
}
*cur_hook_thread_id = GetCurrentThreadId();
tx.send("".to_owned())?;
return Ok((hook_keyboard, hook_mouse));
}
}
pub fn hook() -> ResultType<()> {
let (tx, rx) = channel();
std::thread::spawn(move || {
let hook_keyboard;
let hook_mouse;
unsafe {
match do_hook(tx.clone()) {
Ok(hooks) => {
hook_keyboard = hooks.0;
hook_mouse = hooks.1;
}
Err(e) => {
// Fatal error
tx.send(format!("Unexpected err when hook {}", e)).unwrap();
return;
}
}
if hook_keyboard.is_null() {
return;
}
let mut msg = MSG {
hwnd: NULL as _,
message: 0 as _,
wParam: 0 as _,
lParam: 0 as _,
time: 0 as _,
pt: POINT {
x: 0 as _,
y: 0 as _,
},
};
while FALSE != GetMessageA(&mut msg, NULL as _, 0, 0) {
if msg.message == WM_USER_EXIT_HOOK {
break;
}
TranslateMessage(&msg);
DispatchMessageA(&msg);
}
if FALSE == UnhookWindowsHookEx(hook_keyboard as _) {
// Fatal error
log::error!("Failed UnhookWindowsHookEx keyboard {}", GetLastError());
}
if FALSE == UnhookWindowsHookEx(hook_mouse as _) {
// Fatal error
log::error!("Failed UnhookWindowsHookEx mouse {}", GetLastError());
}
*CUR_HOOK_THREAD_ID.lock().unwrap() = 0;
}
});
match rx.recv() {
Ok(msg) => {
if msg == "" {
Ok(())
} else {
bail!(msg)
}
}
Err(e) => {
bail!("Failed to wait hook result {}", e)
}
}
}
pub fn unhook() -> ResultType<()> {
unsafe {
let cur_hook_thread_id = CUR_HOOK_THREAD_ID.lock().unwrap();
if *cur_hook_thread_id != 0 {
if FALSE == PostThreadMessageA(*cur_hook_thread_id, WM_USER_EXIT_HOOK, 0, 0) {
bail!("Failed to post message to exit hook, {}", GetLastError());
}
}
}
Ok(())
}
#[no_mangle]
pub extern "system" fn privacy_mode_hook_keyboard(
code: c_int,
w_param: WPARAM,
l_param: LPARAM,
) -> LRESULT {
if code < 0 {
unsafe {
return CallNextHookEx(NULL as _, code, w_param, l_param);
}
}
let ks = l_param as PKBDLLHOOKSTRUCT;
let w_param2 = w_param as UINT;
unsafe {
if (*ks).dwExtraInfo != enigo::ENIGO_INPUT_EXTRA_VALUE {
// Disable alt key. Alt + Tab will switch windows.
if (*ks).flags & LLKHF_ALTDOWN == LLKHF_ALTDOWN {
return 1;
}
match w_param2 {
WM_KEYDOWN => {
// Disable all keys other than P and Ctrl.
if ![80, 162, 163].contains(&(*ks).vkCode) {
return 1;
}
// NOTE: GetKeyboardState may not work well...
// Check if Ctrl + P is pressed
let cltr_down = (GetKeyState(VK_CONTROL) as u16) & (0x8000 as u16) > 0;
let key = LOBYTE((*ks).vkCode as _);
if cltr_down && (key == 'p' as u8 || key == 'P' as u8) {
// Ctrl + P is pressed, turn off privacy mode
if let Err(e) =
turn_off_privacy(0, Some(crate::ipc::PrivacyModeState::OffByPeer))
{
log::error!("Failed to off_privacy {}", e);
}
}
}
WM_KEYUP => {
log::trace!("WM_KEYUP {}", (*ks).vkCode);
}
_ => {
log::trace!("KEYBOARD OTHER {} {}", w_param2, (*ks).vkCode);
}
}
}
}
unsafe { CallNextHookEx(NULL as _, code, w_param, l_param) }
}
#[no_mangle]
pub extern "system" fn privacy_mode_hook_mouse(
code: c_int,
w_param: WPARAM,
l_param: LPARAM,
) -> LRESULT {
if code < 0 {
unsafe {
return CallNextHookEx(NULL as _, code, w_param, l_param);
}
}
let ms = l_param as PMOUSEHOOKSTRUCT;
unsafe {
if (*ms).dwExtraInfo != enigo::ENIGO_INPUT_EXTRA_VALUE {
return 1;
}
}
unsafe { CallNextHookEx(NULL as _, code, w_param, l_param) }
}
}
mod test {
#[test]
fn privacy_hook() {
//use super::*;
// privacy_hook::hook().unwrap();
// std::thread::sleep(std::time::Duration::from_millis(50));
// privacy_hook::unhook().unwrap();
}
}

View File

@ -53,26 +53,28 @@ DWORD GetLogonPid(DWORD dwSessionId, BOOL as_user)
return dwLogonPid;
}
// if should try WTSQueryUserToken?
// https://stackoverflow.com/questions/7285666/example-code-a-service-calls-createprocessasuser-i-want-the-process-to-run-in
BOOL GetSessionUserTokenWin(OUT LPHANDLE lphUserToken, DWORD dwSessionId, BOOL as_user)
{
BOOL bResult = FALSE;
DWORD Id = GetLogonPid(dwSessionId, as_user);
if (HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Id))
{
bResult = OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, lphUserToken);
CloseHandle(hProcess);
}
return bResult;
}
// START the app as system
extern "C"
{
// if should try WTSQueryUserToken?
// https://stackoverflow.com/questions/7285666/example-code-a-service-calls-createprocessasuser-i-want-the-process-to-run-in
BOOL GetSessionUserTokenWin(OUT LPHANDLE lphUserToken, DWORD dwSessionId, BOOL as_user)
{
BOOL bResult = FALSE;
DWORD Id = GetLogonPid(dwSessionId, as_user);
if (HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Id))
{
bResult = OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, lphUserToken);
CloseHandle(hProcess);
}
return bResult;
}
bool is_windows_server()
{
return IsWindowsServer();
}
HANDLE LaunchProcessWin(LPCWSTR cmd, DWORD dwSessionId, BOOL as_user)
{
HANDLE hProcess = NULL;