mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
privacy_mode: win10 magnifier
Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user