mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-10 03:41:28 +03:00
feat/virtual_display_privacy_mode
Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
258
src/privacy_mode/win_input.rs
Normal file
258
src/privacy_mode/win_input.rs
Normal file
@@ -0,0 +1,258 @@
|
||||
use hbb_common::{allow_err, bail, lazy_static, log, ResultType};
|
||||
use std::sync::{
|
||||
mpsc::{channel, Sender},
|
||||
Mutex,
|
||||
};
|
||||
use winapi::{
|
||||
ctypes::c_int,
|
||||
shared::{
|
||||
minwindef::{DWORD, FALSE, HMODULE, LOBYTE, LPARAM, LRESULT, UINT, WPARAM},
|
||||
ntdef::NULL,
|
||||
windef::{HHOOK, POINT},
|
||||
},
|
||||
um::{
|
||||
errhandlingapi::GetLastError, libloaderapi::GetModuleHandleExA,
|
||||
processthreadsapi::GetCurrentThreadId, winuser::*,
|
||||
},
|
||||
};
|
||||
|
||||
const GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT: u32 = 2;
|
||||
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 CUR_HOOK_THREAD_ID: Mutex<DWORD> = Mutex::new(0);
|
||||
}
|
||||
|
||||
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
|
||||
allow_err!(tx.send(format!("Unexpected err when hook {}", e)));
|
||||
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 Some(Err(e)) = super::turn_off_privacy(
|
||||
super::INVALID_PRIVACY_MODE_CONN_ID,
|
||||
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();
|
||||
}
|
||||
}
|
||||
416
src/privacy_mode/win_mag.rs
Normal file
416
src/privacy_mode/win_mag.rs
Normal file
@@ -0,0 +1,416 @@
|
||||
use super::{PrivacyMode, INVALID_PRIVACY_MODE_CONN_ID};
|
||||
use crate::{ipc::PrivacyModeState, platform::windows::get_user_token};
|
||||
use hbb_common::{allow_err, bail, log, ResultType};
|
||||
use std::{
|
||||
ffi::CString,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use winapi::{
|
||||
shared::{
|
||||
minwindef::FALSE,
|
||||
ntdef::{HANDLE, NULL},
|
||||
windef::HWND,
|
||||
},
|
||||
um::{
|
||||
errhandlingapi::GetLastError,
|
||||
handleapi::CloseHandle,
|
||||
libloaderapi::{GetModuleHandleA, GetProcAddress},
|
||||
memoryapi::{VirtualAllocEx, WriteProcessMemory},
|
||||
processthreadsapi::{
|
||||
CreateProcessAsUserW, QueueUserAPC, ResumeThread, TerminateProcess,
|
||||
PROCESS_INFORMATION, STARTUPINFOW,
|
||||
},
|
||||
winbase::{WTSGetActiveConsoleSessionId, CREATE_SUSPENDED, DETACHED_PROCESS},
|
||||
winnt::{MEM_COMMIT, PAGE_READWRITE},
|
||||
winuser::*,
|
||||
},
|
||||
};
|
||||
|
||||
pub(super) const PRIVACY_MODE_IMPL: &str = "privacy_mode_impl_mag";
|
||||
|
||||
pub const ORIGIN_PROCESS_EXE: &'static str = "C:\\Windows\\System32\\RuntimeBroker.exe";
|
||||
pub const WIN_MAG_INJECTED_PROCESS_EXE: &'static str = "RuntimeBroker_rustdesk.exe";
|
||||
pub const INJECTED_PROCESS_EXE: &'static str = WIN_MAG_INJECTED_PROCESS_EXE;
|
||||
const PRIVACY_WINDOW_NAME: &'static str = "RustDeskPrivacyWindow";
|
||||
|
||||
struct WindowHandlers {
|
||||
hthread: u64,
|
||||
hprocess: u64,
|
||||
}
|
||||
|
||||
impl Drop for WindowHandlers {
|
||||
fn drop(&mut self) {
|
||||
self.reset();
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowHandlers {
|
||||
fn reset(&mut self) {
|
||||
unsafe {
|
||||
if self.hprocess != 0 {
|
||||
let _res = TerminateProcess(self.hprocess as _, 0);
|
||||
CloseHandle(self.hprocess as _);
|
||||
}
|
||||
self.hprocess = 0;
|
||||
if self.hthread != 0 {
|
||||
CloseHandle(self.hthread as _);
|
||||
}
|
||||
self.hthread = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn is_default(&self) -> bool {
|
||||
self.hthread == 0 && self.hprocess == 0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PrivacyModeImpl {
|
||||
conn_id: i32,
|
||||
handlers: WindowHandlers,
|
||||
}
|
||||
|
||||
impl Default for PrivacyModeImpl {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
conn_id: INVALID_PRIVACY_MODE_CONN_ID,
|
||||
handlers: WindowHandlers {
|
||||
hthread: 0,
|
||||
hprocess: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrivacyMode for PrivacyModeImpl {
|
||||
fn init(&self) -> ResultType<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
allow_err!(self.turn_off_privacy(self.conn_id, None));
|
||||
}
|
||||
|
||||
fn turn_on_privacy(&mut self, conn_id: i32) -> ResultType<bool> {
|
||||
if self.check_on_conn_id(conn_id)? {
|
||||
log::debug!("Privacy mode of conn {} is already on", conn_id);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
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 self.handlers.is_default() {
|
||||
log::info!("turn_on_privacy, dll not found when started, try start");
|
||||
self.start()?;
|
||||
std::thread::sleep(std::time::Duration::from_millis(1_000));
|
||||
}
|
||||
|
||||
let hwnd = wait_find_privacy_hwnd(0)?;
|
||||
if hwnd.is_null() {
|
||||
bail!("No privacy window created");
|
||||
}
|
||||
super::win_input::hook()?;
|
||||
unsafe {
|
||||
ShowWindow(hwnd as _, SW_SHOW);
|
||||
}
|
||||
self.conn_id = conn_id;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn turn_off_privacy(
|
||||
&mut self,
|
||||
conn_id: i32,
|
||||
state: Option<PrivacyModeState>,
|
||||
) -> ResultType<()> {
|
||||
self.check_off_conn_id(conn_id)?;
|
||||
super::win_input::unhook()?;
|
||||
|
||||
unsafe {
|
||||
let hwnd = wait_find_privacy_hwnd(0)?;
|
||||
if !hwnd.is_null() {
|
||||
ShowWindow(hwnd, SW_HIDE);
|
||||
}
|
||||
}
|
||||
|
||||
if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID {
|
||||
if let Some(state) = state {
|
||||
allow_err!(super::set_privacy_mode_state(
|
||||
conn_id,
|
||||
state,
|
||||
PRIVACY_MODE_IMPL.to_string(),
|
||||
1_000
|
||||
));
|
||||
}
|
||||
self.conn_id = INVALID_PRIVACY_MODE_CONN_ID.to_owned();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pre_conn_id(&self) -> i32 {
|
||||
self.conn_id
|
||||
}
|
||||
}
|
||||
|
||||
impl PrivacyModeImpl {
|
||||
pub fn start(&mut self) -> ResultType<()> {
|
||||
if self.handlers.hprocess != 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
log::info!("Start privacy mode window broker, check_update_broker_process");
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
let exe_file = std::env::current_exe()?;
|
||||
let Some(cur_dir) = exe_file.parent() else {
|
||||
bail!("Cannot get parent of current exe file");
|
||||
};
|
||||
|
||||
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()
|
||||
);
|
||||
}
|
||||
|
||||
let hwnd = wait_find_privacy_hwnd(1_000)?;
|
||||
if !hwnd.is_null() {
|
||||
log::info!("Privacy window is ready");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// let cmdline = cur_dir.join("MiniBroker.exe").to_string_lossy().to_string();
|
||||
let cmdline = cur_dir
|
||||
.join(INJECTED_PROCESS_EXE)
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
unsafe {
|
||||
let cmd_utf16: Vec<u16> = cmdline.encode_utf16().chain(Some(0).into_iter()).collect();
|
||||
|
||||
let mut start_info = STARTUPINFOW {
|
||||
cb: 0,
|
||||
lpReserved: NULL as _,
|
||||
lpDesktop: NULL as _,
|
||||
lpTitle: NULL as _,
|
||||
dwX: 0,
|
||||
dwY: 0,
|
||||
dwXSize: 0,
|
||||
dwYSize: 0,
|
||||
dwXCountChars: 0,
|
||||
dwYCountChars: 0,
|
||||
dwFillAttribute: 0,
|
||||
dwFlags: 0,
|
||||
wShowWindow: 0,
|
||||
cbReserved2: 0,
|
||||
lpReserved2: NULL as _,
|
||||
hStdInput: NULL as _,
|
||||
hStdOutput: NULL as _,
|
||||
hStdError: NULL as _,
|
||||
};
|
||||
let mut proc_info = PROCESS_INFORMATION {
|
||||
hProcess: NULL as _,
|
||||
hThread: NULL as _,
|
||||
dwProcessId: 0,
|
||||
dwThreadId: 0,
|
||||
};
|
||||
|
||||
let session_id = WTSGetActiveConsoleSessionId();
|
||||
let token = get_user_token(session_id, true);
|
||||
if token.is_null() {
|
||||
bail!("Failed to get token of current user");
|
||||
}
|
||||
|
||||
let create_res = CreateProcessAsUserW(
|
||||
token,
|
||||
NULL as _,
|
||||
cmd_utf16.as_ptr() as _,
|
||||
NULL as _,
|
||||
NULL as _,
|
||||
FALSE,
|
||||
CREATE_SUSPENDED | DETACHED_PROCESS,
|
||||
NULL,
|
||||
NULL as _,
|
||||
&mut start_info,
|
||||
&mut proc_info,
|
||||
);
|
||||
CloseHandle(token);
|
||||
if 0 == create_res {
|
||||
bail!(
|
||||
"Failed to create privacy window process {}, code {}",
|
||||
cmdline,
|
||||
GetLastError()
|
||||
);
|
||||
};
|
||||
|
||||
inject_dll(
|
||||
proc_info.hProcess,
|
||||
proc_info.hThread,
|
||||
dll_file.to_string_lossy().as_ref(),
|
||||
)?;
|
||||
|
||||
if 0xffffffff == ResumeThread(proc_info.hThread) {
|
||||
// CloseHandle
|
||||
CloseHandle(proc_info.hThread);
|
||||
CloseHandle(proc_info.hProcess);
|
||||
|
||||
bail!(
|
||||
"Failed to create privacy window process, {}",
|
||||
GetLastError()
|
||||
);
|
||||
}
|
||||
|
||||
self.handlers.hthread = proc_info.hThread as _;
|
||||
self.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(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stop(&mut self) {
|
||||
self.handlers.reset();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PrivacyModeImpl {
|
||||
fn drop(&mut self) {
|
||||
if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID {
|
||||
allow_err!(self.turn_off_privacy(self.conn_id, None));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn inject_dll<'a>(hproc: HANDLE, hthread: HANDLE, dll_file: &'a str) -> ResultType<()> {
|
||||
let dll_file_utf16: Vec<u16> = dll_file.encode_utf16().chain(Some(0).into_iter()).collect();
|
||||
|
||||
let buf = VirtualAllocEx(
|
||||
hproc,
|
||||
NULL as _,
|
||||
dll_file_utf16.len() * 2,
|
||||
MEM_COMMIT,
|
||||
PAGE_READWRITE,
|
||||
);
|
||||
if buf.is_null() {
|
||||
bail!("Failed VirtualAllocEx");
|
||||
}
|
||||
|
||||
let mut written: usize = 0;
|
||||
if 0 == WriteProcessMemory(
|
||||
hproc,
|
||||
buf,
|
||||
dll_file_utf16.as_ptr() as _,
|
||||
dll_file_utf16.len() * 2,
|
||||
&mut written,
|
||||
) {
|
||||
bail!("Failed WriteProcessMemory");
|
||||
}
|
||||
|
||||
let kernel32_modulename = CString::new("kernel32")?;
|
||||
let hmodule = GetModuleHandleA(kernel32_modulename.as_ptr() as _);
|
||||
if hmodule.is_null() {
|
||||
bail!("Failed GetModuleHandleA");
|
||||
}
|
||||
|
||||
let load_librarya_name = CString::new("LoadLibraryW")?;
|
||||
let load_librarya = GetProcAddress(hmodule, load_librarya_name.as_ptr() as _);
|
||||
if load_librarya.is_null() {
|
||||
bail!("Failed GetProcAddress of LoadLibraryW");
|
||||
}
|
||||
|
||||
if 0 == QueueUserAPC(Some(std::mem::transmute(load_librarya)), hthread, buf as _) {
|
||||
bail!("Failed QueueUserAPC");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_find_privacy_hwnd(msecs: u128) -> ResultType<HWND> {
|
||||
let tm_begin = Instant::now();
|
||||
let wndname = CString::new(PRIVACY_WINDOW_NAME)?;
|
||||
loop {
|
||||
unsafe {
|
||||
let hwnd = FindWindowA(NULL as _, wndname.as_ptr() as _);
|
||||
if !hwnd.is_null() {
|
||||
return Ok(hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
if msecs == 0 || tm_begin.elapsed().as_millis() > msecs {
|
||||
return Ok(NULL as _);
|
||||
}
|
||||
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_capturer(
|
||||
privacy_mode_id: i32,
|
||||
origin: (i32, i32),
|
||||
width: usize,
|
||||
height: usize,
|
||||
) -> ResultType<Option<scrap::CapturerMag>> {
|
||||
if !super::is_current_privacy_mode_impl(PRIVACY_MODE_IMPL) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
match scrap::CapturerMag::new(origin, width, height) {
|
||||
Ok(mut c1) => {
|
||||
let mut ok = false;
|
||||
let check_begin = Instant::now();
|
||||
while check_begin.elapsed().as_secs() < 5 {
|
||||
match c1.exclude("", PRIVACY_WINDOW_NAME) {
|
||||
Ok(false) => {
|
||||
ok = false;
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
}
|
||||
Err(e) => {
|
||||
bail!(
|
||||
"Failed to exclude privacy window {} - {}, err: {}",
|
||||
"",
|
||||
PRIVACY_WINDOW_NAME,
|
||||
e
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
bail!(
|
||||
"Failed to exclude privacy window {} - {} ",
|
||||
"",
|
||||
PRIVACY_WINDOW_NAME
|
||||
);
|
||||
}
|
||||
log::debug!("Create magnifier capture for {}", privacy_mode_id);
|
||||
Ok(Some(c1))
|
||||
}
|
||||
Err(e) => {
|
||||
bail!(format!("Failed to create magnifier capture {}", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
566
src/privacy_mode/win_virtual_display.rs
Normal file
566
src/privacy_mode/win_virtual_display.rs
Normal file
@@ -0,0 +1,566 @@
|
||||
use super::{PrivacyMode, PrivacyModeState, INVALID_PRIVACY_MODE_CONN_ID, NO_DISPLAYS};
|
||||
use crate::virtual_display_manager;
|
||||
use hbb_common::{allow_err, bail, config::Config, log, ResultType};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use virtual_display::MonitorMode;
|
||||
use winapi::{
|
||||
shared::{
|
||||
minwindef::{DWORD, FALSE},
|
||||
ntdef::{NULL, WCHAR},
|
||||
},
|
||||
um::{
|
||||
errhandlingapi::GetLastError,
|
||||
wingdi::{
|
||||
DEVMODEW, DISPLAY_DEVICEW, DISPLAY_DEVICE_ACTIVE, DISPLAY_DEVICE_ATTACHED_TO_DESKTOP,
|
||||
DISPLAY_DEVICE_MIRRORING_DRIVER, DISPLAY_DEVICE_PRIMARY_DEVICE, DM_POSITION,
|
||||
},
|
||||
winuser::{
|
||||
ChangeDisplaySettingsExW, EnumDisplayDevicesW, EnumDisplaySettingsExW,
|
||||
EnumDisplaySettingsW, CDS_NORESET, CDS_RESET, CDS_SET_PRIMARY, CDS_UPDATEREGISTRY,
|
||||
DISP_CHANGE_SUCCESSFUL, EDD_GET_DEVICE_INTERFACE_NAME, ENUM_CURRENT_SETTINGS,
|
||||
ENUM_REGISTRY_SETTINGS,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub(super) const PRIVACY_MODE_IMPL: &str = "privacy_mode_impl_virtual_display";
|
||||
|
||||
const IDD_DEVICE_STRING: &'static str = "RustDeskIddDriver Device\0";
|
||||
const CONFIG_KEY_REG_RECOVERY: &str = "reg_recovery";
|
||||
|
||||
struct Display {
|
||||
dm: DEVMODEW,
|
||||
name: [WCHAR; 32],
|
||||
primary: bool,
|
||||
}
|
||||
|
||||
pub struct PrivacyModeImpl {
|
||||
conn_id: i32,
|
||||
displays: Vec<Display>,
|
||||
virtual_displays: Vec<Display>,
|
||||
virtual_displays_added: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Default for PrivacyModeImpl {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
conn_id: INVALID_PRIVACY_MODE_CONN_ID,
|
||||
displays: Vec::new(),
|
||||
virtual_displays: Vec::new(),
|
||||
virtual_displays_added: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TurnOnGuard<'a> {
|
||||
privacy_mode: &'a mut PrivacyModeImpl,
|
||||
succeeded: bool,
|
||||
}
|
||||
|
||||
impl<'a> Deref for TurnOnGuard<'a> {
|
||||
type Target = PrivacyModeImpl;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.privacy_mode
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DerefMut for TurnOnGuard<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.privacy_mode
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for TurnOnGuard<'a> {
|
||||
fn drop(&mut self) {
|
||||
if !self.succeeded {
|
||||
self.privacy_mode
|
||||
.turn_off_privacy(INVALID_PRIVACY_MODE_CONN_ID, None)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrivacyModeImpl {
|
||||
// mainly from https://github.com/fufesou/rustdesk/blob/44c3a52ca8502cf53b58b59db130611778d34dbe/libs/scrap/src/dxgi/mod.rs#L365
|
||||
fn set_displays(&mut self) {
|
||||
self.displays.clear();
|
||||
self.virtual_displays.clear();
|
||||
|
||||
let mut i: DWORD = 0;
|
||||
loop {
|
||||
#[allow(invalid_value)]
|
||||
let mut dd: DISPLAY_DEVICEW = unsafe { std::mem::MaybeUninit::uninit().assume_init() };
|
||||
dd.cb = std::mem::size_of::<DISPLAY_DEVICEW>() as _;
|
||||
let ok = unsafe { EnumDisplayDevicesW(std::ptr::null(), i, &mut dd as _, 0) };
|
||||
if ok == FALSE {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
if 0 == (dd.StateFlags & DISPLAY_DEVICE_ACTIVE)
|
||||
|| (dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) > 0
|
||||
{
|
||||
continue;
|
||||
}
|
||||
#[allow(invalid_value)]
|
||||
let mut dm: DEVMODEW = unsafe { std::mem::MaybeUninit::uninit().assume_init() };
|
||||
dm.dmSize = std::mem::size_of::<DEVMODEW>() as _;
|
||||
dm.dmDriverExtra = 0;
|
||||
unsafe {
|
||||
if FALSE
|
||||
== EnumDisplaySettingsExW(
|
||||
dd.DeviceName.as_ptr(),
|
||||
ENUM_CURRENT_SETTINGS,
|
||||
&mut dm as _,
|
||||
0,
|
||||
)
|
||||
{
|
||||
if FALSE
|
||||
== EnumDisplaySettingsExW(
|
||||
dd.DeviceName.as_ptr(),
|
||||
ENUM_REGISTRY_SETTINGS,
|
||||
&mut dm as _,
|
||||
0,
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let primary = (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) > 0;
|
||||
let display = Display {
|
||||
dm,
|
||||
name: dd.DeviceName,
|
||||
primary,
|
||||
};
|
||||
|
||||
if let Ok(s) = std::string::String::from_utf16(&dd.DeviceString) {
|
||||
if &s[..IDD_DEVICE_STRING.len()] == IDD_DEVICE_STRING {
|
||||
self.virtual_displays.push(display);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
self.displays.push(display);
|
||||
}
|
||||
}
|
||||
|
||||
fn restore(&mut self) {
|
||||
Self::restore_displays(&self.displays);
|
||||
Self::restore_displays(&self.virtual_displays);
|
||||
allow_err!(Self::commit_change_display(0));
|
||||
self.restore_plug_out_monitor();
|
||||
}
|
||||
|
||||
fn restore_plug_out_monitor(&mut self) {
|
||||
let _ = virtual_display_manager::plug_out_peer_request(&self.virtual_displays_added);
|
||||
self.virtual_displays_added.clear();
|
||||
}
|
||||
|
||||
fn restore_displays(displays: &[Display]) {
|
||||
for display in displays {
|
||||
unsafe {
|
||||
let mut dm = display.dm.clone();
|
||||
let flags = if display.primary {
|
||||
CDS_NORESET | CDS_UPDATEREGISTRY | CDS_SET_PRIMARY
|
||||
} else {
|
||||
CDS_NORESET | CDS_UPDATEREGISTRY
|
||||
};
|
||||
ChangeDisplaySettingsExW(
|
||||
display.name.as_ptr(),
|
||||
&mut dm,
|
||||
std::ptr::null_mut(),
|
||||
flags,
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_primary_display(&mut self) -> ResultType<()> {
|
||||
let display = &self.virtual_displays[0];
|
||||
|
||||
#[allow(invalid_value)]
|
||||
let mut new_primary_dm: DEVMODEW = unsafe { std::mem::MaybeUninit::uninit().assume_init() };
|
||||
new_primary_dm.dmSize = std::mem::size_of::<DEVMODEW>() as _;
|
||||
new_primary_dm.dmDriverExtra = 0;
|
||||
unsafe {
|
||||
if FALSE
|
||||
== EnumDisplaySettingsW(
|
||||
display.name.as_ptr(),
|
||||
ENUM_CURRENT_SETTINGS,
|
||||
&mut new_primary_dm,
|
||||
)
|
||||
{
|
||||
bail!(
|
||||
"Failed EnumDisplaySettingsW, device name: {:?}, error code: {}",
|
||||
std::string::String::from_utf16(&display.name),
|
||||
GetLastError()
|
||||
);
|
||||
}
|
||||
|
||||
let mut i: DWORD = 0;
|
||||
loop {
|
||||
let mut flags = CDS_UPDATEREGISTRY | CDS_NORESET;
|
||||
#[allow(invalid_value)]
|
||||
let mut dd: DISPLAY_DEVICEW = std::mem::MaybeUninit::uninit().assume_init();
|
||||
dd.cb = std::mem::size_of::<DISPLAY_DEVICEW>() as _;
|
||||
if FALSE
|
||||
== EnumDisplayDevicesW(NULL as _, i, &mut dd, EDD_GET_DEVICE_INTERFACE_NAME)
|
||||
{
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
if (dd.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if dd.DeviceName == display.name {
|
||||
flags |= CDS_SET_PRIMARY;
|
||||
}
|
||||
|
||||
#[allow(invalid_value)]
|
||||
let mut dm: DEVMODEW = std::mem::MaybeUninit::uninit().assume_init();
|
||||
dm.dmSize = std::mem::size_of::<DEVMODEW>() as _;
|
||||
dm.dmDriverExtra = 0;
|
||||
if FALSE
|
||||
== EnumDisplaySettingsW(dd.DeviceName.as_ptr(), ENUM_CURRENT_SETTINGS, &mut dm)
|
||||
{
|
||||
bail!(
|
||||
"Failed EnumDisplaySettingsW, device name: {:?}, error code: {}",
|
||||
std::string::String::from_utf16(&dd.DeviceName),
|
||||
GetLastError()
|
||||
);
|
||||
}
|
||||
|
||||
dm.u1.s2_mut().dmPosition.x -= new_primary_dm.u1.s2().dmPosition.x;
|
||||
dm.u1.s2_mut().dmPosition.y -= new_primary_dm.u1.s2().dmPosition.y;
|
||||
dm.dmFields |= DM_POSITION;
|
||||
let rc = ChangeDisplaySettingsExW(
|
||||
dd.DeviceName.as_ptr(),
|
||||
&mut dm,
|
||||
NULL as _,
|
||||
flags,
|
||||
NULL,
|
||||
);
|
||||
|
||||
if rc != DISP_CHANGE_SUCCESSFUL {
|
||||
log::error!(
|
||||
"Failed ChangeDisplaySettingsEx, device name: {:?}, flags: {}, ret: {}",
|
||||
std::string::String::from_utf16(&dd.DeviceName),
|
||||
flags,
|
||||
rc
|
||||
);
|
||||
bail!("Failed ChangeDisplaySettingsEx, ret: {}", rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn disable_physical_displays(&self) -> ResultType<()> {
|
||||
for display in &self.displays {
|
||||
let mut dm = display.dm.clone();
|
||||
unsafe {
|
||||
dm.u1.s2_mut().dmPosition.x = 10000;
|
||||
dm.u1.s2_mut().dmPosition.y = 10000;
|
||||
dm.dmPelsHeight = 0;
|
||||
dm.dmPelsWidth = 0;
|
||||
let flags = CDS_UPDATEREGISTRY | CDS_NORESET;
|
||||
let rc = ChangeDisplaySettingsExW(
|
||||
display.name.as_ptr(),
|
||||
&mut dm,
|
||||
NULL as _,
|
||||
flags,
|
||||
NULL as _,
|
||||
);
|
||||
if rc != DISP_CHANGE_SUCCESSFUL {
|
||||
log::error!(
|
||||
"Failed ChangeDisplaySettingsEx, device name: {:?}, flags: {}, ret: {}",
|
||||
std::string::String::from_utf16(&display.name),
|
||||
flags,
|
||||
rc
|
||||
);
|
||||
bail!("Failed ChangeDisplaySettingsEx, ret: {}", rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn default_display_modes() -> Vec<MonitorMode> {
|
||||
vec![MonitorMode {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
sync: 60,
|
||||
}]
|
||||
}
|
||||
|
||||
pub fn ensure_virtual_display(&mut self) -> ResultType<()> {
|
||||
if self.virtual_displays.is_empty() {
|
||||
let displays =
|
||||
virtual_display_manager::plug_in_peer_request(vec![Self::default_display_modes()])?;
|
||||
self.virtual_displays_added.extend(displays);
|
||||
self.set_displays();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn commit_change_display(flags: DWORD) -> ResultType<()> {
|
||||
unsafe {
|
||||
// use winapi::{
|
||||
// shared::windef::HDESK,
|
||||
// um::{
|
||||
// processthreadsapi::GetCurrentThreadId,
|
||||
// winnt::MAXIMUM_ALLOWED,
|
||||
// winuser::{CloseDesktop, GetThreadDesktop, OpenInputDesktop, SetThreadDesktop},
|
||||
// },
|
||||
// };
|
||||
// let mut desk_input: HDESK = NULL as _;
|
||||
// let desk_current: HDESK = GetThreadDesktop(GetCurrentThreadId());
|
||||
// if !desk_current.is_null() {
|
||||
// desk_input = OpenInputDesktop(0, FALSE, MAXIMUM_ALLOWED);
|
||||
// if desk_input.is_null() {
|
||||
// SetThreadDesktop(desk_input);
|
||||
// }
|
||||
// }
|
||||
|
||||
let ret = ChangeDisplaySettingsExW(NULL as _, NULL as _, NULL as _, flags, NULL as _);
|
||||
if ret != DISP_CHANGE_SUCCESSFUL {
|
||||
bail!("Failed ChangeDisplaySettingsEx, ret: {}", ret);
|
||||
}
|
||||
|
||||
// if !desk_current.is_null() {
|
||||
// SetThreadDesktop(desk_current);
|
||||
// }
|
||||
// if !desk_input.is_null() {
|
||||
// CloseDesktop(desk_input);
|
||||
// }
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PrivacyMode for PrivacyModeImpl {
|
||||
fn init(&self) -> ResultType<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
allow_err!(self.turn_off_privacy(self.conn_id, None));
|
||||
}
|
||||
|
||||
fn turn_on_privacy(&mut self, conn_id: i32) -> ResultType<bool> {
|
||||
if self.check_on_conn_id(conn_id)? {
|
||||
log::debug!("Privacy mode of conn {} is already on", conn_id);
|
||||
return Ok(true);
|
||||
}
|
||||
self.set_displays();
|
||||
if self.displays.is_empty() {
|
||||
log::debug!("No displays");
|
||||
bail!(NO_DISPLAYS);
|
||||
}
|
||||
|
||||
let mut guard = TurnOnGuard {
|
||||
privacy_mode: self,
|
||||
succeeded: false,
|
||||
};
|
||||
|
||||
guard.ensure_virtual_display()?;
|
||||
if guard.virtual_displays.is_empty() {
|
||||
log::debug!("No virtual displays");
|
||||
bail!("No virtual displays");
|
||||
}
|
||||
|
||||
let reg_connectivity_1 = reg_display_settings::read_reg_connectivity()?;
|
||||
guard.set_primary_display()?;
|
||||
guard.disable_physical_displays()?;
|
||||
Self::commit_change_display(CDS_RESET)?;
|
||||
let reg_connectivity_2 = reg_display_settings::read_reg_connectivity()?;
|
||||
|
||||
if let Some(reg_recovery) =
|
||||
reg_display_settings::diff_recent_connectivity(reg_connectivity_1, reg_connectivity_2)
|
||||
{
|
||||
Config::set_option(
|
||||
CONFIG_KEY_REG_RECOVERY.to_owned(),
|
||||
serde_json::to_string(®_recovery)?,
|
||||
);
|
||||
} else {
|
||||
reset_config_reg_connectivity();
|
||||
};
|
||||
|
||||
// OpenInputDesktop and block the others' input ?
|
||||
guard.conn_id = conn_id;
|
||||
guard.succeeded = true;
|
||||
|
||||
allow_err!(super::win_input::hook());
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn turn_off_privacy(
|
||||
&mut self,
|
||||
conn_id: i32,
|
||||
state: Option<PrivacyModeState>,
|
||||
) -> ResultType<()> {
|
||||
self.check_off_conn_id(conn_id)?;
|
||||
super::win_input::unhook()?;
|
||||
self.restore();
|
||||
restore_reg_connectivity();
|
||||
|
||||
if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID {
|
||||
if let Some(state) = state {
|
||||
allow_err!(super::set_privacy_mode_state(
|
||||
conn_id,
|
||||
state,
|
||||
PRIVACY_MODE_IMPL.to_string(),
|
||||
1_000
|
||||
));
|
||||
}
|
||||
self.conn_id = INVALID_PRIVACY_MODE_CONN_ID.to_owned();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pre_conn_id(&self) -> i32 {
|
||||
self.conn_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PrivacyModeImpl {
|
||||
fn drop(&mut self) {
|
||||
if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID {
|
||||
allow_err!(self.turn_off_privacy(self.conn_id, None));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn reset_config_reg_connectivity() {
|
||||
Config::set_option(CONFIG_KEY_REG_RECOVERY.to_owned(), "".to_owned());
|
||||
}
|
||||
|
||||
pub fn restore_reg_connectivity() {
|
||||
let config_recovery_value = Config::get_option(CONFIG_KEY_REG_RECOVERY);
|
||||
if config_recovery_value.is_empty() {
|
||||
return;
|
||||
}
|
||||
if let Ok(reg_recovery) =
|
||||
serde_json::from_str::<reg_display_settings::RegRecovery>(&config_recovery_value)
|
||||
{
|
||||
if let Err(e) = reg_display_settings::restore_reg_connectivity(reg_recovery) {
|
||||
log::error!("Failed restore_reg_connectivity, error: {}", e);
|
||||
}
|
||||
}
|
||||
reset_config_reg_connectivity();
|
||||
}
|
||||
|
||||
mod reg_display_settings {
|
||||
use hbb_common::ResultType;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use winreg::{enums::*, RegValue};
|
||||
const REG_GRAPHICS_DRIVERS_PATH: &str = "SYSTEM\\CurrentControlSet\\Control\\GraphicsDrivers";
|
||||
const REG_CONNECTIVITY_PATH: &str = "Connectivity";
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub(super) struct RegRecovery {
|
||||
path: String,
|
||||
key: String,
|
||||
old: (Vec<u8>, isize),
|
||||
new: (Vec<u8>, isize),
|
||||
}
|
||||
|
||||
pub(super) fn read_reg_connectivity() -> ResultType<HashMap<String, HashMap<String, RegValue>>>
|
||||
{
|
||||
let hklm = winreg::RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||
let reg_connectivity = hklm.open_subkey_with_flags(
|
||||
format!("{}\\{}", REG_GRAPHICS_DRIVERS_PATH, REG_CONNECTIVITY_PATH),
|
||||
KEY_READ,
|
||||
)?;
|
||||
|
||||
let mut map_connectivity = HashMap::new();
|
||||
for key in reg_connectivity.enum_keys() {
|
||||
let key = key?;
|
||||
let mut map_item = HashMap::new();
|
||||
let reg_item = reg_connectivity.open_subkey_with_flags(&key, KEY_READ)?;
|
||||
for value in reg_item.enum_values() {
|
||||
let (name, value) = value?;
|
||||
map_item.insert(name, value);
|
||||
}
|
||||
map_connectivity.insert(key, map_item);
|
||||
}
|
||||
Ok(map_connectivity)
|
||||
}
|
||||
|
||||
pub(super) fn diff_recent_connectivity(
|
||||
map1: HashMap<String, HashMap<String, RegValue>>,
|
||||
map2: HashMap<String, HashMap<String, RegValue>>,
|
||||
) -> Option<RegRecovery> {
|
||||
for (subkey, map_item2) in map2 {
|
||||
if let Some(map_item1) = map1.get(&subkey) {
|
||||
let key = "Recent";
|
||||
if let Some(value1) = map_item1.get(key) {
|
||||
if let Some(value2) = map_item2.get(key) {
|
||||
if value1 != value2 {
|
||||
return Some(RegRecovery {
|
||||
path: format!(
|
||||
"{}\\{}\\{}",
|
||||
REG_GRAPHICS_DRIVERS_PATH, REG_CONNECTIVITY_PATH, subkey
|
||||
),
|
||||
key: key.to_owned(),
|
||||
old: (value1.bytes.clone(), value1.vtype.clone() as isize),
|
||||
new: (value2.bytes.clone(), value2.vtype.clone() as isize),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn restore_reg_connectivity(reg_recovery: RegRecovery) -> ResultType<()> {
|
||||
let hklm = winreg::RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||
let reg_item = hklm.open_subkey_with_flags(®_recovery.path, KEY_READ | KEY_WRITE)?;
|
||||
let cur_reg_value = reg_item.get_raw_value(®_recovery.key)?;
|
||||
let new_reg_value = RegValue {
|
||||
bytes: reg_recovery.new.0,
|
||||
vtype: isize_to_reg_type(reg_recovery.new.1),
|
||||
};
|
||||
if cur_reg_value != new_reg_value {
|
||||
return Ok(());
|
||||
}
|
||||
let reg_value = RegValue {
|
||||
bytes: reg_recovery.old.0,
|
||||
vtype: isize_to_reg_type(reg_recovery.old.1),
|
||||
};
|
||||
reg_item.set_raw_value(®_recovery.key, ®_value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn isize_to_reg_type(i: isize) -> RegType {
|
||||
match i {
|
||||
0 => RegType::REG_NONE,
|
||||
1 => RegType::REG_SZ,
|
||||
2 => RegType::REG_EXPAND_SZ,
|
||||
3 => RegType::REG_BINARY,
|
||||
4 => RegType::REG_DWORD,
|
||||
5 => RegType::REG_DWORD_BIG_ENDIAN,
|
||||
6 => RegType::REG_LINK,
|
||||
7 => RegType::REG_MULTI_SZ,
|
||||
8 => RegType::REG_RESOURCE_LIST,
|
||||
9 => RegType::REG_FULL_RESOURCE_DESCRIPTOR,
|
||||
10 => RegType::REG_RESOURCE_REQUIREMENTS_LIST,
|
||||
11 => RegType::REG_QWORD,
|
||||
_ => RegType::REG_NONE,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user