privacy_mode: win10 magnifier
Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
parent
85cd066cd7
commit
c269d1c831
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
})
|
||||
},
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
105
libs/scrap/examples/capture_mag.rs
Normal file
105
libs/scrap/examples/capture_mag.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
626
libs/scrap/src/dxgi/mag.rs
Normal file
626
libs/scrap/src/dxgi/mag.rs
Normal file
@ -0,0 +1,626 @@
|
||||
// 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> {
|
||||
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);
|
||||
|
||||
self.rect = RECT {
|
||||
left: x as _,
|
||||
top: y as _,
|
||||
right: (x + w) as _,
|
||||
bottom: (y + h) as _,
|
||||
};
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
use std::{io, mem, ptr, slice};
|
||||
pub mod gdi;
|
||||
pub use gdi::CapturerGDI;
|
||||
pub mod mag;
|
||||
|
||||
use winapi::{
|
||||
shared::{
|
||||
|
@ -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(),
|
||||
|
@ -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
|
||||
}
|
||||
|
11
src/ipc.rs
11
src/ipc.rs
@ -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")]
|
||||
|
@ -265,10 +265,19 @@ 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", "退出"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -270,5 +270,14 @@ 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ý"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -265,8 +265,19 @@ 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"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -265,8 +265,19 @@ 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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -265,8 +265,19 @@ 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"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -265,8 +265,19 @@ 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"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -265,8 +265,19 @@ 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"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -265,8 +265,19 @@ 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"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -270,5 +270,14 @@ 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", "Выключен"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -270,5 +270,14 @@ 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ý"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -270,5 +270,14 @@ 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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -265,8 +265,19 @@ 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ı"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -270,5 +270,15 @@ 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", "退出"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -9,7 +9,8 @@ use hbb_common::{
|
||||
use std::io::prelude::*;
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
io, mem,
|
||||
fs, io, mem,
|
||||
path::Path,
|
||||
sync::{Arc, Mutex},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
@ -404,6 +405,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 +560,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 +798,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() {
|
||||
@ -817,6 +862,7 @@ pub fn update_me() -> ResultType<()> {
|
||||
"
|
||||
chcp 65001
|
||||
sc stop {app_name}
|
||||
taskkill /F /IM {broker_exe}
|
||||
taskkill /F /IM {app_name}.exe
|
||||
copy /Y \"{src_exe}\" \"{exe}\"
|
||||
sc start {app_name}
|
||||
@ -824,6 +870,7 @@ pub fn update_me() -> ResultType<()> {
|
||||
",
|
||||
src_exe = src_exe,
|
||||
exe = exe,
|
||||
broker_exe = crate::ui::win_privacy::INJECTED_PROCESS_EXE,
|
||||
app_name = crate::get_app_name(),
|
||||
lic = register_licence(),
|
||||
);
|
||||
@ -975,6 +1022,7 @@ 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}\"
|
||||
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 +1058,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 +1101,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
|
||||
)
|
||||
}
|
||||
@ -1325,3 +1377,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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,13 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
|
||||
if video_service::get_privacy_mode_conn_id() == id {
|
||||
video_service::set_privacy_mode_conn_id(0);
|
||||
let _ = privacy_mode::turn_off_privacy(id).await;
|
||||
}
|
||||
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 +406,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 +427,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 +458,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 +647,19 @@ 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 +756,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 +1163,44 @@ 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 {
|
||||
video_service::set_privacy_mode_conn_id(0);
|
||||
match privacy_mode::turn_on_privacy(self.inner.id) {
|
||||
Ok(true) => {
|
||||
video_service::set_privacy_mode_conn_id(self.inner.id);
|
||||
crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::OnSucceeded,
|
||||
)
|
||||
}
|
||||
Ok(false) => {
|
||||
crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::OnFailedPlugin,
|
||||
)
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to turn on privacy mode. {}", e);
|
||||
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).await
|
||||
};
|
||||
self.send(msg_out).await;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@ -1318,3 +1351,43 @@ fn try_activate_screen() {
|
||||
mouse_move_relative(6, 6);
|
||||
});
|
||||
}
|
||||
|
||||
mod privacy_mode {
|
||||
use super::*;
|
||||
|
||||
pub(super) async 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -26,10 +26,10 @@ use hbb_common::tokio::{
|
||||
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},
|
||||
};
|
||||
|
||||
@ -45,12 +45,36 @@ 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>,
|
||||
@ -120,6 +144,46 @@ impl VideoFrameController {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> GenericService {
|
||||
let sp = GenericService::new(NAME, true);
|
||||
sp.run(run);
|
||||
@ -156,6 +220,76 @@ 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
|
||||
);
|
||||
}
|
||||
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")?;
|
||||
Box::new(c1)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
fn run(sp: GenericService) -> ResultType<()> {
|
||||
let fps = 30;
|
||||
let wait = 1000 / fps;
|
||||
@ -172,8 +306,9 @@ 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();
|
||||
let mut c = create_capturer(privacy_mode_id, display)?;
|
||||
|
||||
let q = get_image_quality();
|
||||
let (bitrate, rc_min_quantizer, rc_max_quantizer, speed) = get_quality(width, height, q);
|
||||
@ -227,6 +362,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");
|
||||
}
|
||||
@ -250,7 +386,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
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 +409,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;
|
||||
@ -333,6 +469,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();
|
||||
|
@ -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;
|
||||
|
32
src/ui/cm.rs
32
src/ui/cm.rs
@ -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) => {
|
||||
|
@ -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();
|
||||
|
135
src/ui/remote.rs
135
src/ui/remote.rs
@ -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();
|
||||
@ -496,7 +497,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 +506,7 @@ impl Handler {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn load_config(&self) -> PeerConfig {
|
||||
pub(super) fn load_config(&self) -> PeerConfig {
|
||||
load_config(&self.id)
|
||||
}
|
||||
|
||||
@ -523,6 +524,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()));
|
||||
}
|
||||
@ -2217,9 +2222,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 +2251,123 @@ 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.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.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 +2456,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 +2495,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!());
|
||||
|
566
src/ui/win_privacy.rs
Normal file
566
src/ui/win_privacy.rs
Normal file
@ -0,0 +1,566 @@
|
||||
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 already created");
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn set_privacy_mode_state(
|
||||
conn_id: i32,
|
||||
state: PrivacyModeState,
|
||||
ms_timeout: u64,
|
||||
) -> ResultType<()> {
|
||||
println!("set_privacy_mode_state begin");
|
||||
let mut c = connect(ms_timeout, "_cm").await?;
|
||||
println!("set_privacy_mode_state connect done");
|
||||
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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user