2022-05-12 17:35:25 +08:00
|
|
|
use super::{CursorData, ResultType};
|
2022-10-31 14:59:57 +08:00
|
|
|
use crate::common::PORTABLE_APPNAME_RUNTIME_ENV_KEY;
|
2023-04-17 09:48:39 +08:00
|
|
|
use crate::{
|
2024-02-26 19:01:42 +08:00
|
|
|
custom_server::*,
|
2024-02-27 15:42:35 +08:00
|
|
|
ipc,
|
2023-11-19 15:26:02 +08:00
|
|
|
privacy_mode::win_topmost_window::{self, WIN_TOPMOST_INJECTED_PROCESS_EXE},
|
2023-04-17 09:48:39 +08:00
|
|
|
};
|
2024-02-14 21:29:17 +05:30
|
|
|
use hbb_common::libc::{c_int, wchar_t};
|
2022-05-12 17:35:25 +08:00
|
|
|
use hbb_common::{
|
2023-07-22 14:16:41 +08:00
|
|
|
allow_err,
|
|
|
|
anyhow::anyhow,
|
|
|
|
bail,
|
2022-05-12 17:35:25 +08:00
|
|
|
config::{self, Config},
|
2023-02-09 15:53:51 +08:00
|
|
|
log,
|
2024-04-25 10:56:02 +05:30
|
|
|
message_proto::{DisplayInfo, Resolution, WindowsSession},
|
2023-02-09 15:53:51 +08:00
|
|
|
sleep, timeout, tokio,
|
2022-05-12 17:35:25 +08:00
|
|
|
};
|
2023-10-11 19:03:34 +08:00
|
|
|
use std::process::{Command, Stdio};
|
2022-05-12 17:35:25 +08:00
|
|
|
use std::{
|
2023-03-30 10:08:05 +08:00
|
|
|
collections::HashMap,
|
2022-10-31 16:08:51 +08:00
|
|
|
ffi::OsString,
|
2023-04-17 09:48:39 +08:00
|
|
|
fs, io,
|
2023-04-03 00:16:09 +08:00
|
|
|
io::prelude::*,
|
|
|
|
mem,
|
2023-02-07 15:16:49 +08:00
|
|
|
os::windows::process::CommandExt,
|
2023-04-03 00:16:09 +08:00
|
|
|
path::*,
|
|
|
|
ptr::null_mut,
|
2023-06-07 14:25:34 +08:00
|
|
|
sync::{atomic::Ordering, Arc, Mutex},
|
2022-05-12 17:35:25 +08:00
|
|
|
time::{Duration, Instant},
|
|
|
|
};
|
2023-10-11 19:03:34 +08:00
|
|
|
use wallpaper;
|
2024-04-23 17:00:41 +08:00
|
|
|
use winapi::um::sysinfoapi::{GetNativeSystemInfo, SYSTEM_INFO};
|
2022-05-12 17:35:25 +08:00
|
|
|
use winapi::{
|
2022-09-28 20:48:14 +08:00
|
|
|
ctypes::c_void,
|
2023-03-17 13:01:37 +01:00
|
|
|
shared::{minwindef::*, ntdef::NULL, windef::*, winerror::*},
|
2022-05-12 17:35:25 +08:00
|
|
|
um::{
|
2022-09-28 20:48:14 +08:00
|
|
|
errhandlingapi::GetLastError,
|
|
|
|
handleapi::CloseHandle,
|
|
|
|
minwinbase::STILL_ACTIVE,
|
2022-10-30 18:45:44 +08:00
|
|
|
processthreadsapi::{
|
|
|
|
GetCurrentProcess, GetCurrentProcessId, GetExitCodeProcess, OpenProcess,
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
OpenProcessToken, ProcessIdToSessionId, PROCESS_INFORMATION, STARTUPINFOW,
|
2022-10-30 18:45:44 +08:00
|
|
|
},
|
2022-09-28 20:48:14 +08:00
|
|
|
securitybaseapi::GetTokenInformation,
|
2022-10-31 16:08:51 +08:00
|
|
|
shellapi::ShellExecuteW,
|
2022-09-28 20:48:14 +08:00
|
|
|
winbase::*,
|
|
|
|
wingdi::*,
|
|
|
|
winnt::{
|
2023-06-05 20:24:16 +08:00
|
|
|
TokenElevation, ES_AWAYMODE_REQUIRED, ES_CONTINUOUS, ES_DISPLAY_REQUIRED,
|
|
|
|
ES_SYSTEM_REQUIRED, HANDLE, PROCESS_QUERY_LIMITED_INFORMATION, TOKEN_ELEVATION,
|
|
|
|
TOKEN_QUERY,
|
2022-09-28 20:48:14 +08:00
|
|
|
},
|
2023-06-05 15:21:42 +08:00
|
|
|
winreg::HKEY_CURRENT_USER,
|
2022-09-28 20:48:14 +08:00
|
|
|
winuser::*,
|
2022-05-12 17:35:25 +08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
use windows_service::{
|
|
|
|
define_windows_service,
|
|
|
|
service::{
|
|
|
|
ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus,
|
|
|
|
ServiceType,
|
|
|
|
},
|
|
|
|
service_control_handler::{self, ServiceControlHandlerResult},
|
|
|
|
};
|
|
|
|
use winreg::enums::*;
|
|
|
|
use winreg::RegKey;
|
|
|
|
|
2024-04-25 10:56:02 +05:30
|
|
|
pub fn get_focused_display(displays: Vec<DisplayInfo>) -> Option<usize> {
|
|
|
|
unsafe {
|
|
|
|
let hWnd = GetForegroundWindow();
|
|
|
|
let mut rect: RECT = mem::zeroed();
|
|
|
|
if GetWindowRect(hWnd, &mut rect as *mut RECT) == 0 {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
displays.iter().position(|display| {
|
|
|
|
let center_x = rect.left + (rect.right - rect.left) / 2;
|
|
|
|
let center_y = rect.top + (rect.bottom - rect.top) / 2;
|
|
|
|
center_x >= display.x
|
|
|
|
&& center_x <= display.x + display.width
|
|
|
|
&& center_y >= display.y
|
|
|
|
&& center_y <= display.y + display.height
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-12 17:35:25 +08:00
|
|
|
pub fn get_cursor_pos() -> Option<(i32, i32)> {
|
|
|
|
unsafe {
|
2023-01-29 14:27:57 +08:00
|
|
|
#[allow(invalid_value)]
|
2022-05-12 17:35:25 +08:00
|
|
|
let mut out = mem::MaybeUninit::uninit().assume_init();
|
|
|
|
if GetCursorPos(&mut out) == FALSE {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
return Some((out.x, out.y));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn reset_input_cache() {}
|
|
|
|
|
|
|
|
pub fn get_cursor() -> ResultType<Option<u64>> {
|
|
|
|
unsafe {
|
2023-01-29 14:27:57 +08:00
|
|
|
#[allow(invalid_value)]
|
2022-05-12 17:35:25 +08:00
|
|
|
let mut ci: CURSORINFO = mem::MaybeUninit::uninit().assume_init();
|
|
|
|
ci.cbSize = std::mem::size_of::<CURSORINFO>() as _;
|
2022-11-10 10:27:13 +08:00
|
|
|
if crate::portable_service::client::get_cursor_info(&mut ci) == FALSE {
|
2022-05-12 17:35:25 +08:00
|
|
|
return Err(io::Error::last_os_error().into());
|
|
|
|
}
|
|
|
|
if ci.flags & CURSOR_SHOWING == 0 {
|
|
|
|
Ok(None)
|
|
|
|
} else {
|
|
|
|
Ok(Some(ci.hCursor as _))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct IconInfo(ICONINFO);
|
|
|
|
|
|
|
|
impl IconInfo {
|
|
|
|
fn new(icon: HICON) -> ResultType<Self> {
|
|
|
|
unsafe {
|
2023-01-29 14:27:57 +08:00
|
|
|
#[allow(invalid_value)]
|
2022-05-12 17:35:25 +08:00
|
|
|
let mut ii = mem::MaybeUninit::uninit().assume_init();
|
|
|
|
if GetIconInfo(icon, &mut ii) == FALSE {
|
|
|
|
Err(io::Error::last_os_error().into())
|
|
|
|
} else {
|
|
|
|
let ii = Self(ii);
|
|
|
|
if ii.0.hbmMask.is_null() {
|
|
|
|
bail!("Cursor bitmap handle is NULL");
|
|
|
|
}
|
|
|
|
return Ok(ii);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_color(&self) -> bool {
|
|
|
|
!self.0.hbmColor.is_null()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for IconInfo {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe {
|
|
|
|
if !self.0.hbmColor.is_null() {
|
|
|
|
DeleteObject(self.0.hbmColor as _);
|
|
|
|
}
|
|
|
|
if !self.0.hbmMask.is_null() {
|
|
|
|
DeleteObject(self.0.hbmMask as _);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://github.com/TurboVNC/tightvnc/blob/a235bae328c12fd1c3aed6f3f034a37a6ffbbd22/vnc_winsrc/winvnc/vncEncoder.cpp
|
|
|
|
// https://github.com/TigerVNC/tigervnc/blob/master/win/rfb_win32/DeviceFrameBuffer.cxx
|
|
|
|
pub fn get_cursor_data(hcursor: u64) -> ResultType<CursorData> {
|
|
|
|
unsafe {
|
|
|
|
let mut ii = IconInfo::new(hcursor as _)?;
|
|
|
|
let bm_mask = get_bitmap(ii.0.hbmMask)?;
|
|
|
|
let mut width = bm_mask.bmWidth;
|
|
|
|
let mut height = if ii.is_color() {
|
|
|
|
bm_mask.bmHeight
|
|
|
|
} else {
|
|
|
|
bm_mask.bmHeight / 2
|
|
|
|
};
|
|
|
|
let cbits_size = width * height * 4;
|
|
|
|
if cbits_size < 16 {
|
|
|
|
bail!("Invalid icon: too small"); // solve some crash
|
|
|
|
}
|
|
|
|
let mut cbits: Vec<u8> = Vec::new();
|
|
|
|
cbits.resize(cbits_size as _, 0);
|
|
|
|
let mut mbits: Vec<u8> = Vec::new();
|
|
|
|
mbits.resize((bm_mask.bmWidthBytes * bm_mask.bmHeight) as _, 0);
|
|
|
|
let r = GetBitmapBits(ii.0.hbmMask, mbits.len() as _, mbits.as_mut_ptr() as _);
|
|
|
|
if r == 0 {
|
|
|
|
bail!("Failed to copy bitmap data");
|
|
|
|
}
|
|
|
|
if r != (mbits.len() as i32) {
|
|
|
|
bail!(
|
|
|
|
"Invalid mask cursor buffer size, got {} bytes, expected {}",
|
|
|
|
r,
|
|
|
|
mbits.len()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
let do_outline;
|
|
|
|
if ii.is_color() {
|
|
|
|
get_rich_cursor_data(ii.0.hbmColor, width, height, &mut cbits)?;
|
|
|
|
do_outline = fix_cursor_mask(
|
|
|
|
&mut mbits,
|
|
|
|
&mut cbits,
|
|
|
|
width as _,
|
|
|
|
height as _,
|
|
|
|
bm_mask.bmWidthBytes as _,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
do_outline = handleMask(
|
|
|
|
cbits.as_mut_ptr(),
|
|
|
|
mbits.as_ptr(),
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
bm_mask.bmWidthBytes,
|
|
|
|
bm_mask.bmHeight,
|
|
|
|
) > 0;
|
|
|
|
}
|
|
|
|
if do_outline {
|
|
|
|
let mut outline = Vec::new();
|
|
|
|
outline.resize(((width + 2) * (height + 2) * 4) as _, 0);
|
|
|
|
drawOutline(
|
|
|
|
outline.as_mut_ptr(),
|
|
|
|
cbits.as_ptr(),
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
outline.len() as _,
|
|
|
|
);
|
|
|
|
cbits = outline;
|
|
|
|
width += 2;
|
|
|
|
height += 2;
|
|
|
|
ii.0.xHotspot += 1;
|
|
|
|
ii.0.yHotspot += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(CursorData {
|
|
|
|
id: hcursor,
|
2022-07-16 00:45:23 +08:00
|
|
|
colors: cbits.into(),
|
2022-05-12 17:35:25 +08:00
|
|
|
hotx: ii.0.xHotspot as _,
|
|
|
|
hoty: ii.0.yHotspot as _,
|
|
|
|
width: width as _,
|
|
|
|
height: height as _,
|
|
|
|
..Default::default()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn get_bitmap(handle: HBITMAP) -> ResultType<BITMAP> {
|
|
|
|
unsafe {
|
|
|
|
let mut bm: BITMAP = mem::zeroed();
|
|
|
|
if GetObjectA(
|
|
|
|
handle as _,
|
|
|
|
std::mem::size_of::<BITMAP>() as _,
|
|
|
|
&mut bm as *mut BITMAP as *mut _,
|
|
|
|
) == FALSE
|
|
|
|
{
|
|
|
|
return Err(io::Error::last_os_error().into());
|
|
|
|
}
|
|
|
|
if bm.bmPlanes != 1 {
|
|
|
|
bail!("Unsupported multi-plane cursor");
|
|
|
|
}
|
|
|
|
if bm.bmBitsPixel != 1 {
|
|
|
|
bail!("Unsupported cursor mask format");
|
|
|
|
}
|
|
|
|
Ok(bm)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct DC(HDC);
|
|
|
|
|
|
|
|
impl DC {
|
|
|
|
fn new() -> ResultType<Self> {
|
|
|
|
unsafe {
|
|
|
|
let dc = GetDC(0 as _);
|
|
|
|
if dc.is_null() {
|
|
|
|
bail!("Failed to get a drawing context");
|
|
|
|
}
|
|
|
|
Ok(Self(dc))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for DC {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe {
|
|
|
|
if !self.0.is_null() {
|
|
|
|
ReleaseDC(0 as _, self.0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct CompatibleDC(HDC);
|
|
|
|
|
|
|
|
impl CompatibleDC {
|
|
|
|
fn new(existing: HDC) -> ResultType<Self> {
|
|
|
|
unsafe {
|
|
|
|
let dc = CreateCompatibleDC(existing);
|
|
|
|
if dc.is_null() {
|
|
|
|
bail!("Failed to get a compatible drawing context");
|
|
|
|
}
|
|
|
|
Ok(Self(dc))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for CompatibleDC {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe {
|
|
|
|
if !self.0.is_null() {
|
|
|
|
DeleteDC(self.0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct BitmapDC(CompatibleDC, HBITMAP);
|
|
|
|
|
|
|
|
impl BitmapDC {
|
|
|
|
fn new(hdc: HDC, hbitmap: HBITMAP) -> ResultType<Self> {
|
|
|
|
unsafe {
|
|
|
|
let dc = CompatibleDC::new(hdc)?;
|
|
|
|
let oldbitmap = SelectObject(dc.0, hbitmap as _) as HBITMAP;
|
|
|
|
if oldbitmap.is_null() {
|
|
|
|
bail!("Failed to select CompatibleDC");
|
|
|
|
}
|
|
|
|
Ok(Self(dc, oldbitmap))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn dc(&self) -> HDC {
|
|
|
|
(self.0).0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for BitmapDC {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe {
|
|
|
|
if !self.1.is_null() {
|
|
|
|
SelectObject((self.0).0, self.1 as _);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn get_rich_cursor_data(
|
|
|
|
hbm_color: HBITMAP,
|
|
|
|
width: i32,
|
|
|
|
height: i32,
|
|
|
|
out: &mut Vec<u8>,
|
|
|
|
) -> ResultType<()> {
|
|
|
|
unsafe {
|
|
|
|
let dc = DC::new()?;
|
|
|
|
let bitmap_dc = BitmapDC::new(dc.0, hbm_color)?;
|
|
|
|
if get_di_bits(out.as_mut_ptr(), bitmap_dc.dc(), hbm_color, width, height) > 0 {
|
2023-11-20 17:38:53 +08:00
|
|
|
bail!("Failed to get di bits: {}", io::Error::last_os_error());
|
2022-05-12 17:35:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn fix_cursor_mask(
|
|
|
|
mbits: &mut Vec<u8>,
|
|
|
|
cbits: &mut Vec<u8>,
|
|
|
|
width: usize,
|
|
|
|
height: usize,
|
|
|
|
bm_width_bytes: usize,
|
|
|
|
) -> bool {
|
|
|
|
let mut pix_idx = 0;
|
|
|
|
for _ in 0..height {
|
|
|
|
for _ in 0..width {
|
|
|
|
if cbits[pix_idx + 3] != 0 {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
pix_idx += 4;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let packed_width_bytes = (width + 7) >> 3;
|
|
|
|
let bm_size = mbits.len();
|
|
|
|
let c_size = cbits.len();
|
|
|
|
|
|
|
|
// Pack and invert bitmap data (mbits)
|
|
|
|
// borrow from tigervnc
|
|
|
|
for y in 0..height {
|
|
|
|
for x in 0..packed_width_bytes {
|
|
|
|
let a = y * packed_width_bytes + x;
|
|
|
|
let b = y * bm_width_bytes + x;
|
|
|
|
if a < bm_size && b < bm_size {
|
|
|
|
mbits[a] = !mbits[b];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Replace "inverted background" bits with black color to ensure
|
|
|
|
// cross-platform interoperability. Not beautiful but necessary code.
|
|
|
|
// borrow from tigervnc
|
|
|
|
let bytes_row = width << 2;
|
|
|
|
for y in 0..height {
|
|
|
|
let mut bitmask: u8 = 0x80;
|
|
|
|
for x in 0..width {
|
|
|
|
let mask_idx = y * packed_width_bytes + (x >> 3);
|
|
|
|
if mask_idx < bm_size {
|
|
|
|
let pix_idx = y * bytes_row + (x << 2);
|
|
|
|
if (mbits[mask_idx] & bitmask) == 0 {
|
|
|
|
for b1 in 0..4 {
|
|
|
|
let a = pix_idx + b1;
|
|
|
|
if a < c_size {
|
|
|
|
if cbits[a] != 0 {
|
|
|
|
mbits[mask_idx] ^= bitmask;
|
|
|
|
for b2 in b1..4 {
|
|
|
|
let b = pix_idx + b2;
|
|
|
|
if b < c_size {
|
|
|
|
cbits[b] = 0x00;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bitmask >>= 1;
|
|
|
|
if bitmask == 0 {
|
|
|
|
bitmask = 0x80;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// borrow from noVNC
|
|
|
|
let mut pix_idx = 0;
|
|
|
|
for y in 0..height {
|
|
|
|
for x in 0..width {
|
|
|
|
let mask_idx = y * packed_width_bytes + (x >> 3);
|
|
|
|
let mut alpha = 255;
|
|
|
|
if mask_idx < bm_size {
|
|
|
|
if (mbits[mask_idx] << (x & 0x7)) & 0x80 == 0 {
|
|
|
|
alpha = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let a = cbits[pix_idx + 2];
|
|
|
|
let b = cbits[pix_idx + 1];
|
|
|
|
let c = cbits[pix_idx];
|
|
|
|
cbits[pix_idx] = a;
|
|
|
|
cbits[pix_idx + 1] = b;
|
|
|
|
cbits[pix_idx + 2] = c;
|
|
|
|
cbits[pix_idx + 3] = alpha;
|
|
|
|
pix_idx += 4;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
define_windows_service!(ffi_service_main, service_main);
|
|
|
|
|
|
|
|
fn service_main(arguments: Vec<OsString>) {
|
|
|
|
if let Err(e) = run_service(arguments) {
|
|
|
|
log::error!("run_service failed: {}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn start_os_service() {
|
|
|
|
if let Err(e) =
|
|
|
|
windows_service::service_dispatcher::start(crate::get_app_name(), ffi_service_main)
|
|
|
|
{
|
|
|
|
log::error!("start_service failed: {}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS;
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
fn get_current_session(rdp: BOOL) -> DWORD;
|
|
|
|
fn LaunchProcessWin(cmd: *const u16, session_id: DWORD, as_user: BOOL) -> HANDLE;
|
2022-04-25 12:28:28 +08:00
|
|
|
fn GetSessionUserTokenWin(lphUserToken: LPHANDLE, dwSessionId: DWORD, as_user: BOOL) -> BOOL;
|
2022-05-12 17:35:25 +08:00
|
|
|
fn selectInputDesktop() -> BOOL;
|
|
|
|
fn inputDesktopSelected() -> BOOL;
|
|
|
|
fn is_windows_server() -> BOOL;
|
|
|
|
fn handleMask(
|
|
|
|
out: *mut u8,
|
|
|
|
mask: *const u8,
|
|
|
|
width: i32,
|
|
|
|
height: i32,
|
|
|
|
bmWidthBytes: i32,
|
|
|
|
bmHeight: i32,
|
|
|
|
) -> i32;
|
|
|
|
fn drawOutline(out: *mut u8, in_: *const u8, width: i32, height: i32, out_size: i32);
|
|
|
|
fn get_di_bits(out: *mut u8, dc: HDC, hbmColor: HBITMAP, width: i32, height: i32) -> i32;
|
|
|
|
fn blank_screen(v: BOOL);
|
|
|
|
fn win32_enable_lowlevel_keyboard(hwnd: HWND) -> i32;
|
|
|
|
fn win32_disable_lowlevel_keyboard(hwnd: HWND);
|
|
|
|
fn win_stop_system_key_propagate(v: BOOL);
|
|
|
|
fn is_win_down() -> BOOL;
|
2022-12-14 00:51:43 +08:00
|
|
|
fn is_local_system() -> BOOL;
|
2023-07-17 18:52:43 +08:00
|
|
|
fn alloc_console_and_redirect();
|
2024-04-19 11:31:52 +08:00
|
|
|
fn is_service_running_w(svc_name: *const u16) -> bool;
|
2022-05-12 17:35:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
extern "system" {
|
|
|
|
fn BlockInput(v: BOOL) -> BOOL;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::main(flavor = "current_thread")]
|
|
|
|
async fn run_service(_arguments: Vec<OsString>) -> ResultType<()> {
|
|
|
|
let event_handler = move |control_event| -> ServiceControlHandlerResult {
|
|
|
|
log::info!("Got service control event: {:?}", control_event);
|
|
|
|
match control_event {
|
|
|
|
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
|
2023-11-14 12:11:38 +08:00
|
|
|
ServiceControl::Stop | ServiceControl::Preshutdown | ServiceControl::Shutdown => {
|
2022-05-12 17:35:25 +08:00
|
|
|
send_close(crate::POSTFIX_SERVICE).ok();
|
|
|
|
ServiceControlHandlerResult::NoError
|
|
|
|
}
|
|
|
|
_ => ServiceControlHandlerResult::NotImplemented,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Register system service event handler
|
|
|
|
let status_handle = service_control_handler::register(crate::get_app_name(), event_handler)?;
|
|
|
|
|
|
|
|
let next_status = ServiceStatus {
|
|
|
|
// Should match the one from system service registry
|
|
|
|
service_type: SERVICE_TYPE,
|
|
|
|
// The new state
|
|
|
|
current_state: ServiceState::Running,
|
|
|
|
// Accept stop events when running
|
|
|
|
controls_accepted: ServiceControlAccept::STOP,
|
|
|
|
// Used to report an error when starting or stopping only, otherwise must be zero
|
|
|
|
exit_code: ServiceExitCode::Win32(0),
|
|
|
|
// Only used for pending states, otherwise must be zero
|
|
|
|
checkpoint: 0,
|
|
|
|
// Only used for pending states, otherwise must be zero
|
|
|
|
wait_hint: Duration::default(),
|
|
|
|
process_id: None,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Tell the system that the service is running now
|
|
|
|
status_handle.set_service_status(next_status)?;
|
|
|
|
|
|
|
|
let mut session_id = unsafe { get_current_session(share_rdp()) };
|
|
|
|
log::info!("session id {}", session_id);
|
|
|
|
let mut h_process = launch_server(session_id, true).await.unwrap_or(NULL);
|
|
|
|
let mut incoming = ipc::new_listener(crate::POSTFIX_SERVICE).await?;
|
2024-02-14 21:29:17 +05:30
|
|
|
let mut stored_usid = None;
|
2022-05-12 17:35:25 +08:00
|
|
|
loop {
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
let sids: Vec<_> = get_available_sessions(false)
|
|
|
|
.iter()
|
|
|
|
.map(|e| e.sid)
|
|
|
|
.collect();
|
|
|
|
if !sids.contains(&session_id) || !is_share_rdp() {
|
2024-02-14 21:29:17 +05:30
|
|
|
let current_active_session = unsafe { get_current_session(share_rdp()) };
|
|
|
|
if session_id != current_active_session {
|
|
|
|
session_id = current_active_session;
|
|
|
|
h_process = launch_server(session_id, true).await.unwrap_or(NULL);
|
|
|
|
}
|
|
|
|
}
|
2022-05-12 17:35:25 +08:00
|
|
|
let res = timeout(super::SERVICE_INTERVAL, incoming.next()).await;
|
|
|
|
match res {
|
|
|
|
Ok(res) => match res {
|
|
|
|
Some(Ok(stream)) => {
|
|
|
|
let mut stream = ipc::Connection::new(stream);
|
|
|
|
if let Ok(Some(data)) = stream.next_timeout(1000).await {
|
|
|
|
match data {
|
|
|
|
ipc::Data::Close => {
|
|
|
|
log::info!("close received");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ipc::Data::SAS => {
|
|
|
|
send_sas();
|
|
|
|
}
|
2024-02-14 21:29:17 +05:30
|
|
|
ipc::Data::UserSid(usid) => {
|
|
|
|
if let Some(usid) = usid {
|
|
|
|
if session_id != usid {
|
|
|
|
log::info!(
|
|
|
|
"session changed from {} to {}",
|
|
|
|
session_id,
|
|
|
|
usid
|
|
|
|
);
|
|
|
|
session_id = usid;
|
|
|
|
stored_usid = Some(session_id);
|
|
|
|
h_process =
|
|
|
|
launch_server(session_id, true).await.unwrap_or(NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-05-12 17:35:25 +08:00
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
},
|
|
|
|
Err(_) => {
|
|
|
|
// timeout
|
|
|
|
unsafe {
|
|
|
|
let tmp = get_current_session(share_rdp());
|
|
|
|
if tmp == 0xFFFFFFFF {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
let mut close_sent = false;
|
2024-02-14 21:29:17 +05:30
|
|
|
if tmp != session_id && stored_usid != Some(session_id) {
|
2022-05-12 17:35:25 +08:00
|
|
|
log::info!("session changed from {} to {}", session_id, tmp);
|
|
|
|
session_id = tmp;
|
|
|
|
send_close_async("").await.ok();
|
|
|
|
close_sent = true;
|
|
|
|
}
|
|
|
|
let mut exit_code: DWORD = 0;
|
|
|
|
if h_process.is_null()
|
|
|
|
|| (GetExitCodeProcess(h_process, &mut exit_code) == TRUE
|
|
|
|
&& exit_code != STILL_ACTIVE
|
|
|
|
&& CloseHandle(h_process) == TRUE)
|
|
|
|
{
|
|
|
|
match launch_server(session_id, !close_sent).await {
|
|
|
|
Ok(ptr) => {
|
|
|
|
h_process = ptr;
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
log::error!("Failed to launch server: {}", err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !h_process.is_null() {
|
|
|
|
send_close_async("").await.ok();
|
|
|
|
unsafe { CloseHandle(h_process) };
|
|
|
|
}
|
|
|
|
|
|
|
|
status_handle.set_service_status(ServiceStatus {
|
|
|
|
service_type: SERVICE_TYPE,
|
|
|
|
current_state: ServiceState::Stopped,
|
|
|
|
controls_accepted: ServiceControlAccept::empty(),
|
|
|
|
exit_code: ServiceExitCode::Win32(0),
|
|
|
|
checkpoint: 0,
|
|
|
|
wait_hint: Duration::default(),
|
|
|
|
process_id: None,
|
|
|
|
})?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn launch_server(session_id: DWORD, close_first: bool) -> ResultType<HANDLE> {
|
|
|
|
if close_first {
|
|
|
|
// in case started some elsewhere
|
|
|
|
send_close_async("").await.ok();
|
|
|
|
}
|
|
|
|
let cmd = format!(
|
|
|
|
"\"{}\" --server",
|
|
|
|
std::env::current_exe()?.to_str().unwrap_or("")
|
|
|
|
);
|
|
|
|
use std::os::windows::ffi::OsStrExt;
|
|
|
|
let wstr: Vec<u16> = std::ffi::OsStr::new(&cmd)
|
|
|
|
.encode_wide()
|
|
|
|
.chain(Some(0).into_iter())
|
|
|
|
.collect();
|
|
|
|
let wstr = wstr.as_ptr();
|
|
|
|
let h = unsafe { LaunchProcessWin(wstr, session_id, FALSE) };
|
|
|
|
if h.is_null() {
|
2023-11-20 17:38:53 +08:00
|
|
|
log::error!("Failed to launch server: {}", io::Error::last_os_error());
|
2022-05-12 17:35:25 +08:00
|
|
|
}
|
|
|
|
Ok(h)
|
|
|
|
}
|
|
|
|
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
pub fn run_as_user(arg: Vec<&str>) -> ResultType<Option<std::process::Child>> {
|
2022-05-12 17:35:25 +08:00
|
|
|
let cmd = format!(
|
|
|
|
"\"{}\" {}",
|
|
|
|
std::env::current_exe()?.to_str().unwrap_or(""),
|
2022-11-27 12:31:53 +08:00
|
|
|
arg.join(" "),
|
2022-05-12 17:35:25 +08:00
|
|
|
);
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
let Some(session_id) = get_current_process_session_id() else {
|
|
|
|
bail!("Failed to get current process session id");
|
|
|
|
};
|
2022-05-12 17:35:25 +08:00
|
|
|
use std::os::windows::ffi::OsStrExt;
|
|
|
|
let wstr: Vec<u16> = std::ffi::OsStr::new(&cmd)
|
|
|
|
.encode_wide()
|
|
|
|
.chain(Some(0).into_iter())
|
|
|
|
.collect();
|
|
|
|
let wstr = wstr.as_ptr();
|
|
|
|
let h = unsafe { LaunchProcessWin(wstr, session_id, TRUE) };
|
|
|
|
if h.is_null() {
|
|
|
|
bail!(
|
2022-11-27 12:31:53 +08:00
|
|
|
"Failed to launch {:?} with session id {}: {}",
|
2022-05-12 17:35:25 +08:00
|
|
|
arg,
|
|
|
|
session_id,
|
2023-11-20 17:38:53 +08:00
|
|
|
io::Error::last_os_error()
|
2022-05-12 17:35:25 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::main(flavor = "current_thread")]
|
|
|
|
async fn send_close(postfix: &str) -> ResultType<()> {
|
|
|
|
send_close_async(postfix).await
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn send_close_async(postfix: &str) -> ResultType<()> {
|
|
|
|
ipc::connect(1000, postfix)
|
|
|
|
.await?
|
|
|
|
.send(&ipc::Data::Close)
|
|
|
|
.await?;
|
|
|
|
// sleep a while to wait for closing and exit
|
|
|
|
sleep(0.1).await;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://docs.microsoft.com/en-us/windows/win32/api/sas/nf-sas-sendsas
|
|
|
|
// https://www.cnblogs.com/doutu/p/4892726.html
|
2024-02-26 13:22:21 +08:00
|
|
|
pub fn send_sas() {
|
2022-05-12 17:35:25 +08:00
|
|
|
#[link(name = "sas")]
|
|
|
|
extern "system" {
|
|
|
|
pub fn SendSAS(AsUser: BOOL);
|
|
|
|
}
|
|
|
|
unsafe {
|
|
|
|
log::info!("SAS received");
|
|
|
|
SendSAS(FALSE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
lazy_static::lazy_static! {
|
|
|
|
static ref SUPPRESS: Arc<Mutex<Instant>> = Arc::new(Mutex::new(Instant::now()));
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn desktop_changed() -> bool {
|
|
|
|
unsafe { inputDesktopSelected() == FALSE }
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn try_change_desktop() -> bool {
|
|
|
|
unsafe {
|
|
|
|
if inputDesktopSelected() == FALSE {
|
|
|
|
let res = selectInputDesktop() == TRUE;
|
|
|
|
if !res {
|
|
|
|
let mut s = SUPPRESS.lock().unwrap();
|
|
|
|
if s.elapsed() > std::time::Duration::from_secs(3) {
|
2023-11-20 17:38:53 +08:00
|
|
|
log::error!("Failed to switch desktop: {}", io::Error::last_os_error());
|
2022-05-12 17:35:25 +08:00
|
|
|
*s = Instant::now();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log::info!("Desktop switched");
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn share_rdp() -> BOOL {
|
2024-02-14 21:29:17 +05:30
|
|
|
if get_reg("share_rdp") != "false" {
|
2022-05-12 17:35:25 +08:00
|
|
|
TRUE
|
2024-02-14 21:29:17 +05:30
|
|
|
} else {
|
|
|
|
FALSE
|
2022-05-12 17:35:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_share_rdp() -> bool {
|
|
|
|
share_rdp() == TRUE
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_share_rdp(enable: bool) {
|
2023-06-05 20:27:48 +08:00
|
|
|
let (subkey, _, _, _) = get_install_info();
|
2022-05-12 17:35:25 +08:00
|
|
|
let cmd = format!(
|
|
|
|
"reg add {} /f /v share_rdp /t REG_SZ /d \"{}\"",
|
|
|
|
subkey,
|
|
|
|
if enable { "true" } else { "false" }
|
|
|
|
);
|
2022-06-27 00:36:31 +08:00
|
|
|
run_cmds(cmd, false, "share_rdp").ok();
|
2022-05-12 17:35:25 +08:00
|
|
|
}
|
|
|
|
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
pub fn get_current_process_session_id() -> Option<u32> {
|
|
|
|
let mut sid = 0;
|
|
|
|
if unsafe { ProcessIdToSessionId(GetCurrentProcessId(), &mut sid) == TRUE } {
|
|
|
|
Some(sid)
|
|
|
|
} else {
|
|
|
|
None
|
2024-02-14 21:29:17 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-26 13:22:21 +08:00
|
|
|
pub fn is_physical_console_session() -> Option<bool> {
|
|
|
|
if let Some(sid) = get_current_process_session_id() {
|
2024-02-26 13:34:26 +08:00
|
|
|
let physical_console_session_id = unsafe { get_current_session(FALSE) };
|
2024-02-26 13:22:21 +08:00
|
|
|
if physical_console_session_id == u32::MAX {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
return Some(physical_console_session_id == sid);
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2022-05-12 17:35:25 +08:00
|
|
|
pub fn get_active_username() -> String {
|
2024-02-19 10:32:13 +08:00
|
|
|
// get_active_user will give console username higher priority
|
|
|
|
if let Some(name) = get_current_session_username() {
|
|
|
|
return name;
|
|
|
|
}
|
2022-12-14 00:51:43 +08:00
|
|
|
if !is_root() {
|
|
|
|
return crate::username();
|
2022-05-12 17:35:25 +08:00
|
|
|
}
|
2022-12-14 00:51:43 +08:00
|
|
|
|
2022-05-12 17:35:25 +08:00
|
|
|
extern "C" {
|
|
|
|
fn get_active_user(path: *mut u16, n: u32, rdp: BOOL) -> u32;
|
|
|
|
}
|
|
|
|
let buff_size = 256;
|
|
|
|
let mut buff: Vec<u16> = Vec::with_capacity(buff_size);
|
|
|
|
buff.resize(buff_size, 0);
|
|
|
|
let n = unsafe { get_active_user(buff.as_mut_ptr(), buff_size as _, share_rdp()) };
|
|
|
|
if n == 0 {
|
|
|
|
return "".to_owned();
|
|
|
|
}
|
|
|
|
let sl = unsafe { std::slice::from_raw_parts(buff.as_ptr(), n as _) };
|
|
|
|
String::from_utf16(sl)
|
|
|
|
.unwrap_or("??".to_owned())
|
|
|
|
.trim_end_matches('\0')
|
|
|
|
.to_owned()
|
|
|
|
}
|
|
|
|
|
2024-02-19 10:32:13 +08:00
|
|
|
fn get_current_session_username() -> Option<String> {
|
|
|
|
let Some(sid) = get_current_process_session_id() else {
|
|
|
|
log::error!("get_current_process_session_id failed");
|
|
|
|
return None;
|
|
|
|
};
|
|
|
|
Some(get_session_username(sid))
|
|
|
|
}
|
|
|
|
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
fn get_session_username(session_id: u32) -> String {
|
2024-02-14 21:29:17 +05:30
|
|
|
extern "C" {
|
|
|
|
fn get_session_user_info(path: *mut u16, n: u32, rdp: bool, session_id: u32) -> u32;
|
|
|
|
}
|
|
|
|
let buff_size = 256;
|
|
|
|
let mut buff: Vec<u16> = Vec::with_capacity(buff_size);
|
|
|
|
buff.resize(buff_size, 0);
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
let n = unsafe { get_session_user_info(buff.as_mut_ptr(), buff_size as _, true, session_id) };
|
2024-02-14 21:29:17 +05:30
|
|
|
if n == 0 {
|
|
|
|
return "".to_owned();
|
|
|
|
}
|
|
|
|
let sl = unsafe { std::slice::from_raw_parts(buff.as_ptr(), n as _) };
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
String::from_utf16(sl)
|
2024-02-14 21:29:17 +05:30
|
|
|
.unwrap_or("".to_owned())
|
|
|
|
.trim_end_matches('\0')
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
.to_owned()
|
2024-02-14 21:29:17 +05:30
|
|
|
}
|
|
|
|
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
pub fn get_available_sessions(name: bool) -> Vec<WindowsSession> {
|
2024-02-14 21:29:17 +05:30
|
|
|
extern "C" {
|
|
|
|
fn get_available_session_ids(buf: *mut wchar_t, buf_size: c_int, include_rdp: bool);
|
|
|
|
}
|
|
|
|
const BUF_SIZE: c_int = 1024;
|
|
|
|
let mut buf: Vec<wchar_t> = vec![0; BUF_SIZE as usize];
|
|
|
|
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
let station_session_id_array = unsafe {
|
2024-02-14 21:29:17 +05:30
|
|
|
get_available_session_ids(buf.as_mut_ptr(), BUF_SIZE, true);
|
|
|
|
let session_ids = String::from_utf16_lossy(&buf);
|
|
|
|
session_ids.trim_matches(char::from(0)).trim().to_string()
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
};
|
|
|
|
let mut v: Vec<WindowsSession> = vec![];
|
|
|
|
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-wtsgetactiveconsolesessionid
|
2024-02-19 10:32:13 +08:00
|
|
|
let physical_console_sid = unsafe { get_current_session(FALSE) };
|
|
|
|
if physical_console_sid != u32::MAX {
|
|
|
|
let physical_console_name = if name {
|
|
|
|
let physical_console_username = get_session_username(physical_console_sid);
|
|
|
|
if physical_console_username.is_empty() {
|
|
|
|
"Console".to_owned()
|
|
|
|
} else {
|
2024-02-19 11:06:01 +08:00
|
|
|
format!("Console: {physical_console_username}")
|
2024-02-19 10:32:13 +08:00
|
|
|
}
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
} else {
|
2024-02-19 10:32:13 +08:00
|
|
|
"".to_owned()
|
|
|
|
};
|
|
|
|
v.push(WindowsSession {
|
|
|
|
sid: physical_console_sid,
|
|
|
|
name: physical_console_name,
|
|
|
|
..Default::default()
|
|
|
|
});
|
|
|
|
}
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
// https://learn.microsoft.com/en-us/previous-versions//cc722458(v=technet.10)?redirectedfrom=MSDN
|
|
|
|
for type_session_id in station_session_id_array.split(",") {
|
|
|
|
let split: Vec<_> = type_session_id.split(":").collect();
|
|
|
|
if split.len() == 2 {
|
|
|
|
if let Ok(sid) = split[1].parse::<u32>() {
|
|
|
|
if !v.iter().any(|e| (*e).sid == sid) {
|
|
|
|
let name = if name {
|
2024-02-19 11:06:01 +08:00
|
|
|
let name = get_session_username(sid);
|
|
|
|
if name.is_empty() {
|
|
|
|
split[0].to_string()
|
|
|
|
} else {
|
|
|
|
format!("{}: {}", split[0], name)
|
|
|
|
}
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
} else {
|
|
|
|
"".to_owned()
|
|
|
|
};
|
|
|
|
v.push(WindowsSession {
|
|
|
|
sid,
|
|
|
|
name,
|
|
|
|
..Default::default()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-02-14 21:29:17 +05:30
|
|
|
}
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
if name {
|
|
|
|
let mut name_count: HashMap<String, usize> = HashMap::new();
|
|
|
|
for session in &v {
|
|
|
|
*name_count.entry(session.name.clone()).or_insert(0) += 1;
|
|
|
|
}
|
|
|
|
let current_sid = get_current_process_session_id().unwrap_or_default();
|
|
|
|
for e in v.iter_mut() {
|
|
|
|
let running = e.sid == current_sid && current_sid != 0;
|
|
|
|
if name_count.get(&e.name).map(|v| *v).unwrap_or_default() > 1 {
|
|
|
|
e.name = format!("{} (sid = {})", e.name, e.sid);
|
|
|
|
}
|
|
|
|
if running {
|
|
|
|
e.name = format!("{} (running)", e.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
v
|
2024-02-14 21:29:17 +05:30
|
|
|
}
|
|
|
|
|
2022-10-12 16:06:15 +08:00
|
|
|
pub fn get_active_user_home() -> Option<PathBuf> {
|
|
|
|
let username = get_active_username();
|
|
|
|
if !username.is_empty() {
|
|
|
|
let drive = std::env::var("SystemDrive").unwrap_or("C:".to_owned());
|
|
|
|
let home = PathBuf::from(format!("{}\\Users\\{}", drive, username));
|
|
|
|
if home.exists() {
|
|
|
|
return Some(home);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2022-05-12 17:35:25 +08:00
|
|
|
pub fn is_prelogin() -> bool {
|
2024-02-19 10:32:13 +08:00
|
|
|
let Some(username) = get_current_session_username() else {
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
return false;
|
|
|
|
};
|
2022-05-12 17:35:25 +08:00
|
|
|
username.is_empty() || username == "SYSTEM"
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_root() -> bool {
|
2022-12-14 00:51:43 +08:00
|
|
|
// https://stackoverflow.com/questions/4023586/correct-way-to-find-out-if-a-service-is-running-as-the-system-user
|
|
|
|
unsafe { is_local_system() == TRUE }
|
2022-05-12 17:35:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn lock_screen() {
|
|
|
|
extern "system" {
|
|
|
|
pub fn LockWorkStation() -> BOOL;
|
|
|
|
}
|
|
|
|
unsafe {
|
|
|
|
LockWorkStation();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const IS1: &str = "{54E86BC2-6C85-41F3-A9EB-1A94AC9B1F93}_is1";
|
|
|
|
|
|
|
|
fn get_subkey(name: &str, wow: bool) -> String {
|
|
|
|
let tmp = format!(
|
|
|
|
"HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{}",
|
|
|
|
name
|
|
|
|
);
|
|
|
|
if wow {
|
|
|
|
tmp.replace("Microsoft", "Wow6432Node\\Microsoft")
|
|
|
|
} else {
|
|
|
|
tmp
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_valid_subkey() -> String {
|
|
|
|
let subkey = get_subkey(IS1, false);
|
|
|
|
if !get_reg_of(&subkey, "InstallLocation").is_empty() {
|
|
|
|
return subkey;
|
|
|
|
}
|
|
|
|
let subkey = get_subkey(IS1, true);
|
|
|
|
if !get_reg_of(&subkey, "InstallLocation").is_empty() {
|
|
|
|
return subkey;
|
|
|
|
}
|
|
|
|
let app_name = crate::get_app_name();
|
|
|
|
let subkey = get_subkey(&app_name, true);
|
|
|
|
if !get_reg_of(&subkey, "InstallLocation").is_empty() {
|
|
|
|
return subkey;
|
|
|
|
}
|
|
|
|
return get_subkey(&app_name, false);
|
|
|
|
}
|
|
|
|
|
2023-06-05 20:27:48 +08:00
|
|
|
pub fn get_install_info() -> (String, String, String, String) {
|
2022-05-12 17:35:25 +08:00
|
|
|
get_install_info_with_subkey(get_valid_subkey())
|
|
|
|
}
|
|
|
|
|
2023-06-05 20:27:48 +08:00
|
|
|
fn get_default_install_info() -> (String, String, String, String) {
|
2022-05-12 17:35:25 +08:00
|
|
|
get_install_info_with_subkey(get_subkey(&crate::get_app_name(), false))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_default_install_path() -> String {
|
|
|
|
let mut pf = "C:\\Program Files".to_owned();
|
2022-05-13 17:56:36 +08:00
|
|
|
if let Ok(x) = std::env::var("ProgramFiles") {
|
|
|
|
if std::path::Path::new(&x).exists() {
|
|
|
|
pf = x;
|
2022-05-12 17:35:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#[cfg(target_pointer_width = "32")]
|
|
|
|
{
|
|
|
|
let tmp = pf.replace("Program Files", "Program Files (x86)");
|
|
|
|
if std::path::Path::new(&tmp).exists() {
|
|
|
|
pf = tmp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
format!("{}\\{}", pf, crate::get_app_name())
|
|
|
|
}
|
|
|
|
|
2022-04-25 12:28:28 +08:00
|
|
|
pub fn check_update_broker_process() -> ResultType<()> {
|
2023-11-19 15:26:02 +08:00
|
|
|
let process_exe = win_topmost_window::INJECTED_PROCESS_EXE;
|
|
|
|
let origin_process_exe = win_topmost_window::ORIGIN_PROCESS_EXE;
|
2022-04-25 12:28:28 +08:00
|
|
|
|
|
|
|
let exe_file = std::env::current_exe()?;
|
2023-07-22 14:16:41 +08:00
|
|
|
let Some(cur_dir) = exe_file.parent() else {
|
2022-04-25 12:28:28 +08:00
|
|
|
bail!("Cannot get parent of current exe file");
|
2023-07-22 14:16:41 +08:00
|
|
|
};
|
2022-04-25 12:28:28 +08:00
|
|
|
let cur_exe = cur_dir.join(process_exe);
|
|
|
|
|
2023-11-10 08:12:29 +08:00
|
|
|
// Force update broker exe if failed to check modified time.
|
|
|
|
let cmds = format!(
|
|
|
|
"
|
|
|
|
chcp 65001
|
|
|
|
taskkill /F /IM {process_exe}
|
|
|
|
copy /Y \"{origin_process_exe}\" \"{cur_exe}\"
|
|
|
|
",
|
|
|
|
cur_exe = cur_exe.to_string_lossy(),
|
|
|
|
);
|
|
|
|
|
2023-01-27 13:03:35 +08:00
|
|
|
if !std::path::Path::new(&cur_exe).exists() {
|
2023-11-10 08:12:29 +08:00
|
|
|
run_cmds(cmds, false, "update_broker")?;
|
2023-01-27 13:03:35 +08:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
2022-04-25 12:28:28 +08:00
|
|
|
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
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-27 00:36:31 +08:00
|
|
|
run_cmds(cmds, false, "update_broker")?;
|
2022-04-25 12:28:28 +08:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-06-05 20:27:48 +08:00
|
|
|
fn get_install_info_with_subkey(subkey: String) -> (String, String, String, String) {
|
2022-05-12 17:35:25 +08:00
|
|
|
let mut path = get_reg_of(&subkey, "InstallLocation");
|
|
|
|
if path.is_empty() {
|
|
|
|
path = get_default_install_path();
|
|
|
|
}
|
|
|
|
path = path.trim_end_matches('\\').to_owned();
|
|
|
|
let start_menu = format!(
|
|
|
|
"%ProgramData%\\Microsoft\\Windows\\Start Menu\\Programs\\{}",
|
|
|
|
crate::get_app_name()
|
|
|
|
);
|
|
|
|
let exe = format!("{}\\{}.exe", path, crate::get_app_name());
|
2023-06-05 20:27:48 +08:00
|
|
|
(subkey, path, start_menu, exe)
|
2022-05-12 17:35:25 +08:00
|
|
|
}
|
|
|
|
|
2023-07-22 14:16:41 +08:00
|
|
|
pub fn copy_raw_cmd(src_raw: &str, _raw: &str, _path: &str) -> ResultType<String> {
|
2023-03-30 10:08:05 +08:00
|
|
|
let main_raw = format!(
|
2022-10-23 19:52:30 +08:00
|
|
|
"XCOPY \"{}\" \"{}\" /Y /E /H /C /I /K /R /Z",
|
2023-03-30 10:08:05 +08:00
|
|
|
PathBuf::from(src_raw)
|
2022-10-23 19:52:30 +08:00
|
|
|
.parent()
|
2023-07-22 14:16:41 +08:00
|
|
|
.ok_or(anyhow!("Can't get parent directory of {src_raw}"))?
|
2022-10-23 19:52:30 +08:00
|
|
|
.to_string_lossy()
|
|
|
|
.to_string(),
|
2023-03-30 10:08:05 +08:00
|
|
|
_path
|
2022-10-23 19:52:30 +08:00
|
|
|
);
|
2023-07-22 14:16:41 +08:00
|
|
|
return Ok(main_raw);
|
2023-03-30 10:08:05 +08:00
|
|
|
}
|
|
|
|
|
2023-07-22 14:16:41 +08:00
|
|
|
pub fn copy_exe_cmd(src_exe: &str, exe: &str, path: &str) -> ResultType<String> {
|
|
|
|
let main_exe = copy_raw_cmd(src_exe, exe, path)?;
|
|
|
|
Ok(format!(
|
2023-04-17 09:48:39 +08:00
|
|
|
"
|
|
|
|
{main_exe}
|
|
|
|
copy /Y \"{ORIGIN_PROCESS_EXE}\" \"{path}\\{broker_exe}\"
|
|
|
|
",
|
2023-11-19 15:26:02 +08:00
|
|
|
ORIGIN_PROCESS_EXE = win_topmost_window::ORIGIN_PROCESS_EXE,
|
|
|
|
broker_exe = win_topmost_window::INJECTED_PROCESS_EXE,
|
2023-07-22 14:16:41 +08:00
|
|
|
))
|
2022-10-23 19:52:30 +08:00
|
|
|
}
|
|
|
|
|
2022-05-12 17:35:25 +08:00
|
|
|
fn get_after_install(exe: &str) -> String {
|
|
|
|
let app_name = crate::get_app_name();
|
|
|
|
let ext = app_name.to_lowercase();
|
2023-06-05 15:21:42 +08:00
|
|
|
|
2023-06-05 00:27:40 +08:00
|
|
|
// reg delete HKEY_CURRENT_USER\Software\Classes for
|
|
|
|
// https://github.com/rustdesk/rustdesk/commit/f4bdfb6936ae4804fc8ab1cf560db192622ad01a
|
|
|
|
// and https://github.com/leanflutter/uni_links_desktop/blob/1b72b0226cec9943ca8a84e244c149773f384e46/lib/src/protocol_registrar_impl_windows.dart#L30
|
2023-06-05 15:21:42 +08:00
|
|
|
let hcu = winreg::RegKey::predef(HKEY_CURRENT_USER);
|
|
|
|
hcu.delete_subkey_all(format!("Software\\Classes\\{}", exe))
|
|
|
|
.ok();
|
|
|
|
|
2022-05-12 17:35:25 +08:00
|
|
|
format!("
|
|
|
|
chcp 65001
|
|
|
|
reg add HKEY_CLASSES_ROOT\\.{ext} /f
|
|
|
|
reg add HKEY_CLASSES_ROOT\\.{ext}\\DefaultIcon /f
|
|
|
|
reg add HKEY_CLASSES_ROOT\\.{ext}\\DefaultIcon /f /ve /t REG_SZ /d \"\\\"{exe}\\\",0\"
|
|
|
|
reg add HKEY_CLASSES_ROOT\\.{ext}\\shell /f
|
|
|
|
reg add HKEY_CLASSES_ROOT\\.{ext}\\shell\\open /f
|
|
|
|
reg add HKEY_CLASSES_ROOT\\.{ext}\\shell\\open\\command /f
|
|
|
|
reg add HKEY_CLASSES_ROOT\\.{ext}\\shell\\open\\command /f /ve /t REG_SZ /d \"\\\"{exe}\\\" --play \\\"%%1\\\"\"
|
2023-06-05 00:27:40 +08:00
|
|
|
reg add HKEY_CLASSES_ROOT\\{ext} /f
|
|
|
|
reg add HKEY_CLASSES_ROOT\\{ext} /f /v \"URL Protocol\" /t REG_SZ /d \"\"
|
|
|
|
reg add HKEY_CLASSES_ROOT\\{ext}\\shell /f
|
|
|
|
reg add HKEY_CLASSES_ROOT\\{ext}\\shell\\open /f
|
|
|
|
reg add HKEY_CLASSES_ROOT\\{ext}\\shell\\open\\command /f
|
|
|
|
reg add HKEY_CLASSES_ROOT\\{ext}\\shell\\open\\command /f /ve /t REG_SZ /d \"\\\"{exe}\\\" \\\"%%1\\\"\"
|
2023-08-02 10:28:34 +08:00
|
|
|
netsh advfirewall firewall add rule name=\"{app_name} Service\" dir=out action=allow program=\"{exe}\" enable=yes
|
|
|
|
netsh advfirewall firewall add rule name=\"{app_name} Service\" dir=in action=allow program=\"{exe}\" enable=yes
|
2023-06-05 20:27:48 +08:00
|
|
|
{create_service}
|
2022-05-12 17:35:25 +08:00
|
|
|
reg add HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System /f /v SoftwareSASGeneration /t REG_DWORD /d 1
|
2023-06-05 20:27:48 +08:00
|
|
|
", create_service=get_create_service(&exe))
|
2022-05-12 17:35:25 +08:00
|
|
|
}
|
|
|
|
|
2022-06-27 00:36:31 +08:00
|
|
|
pub fn install_me(options: &str, path: String, silent: bool, debug: bool) -> ResultType<()> {
|
2023-03-01 11:17:46 +08:00
|
|
|
let uninstall_str = get_uninstall(false);
|
2022-05-12 17:35:25 +08:00
|
|
|
let mut path = path.trim_end_matches('\\').to_owned();
|
2023-06-05 20:27:48 +08:00
|
|
|
let (subkey, _path, start_menu, exe) = get_default_install_info();
|
2022-05-12 17:35:25 +08:00
|
|
|
let mut exe = exe;
|
|
|
|
if path.is_empty() {
|
|
|
|
path = _path;
|
|
|
|
} else {
|
|
|
|
exe = exe.replace(&_path, &path);
|
|
|
|
}
|
|
|
|
let mut version_major = "0";
|
|
|
|
let mut version_minor = "0";
|
|
|
|
let mut version_build = "0";
|
|
|
|
let versions: Vec<&str> = crate::VERSION.split(".").collect();
|
|
|
|
if versions.len() > 0 {
|
|
|
|
version_major = versions[0];
|
|
|
|
}
|
|
|
|
if versions.len() > 1 {
|
|
|
|
version_minor = versions[1];
|
|
|
|
}
|
|
|
|
if versions.len() > 2 {
|
|
|
|
version_build = versions[2];
|
|
|
|
}
|
2023-06-05 20:27:48 +08:00
|
|
|
let app_name = crate::get_app_name();
|
2022-05-12 17:35:25 +08:00
|
|
|
|
2022-06-27 00:36:31 +08:00
|
|
|
let tmp_path = std::env::temp_dir().to_string_lossy().to_string();
|
2022-05-12 17:35:25 +08:00
|
|
|
let mk_shortcut = write_cmds(
|
|
|
|
format!(
|
|
|
|
"
|
|
|
|
Set oWS = WScript.CreateObject(\"WScript.Shell\")
|
|
|
|
sLinkFile = \"{tmp_path}\\{app_name}.lnk\"
|
|
|
|
|
|
|
|
Set oLink = oWS.CreateShortcut(sLinkFile)
|
|
|
|
oLink.TargetPath = \"{exe}\"
|
|
|
|
oLink.Save
|
2023-06-05 20:27:48 +08:00
|
|
|
"
|
2022-05-12 17:35:25 +08:00
|
|
|
),
|
|
|
|
"vbs",
|
2022-06-27 00:36:31 +08:00
|
|
|
"mk_shortcut",
|
2022-05-12 17:35:25 +08:00
|
|
|
)?
|
|
|
|
.to_str()
|
|
|
|
.unwrap_or("")
|
|
|
|
.to_owned();
|
|
|
|
// https://superuser.com/questions/392061/how-to-make-a-shortcut-from-cmd
|
|
|
|
let uninstall_shortcut = write_cmds(
|
|
|
|
format!(
|
|
|
|
"
|
|
|
|
Set oWS = WScript.CreateObject(\"WScript.Shell\")
|
|
|
|
sLinkFile = \"{tmp_path}\\Uninstall {app_name}.lnk\"
|
|
|
|
Set oLink = oWS.CreateShortcut(sLinkFile)
|
|
|
|
oLink.TargetPath = \"{exe}\"
|
|
|
|
oLink.Arguments = \"--uninstall\"
|
|
|
|
oLink.IconLocation = \"msiexec.exe\"
|
|
|
|
oLink.Save
|
2023-06-05 20:27:48 +08:00
|
|
|
"
|
2022-05-12 17:35:25 +08:00
|
|
|
),
|
|
|
|
"vbs",
|
2022-06-27 00:36:31 +08:00
|
|
|
"uninstall_shortcut",
|
2022-05-12 17:35:25 +08:00
|
|
|
)?
|
|
|
|
.to_str()
|
|
|
|
.unwrap_or("")
|
|
|
|
.to_owned();
|
2023-06-05 20:27:48 +08:00
|
|
|
let tray_shortcut = get_tray_shortcut(&exe, &tmp_path)?;
|
2022-05-12 17:35:25 +08:00
|
|
|
let mut shortcuts = Default::default();
|
|
|
|
if options.contains("desktopicon") {
|
|
|
|
shortcuts = format!(
|
|
|
|
"copy /Y \"{}\\{}.lnk\" \"%PUBLIC%\\Desktop\\\"",
|
|
|
|
tmp_path,
|
|
|
|
crate::get_app_name()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if options.contains("startmenu") {
|
|
|
|
shortcuts = format!(
|
2023-06-05 20:27:48 +08:00
|
|
|
"{shortcuts}
|
2022-05-12 17:35:25 +08:00
|
|
|
md \"{start_menu}\"
|
|
|
|
copy /Y \"{tmp_path}\\{app_name}.lnk\" \"{start_menu}\\\"
|
|
|
|
copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{start_menu}\\\"
|
2023-06-05 20:27:48 +08:00
|
|
|
"
|
2022-05-12 17:35:25 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let meta = std::fs::symlink_metadata(std::env::current_exe()?)?;
|
|
|
|
let size = meta.len() / 1024;
|
|
|
|
// https://docs.microsoft.com/zh-cn/windows/win32/msi/uninstall-registry-key?redirectedfrom=MSDNa
|
|
|
|
// https://www.windowscentral.com/how-edit-registry-using-command-prompt-windows-10
|
|
|
|
// https://www.tenforums.com/tutorials/70903-add-remove-allowed-apps-through-windows-firewall-windows-10-a.html
|
2022-07-06 01:33:04 +08:00
|
|
|
// Note: without if exist, the bat may exit in advance on some Windows7 https://github.com/rustdesk/rustdesk/issues/895
|
2022-07-06 01:29:11 +08:00
|
|
|
let dels = format!(
|
|
|
|
"
|
|
|
|
if exist \"{mk_shortcut}\" del /f /q \"{mk_shortcut}\"
|
|
|
|
if exist \"{uninstall_shortcut}\" del /f /q \"{uninstall_shortcut}\"
|
|
|
|
if exist \"{tray_shortcut}\" del /f /q \"{tray_shortcut}\"
|
|
|
|
if exist \"{tmp_path}\\{app_name}.lnk\" del /f /q \"{tmp_path}\\{app_name}.lnk\"
|
|
|
|
if exist \"{tmp_path}\\Uninstall {app_name}.lnk\" del /f /q \"{tmp_path}\\Uninstall {app_name}.lnk\"
|
|
|
|
if exist \"{tmp_path}\\{app_name} Tray.lnk\" del /f /q \"{tmp_path}\\{app_name} Tray.lnk\"
|
2023-06-05 20:27:48 +08:00
|
|
|
"
|
2022-07-06 01:29:11 +08:00
|
|
|
);
|
2022-10-23 19:52:30 +08:00
|
|
|
let src_exe = std::env::current_exe()?.to_str().unwrap_or("").to_string();
|
|
|
|
|
2023-07-19 14:45:11 +08:00
|
|
|
// potential bug here: if run_cmd cancelled, but config file is changed.
|
2023-08-04 11:27:33 +08:00
|
|
|
if let Some(lic) = get_license() {
|
|
|
|
Config::set_option("key".into(), lic.key);
|
|
|
|
Config::set_option("custom-rendezvous-server".into(), lic.host);
|
|
|
|
Config::set_option("api-server".into(), lic.api);
|
2023-07-19 14:45:11 +08:00
|
|
|
}
|
|
|
|
|
2024-03-12 21:47:29 +08:00
|
|
|
let tray_shortcuts = if config::is_outgoing_only() {
|
|
|
|
"".to_owned()
|
|
|
|
} else {
|
|
|
|
format!("
|
|
|
|
cscript \"{tray_shortcut}\"
|
|
|
|
copy /Y \"{tmp_path}\\{app_name} Tray.lnk\" \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\\"
|
|
|
|
")
|
|
|
|
};
|
|
|
|
|
2022-05-12 17:35:25 +08:00
|
|
|
let cmds = format!(
|
|
|
|
"
|
|
|
|
{uninstall_str}
|
|
|
|
chcp 65001
|
|
|
|
md \"{path}\"
|
2022-10-23 19:52:30 +08:00
|
|
|
{copy_exe}
|
2022-05-12 17:35:25 +08:00
|
|
|
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}\"
|
2022-06-13 19:22:37 +08:00
|
|
|
reg add {subkey} /f /v DisplayVersion /t REG_SZ /d \"{version}\"
|
2022-05-12 17:35:25 +08:00
|
|
|
reg add {subkey} /f /v Version /t REG_SZ /d \"{version}\"
|
2023-06-05 20:27:48 +08:00
|
|
|
reg add {subkey} /f /v BuildDate /t REG_SZ /d \"{build_date}\"
|
2022-05-12 17:35:25 +08:00
|
|
|
reg add {subkey} /f /v InstallLocation /t REG_SZ /d \"{path}\"
|
|
|
|
reg add {subkey} /f /v Publisher /t REG_SZ /d \"{app_name}\"
|
2023-06-05 20:27:48 +08:00
|
|
|
reg add {subkey} /f /v VersionMajor /t REG_DWORD /d {version_major}
|
|
|
|
reg add {subkey} /f /v VersionMinor /t REG_DWORD /d {version_minor}
|
|
|
|
reg add {subkey} /f /v VersionBuild /t REG_DWORD /d {version_build}
|
2022-05-12 17:35:25 +08:00
|
|
|
reg add {subkey} /f /v UninstallString /t REG_SZ /d \"\\\"{exe}\\\" --uninstall\"
|
|
|
|
reg add {subkey} /f /v EstimatedSize /t REG_DWORD /d {size}
|
|
|
|
reg add {subkey} /f /v WindowsInstaller /t REG_DWORD /d 0
|
2022-06-02 20:44:40 +08:00
|
|
|
cscript \"{mk_shortcut}\"
|
|
|
|
cscript \"{uninstall_shortcut}\"
|
2024-03-12 21:47:29 +08:00
|
|
|
{tray_shortcuts}
|
2022-05-12 17:35:25 +08:00
|
|
|
{shortcuts}
|
|
|
|
copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{path}\\\"
|
2022-07-06 01:29:11 +08:00
|
|
|
{dels}
|
2023-06-05 20:27:48 +08:00
|
|
|
{import_config}
|
2022-05-12 17:35:25 +08:00
|
|
|
{after_install}
|
2022-07-01 23:22:28 +08:00
|
|
|
{sleep}
|
2022-05-12 17:35:25 +08:00
|
|
|
",
|
2024-04-07 11:31:51 +08:00
|
|
|
version = crate::VERSION.replace("-", "."),
|
2024-03-12 21:47:29 +08:00
|
|
|
build_date = crate::BUILD_DATE,
|
|
|
|
after_install = get_after_install(&exe),
|
|
|
|
sleep = if debug { "timeout 300" } else { "" },
|
|
|
|
dels = if debug { "" } else { &dels },
|
2023-07-22 14:16:41 +08:00
|
|
|
copy_exe = copy_exe_cmd(&src_exe, &exe, &path)?,
|
2023-06-05 20:27:48 +08:00
|
|
|
import_config = get_import_config(&exe),
|
2022-05-12 17:35:25 +08:00
|
|
|
);
|
2022-06-27 00:36:31 +08:00
|
|
|
run_cmds(cmds, debug, "install")?;
|
2023-06-07 14:25:34 +08:00
|
|
|
run_after_run_cmds(silent);
|
2022-05-12 17:35:25 +08:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn run_after_install() -> ResultType<()> {
|
2023-06-05 20:27:48 +08:00
|
|
|
let (_, _, _, exe) = get_install_info();
|
2022-06-27 00:36:31 +08:00
|
|
|
run_cmds(get_after_install(&exe), true, "after_install")
|
2022-05-12 17:35:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn run_before_uninstall() -> ResultType<()> {
|
2023-03-01 11:17:46 +08:00
|
|
|
run_cmds(get_before_uninstall(true), true, "before_install")
|
2022-05-12 17:35:25 +08:00
|
|
|
}
|
|
|
|
|
2023-03-01 11:17:46 +08:00
|
|
|
fn get_before_uninstall(kill_self: bool) -> String {
|
2022-05-12 17:35:25 +08:00
|
|
|
let app_name = crate::get_app_name();
|
|
|
|
let ext = app_name.to_lowercase();
|
2023-03-01 11:17:46 +08:00
|
|
|
let filter = if kill_self {
|
|
|
|
"".to_string()
|
|
|
|
} else {
|
|
|
|
format!(" /FI \"PID ne {}\"", get_current_pid())
|
|
|
|
};
|
2022-05-12 17:35:25 +08:00
|
|
|
format!(
|
|
|
|
"
|
|
|
|
chcp 65001
|
|
|
|
sc stop {app_name}
|
|
|
|
sc delete {app_name}
|
2022-04-25 12:28:28 +08:00
|
|
|
taskkill /F /IM {broker_exe}
|
2023-03-01 11:17:46 +08:00
|
|
|
taskkill /F /IM {app_name}.exe{filter}
|
2022-05-12 17:35:25 +08:00
|
|
|
reg delete HKEY_CLASSES_ROOT\\.{ext} /f
|
2023-06-05 20:27:48 +08:00
|
|
|
reg delete HKEY_CLASSES_ROOT\\{ext} /f
|
2022-05-12 17:35:25 +08:00
|
|
|
netsh advfirewall firewall delete rule name=\"{app_name} Service\"
|
|
|
|
",
|
2023-11-19 15:26:02 +08:00
|
|
|
broker_exe = WIN_TOPMOST_INJECTED_PROCESS_EXE,
|
2022-05-12 17:35:25 +08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-03-01 11:17:46 +08:00
|
|
|
fn get_uninstall(kill_self: bool) -> String {
|
2024-04-11 11:51:35 +08:00
|
|
|
let reg_uninstall_string = get_reg("UninstallString");
|
|
|
|
if reg_uninstall_string.to_lowercase().contains("msiexec.exe") {
|
|
|
|
return reg_uninstall_string;
|
|
|
|
}
|
|
|
|
|
2023-06-05 15:17:05 +08:00
|
|
|
let mut uninstall_cert_cmd = "".to_string();
|
|
|
|
if let Ok(exe) = std::env::current_exe() {
|
|
|
|
if let Some(exe_path) = exe.to_str() {
|
|
|
|
uninstall_cert_cmd = format!("\"{}\" --uninstall-cert", exe_path);
|
|
|
|
}
|
|
|
|
}
|
2023-06-05 20:27:48 +08:00
|
|
|
let (subkey, path, start_menu, _) = get_install_info();
|
2022-05-12 17:35:25 +08:00
|
|
|
format!(
|
|
|
|
"
|
|
|
|
{before_uninstall}
|
2023-06-05 15:17:05 +08:00
|
|
|
{uninstall_cert_cmd}
|
2022-05-12 17:35:25 +08:00
|
|
|
reg delete {subkey} /f
|
2024-04-19 11:31:52 +08:00
|
|
|
{uninstall_amyuni_idd}
|
2022-07-06 01:29:11 +08:00
|
|
|
if exist \"{path}\" rd /s /q \"{path}\"
|
|
|
|
if exist \"{start_menu}\" rd /s /q \"{start_menu}\"
|
|
|
|
if exist \"%PUBLIC%\\Desktop\\{app_name}.lnk\" del /f /q \"%PUBLIC%\\Desktop\\{app_name}.lnk\"
|
|
|
|
if exist \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" del /f /q \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\"
|
2022-05-12 17:35:25 +08:00
|
|
|
",
|
2023-03-01 11:17:46 +08:00
|
|
|
before_uninstall=get_before_uninstall(kill_self),
|
2024-04-23 17:00:41 +08:00
|
|
|
uninstall_amyuni_idd=get_uninstall_amyuni_idd(),
|
2022-05-12 17:35:25 +08:00
|
|
|
app_name = crate::get_app_name(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-03-02 21:40:03 +08:00
|
|
|
pub fn uninstall_me(kill_self: bool) -> ResultType<()> {
|
|
|
|
run_cmds(get_uninstall(kill_self), true, "uninstall")
|
2022-05-12 17:35:25 +08:00
|
|
|
}
|
|
|
|
|
2022-06-27 00:36:31 +08:00
|
|
|
fn write_cmds(cmds: String, ext: &str, tip: &str) -> ResultType<std::path::PathBuf> {
|
2023-06-05 20:27:48 +08:00
|
|
|
let mut cmds = cmds;
|
2022-05-12 17:35:25 +08:00
|
|
|
let mut tmp = std::env::temp_dir();
|
2023-02-27 19:22:52 +08:00
|
|
|
// When dir contains these characters, the bat file will not execute in elevated mode.
|
|
|
|
if vec!["&", "@", "^"]
|
|
|
|
.drain(..)
|
|
|
|
.any(|s| tmp.to_string_lossy().to_string().contains(s))
|
|
|
|
{
|
|
|
|
if let Ok(dir) = user_accessible_folder() {
|
|
|
|
tmp = dir;
|
|
|
|
}
|
|
|
|
}
|
2022-06-27 00:36:31 +08:00
|
|
|
tmp.push(format!("{}_{}.{}", crate::get_app_name(), tip, ext));
|
2022-05-12 17:35:25 +08:00
|
|
|
let mut file = std::fs::File::create(&tmp)?;
|
2023-06-05 20:27:48 +08:00
|
|
|
if ext == "bat" {
|
2023-07-22 14:16:41 +08:00
|
|
|
let tmp2 = get_undone_file(&tmp)?;
|
2023-06-05 20:27:48 +08:00
|
|
|
std::fs::File::create(&tmp2).ok();
|
|
|
|
cmds = format!(
|
|
|
|
"
|
|
|
|
{cmds}
|
|
|
|
if exist \"{path}\" del /f /q \"{path}\"
|
|
|
|
",
|
|
|
|
path = tmp2.to_string_lossy()
|
|
|
|
);
|
|
|
|
}
|
2022-05-12 17:35:25 +08:00
|
|
|
// in case cmds mixed with \r\n and \n, make sure all ending with \r\n
|
|
|
|
// in some windows, \r\n required for cmd file to run
|
2023-06-05 20:27:48 +08:00
|
|
|
cmds = cmds.replace("\r\n", "\n").replace("\n", "\r\n");
|
2022-05-12 17:35:25 +08:00
|
|
|
if ext == "vbs" {
|
|
|
|
let mut v: Vec<u16> = cmds.encode_utf16().collect();
|
|
|
|
// utf8 -> utf16le which vbs support it only
|
|
|
|
file.write_all(to_le(&mut v))?;
|
|
|
|
} else {
|
|
|
|
file.write_all(cmds.as_bytes())?;
|
|
|
|
}
|
|
|
|
file.sync_all()?;
|
|
|
|
return Ok(tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn to_le(v: &mut [u16]) -> &[u8] {
|
|
|
|
for b in v.iter_mut() {
|
|
|
|
*b = b.to_le()
|
|
|
|
}
|
|
|
|
unsafe { v.align_to().1 }
|
|
|
|
}
|
|
|
|
|
2023-07-22 14:16:41 +08:00
|
|
|
fn get_undone_file(tmp: &PathBuf) -> ResultType<PathBuf> {
|
2023-06-05 20:27:48 +08:00
|
|
|
let mut tmp1 = tmp.clone();
|
|
|
|
tmp1.set_file_name(format!(
|
|
|
|
"{}.undone",
|
2023-07-22 14:16:41 +08:00
|
|
|
tmp.file_name()
|
|
|
|
.ok_or(anyhow!("Failed to get filename of {:?}", tmp))?
|
|
|
|
.to_string_lossy()
|
2023-06-05 20:27:48 +08:00
|
|
|
));
|
2023-07-22 14:16:41 +08:00
|
|
|
Ok(tmp1)
|
2023-06-05 20:27:48 +08:00
|
|
|
}
|
|
|
|
|
2022-06-27 00:36:31 +08:00
|
|
|
fn run_cmds(cmds: String, show: bool, tip: &str) -> ResultType<()> {
|
|
|
|
let tmp = write_cmds(cmds, "bat", tip)?;
|
2023-07-22 14:16:41 +08:00
|
|
|
let tmp2 = get_undone_file(&tmp)?;
|
2022-06-02 17:14:32 +08:00
|
|
|
let tmp_fn = tmp.to_str().unwrap_or("");
|
2024-01-06 20:28:46 +08:00
|
|
|
// https://github.com/rustdesk/rustdesk/issues/6786#issuecomment-1879655410
|
2024-01-06 20:56:40 +08:00
|
|
|
// Specify cmd.exe explicitly to avoid the replacement of cmd commands.
|
|
|
|
let res = runas::Command::new("cmd.exe")
|
|
|
|
.args(&["/C", &tmp_fn])
|
2022-05-12 17:35:25 +08:00
|
|
|
.show(show)
|
|
|
|
.force_prompt(true)
|
|
|
|
.status();
|
2022-07-06 01:29:11 +08:00
|
|
|
if !show {
|
|
|
|
allow_err!(std::fs::remove_file(tmp));
|
2022-06-02 17:14:32 +08:00
|
|
|
}
|
2022-05-12 17:35:25 +08:00
|
|
|
let _ = res?;
|
2023-06-05 20:27:48 +08:00
|
|
|
if tmp2.exists() {
|
|
|
|
allow_err!(std::fs::remove_file(tmp2));
|
|
|
|
bail!("{} failed", tip);
|
|
|
|
}
|
2022-05-12 17:35:25 +08:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn toggle_blank_screen(v: bool) {
|
|
|
|
let v = if v { TRUE } else { FALSE };
|
|
|
|
unsafe {
|
|
|
|
blank_screen(v);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-17 10:53:04 +08:00
|
|
|
pub fn block_input(v: bool) -> (bool, String) {
|
2022-05-12 17:35:25 +08:00
|
|
|
let v = if v { TRUE } else { FALSE };
|
2023-09-17 10:53:04 +08:00
|
|
|
unsafe {
|
|
|
|
if BlockInput(v) == TRUE {
|
|
|
|
(true, "".to_owned())
|
|
|
|
} else {
|
2023-11-20 17:38:53 +08:00
|
|
|
(false, format!("Error: {}", io::Error::last_os_error()))
|
2023-09-17 10:53:04 +08:00
|
|
|
}
|
|
|
|
}
|
2022-05-12 17:35:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add_recent_document(path: &str) {
|
|
|
|
extern "C" {
|
|
|
|
fn AddRecentDocument(path: *const u16);
|
|
|
|
}
|
|
|
|
use std::os::windows::ffi::OsStrExt;
|
|
|
|
let wstr: Vec<u16> = std::ffi::OsStr::new(path)
|
|
|
|
.encode_wide()
|
|
|
|
.chain(Some(0).into_iter())
|
|
|
|
.collect();
|
|
|
|
let wstr = wstr.as_ptr();
|
|
|
|
unsafe {
|
|
|
|
AddRecentDocument(wstr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_installed() -> bool {
|
2023-06-06 01:51:04 +08:00
|
|
|
let (_, _, _, exe) = get_install_info();
|
|
|
|
std::fs::metadata(exe).is_ok()
|
2022-05-12 17:35:25 +08:00
|
|
|
}
|
|
|
|
|
2023-06-05 20:27:48 +08:00
|
|
|
pub fn get_reg(name: &str) -> String {
|
|
|
|
let (subkey, _, _, _) = get_install_info();
|
2022-05-12 17:35:25 +08:00
|
|
|
get_reg_of(&subkey, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_reg_of(subkey: &str, name: &str) -> String {
|
|
|
|
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
|
|
|
if let Ok(tmp) = hklm.open_subkey(subkey.replace("HKEY_LOCAL_MACHINE\\", "")) {
|
|
|
|
if let Ok(v) = tmp.get_value(name) {
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"".to_owned()
|
|
|
|
}
|
|
|
|
|
2024-02-26 19:01:42 +08:00
|
|
|
pub fn get_license_from_exe_name() -> ResultType<CustomServer> {
|
2022-10-31 14:59:57 +08:00
|
|
|
let mut exe = std::env::current_exe()?.to_str().unwrap_or("").to_owned();
|
|
|
|
// if defined portable appname entry, replace original executable name with it.
|
|
|
|
if let Ok(portable_exe) = std::env::var(PORTABLE_APPNAME_RUNTIME_ENV_KEY) {
|
|
|
|
exe = portable_exe;
|
|
|
|
}
|
2024-02-26 19:01:42 +08:00
|
|
|
get_custom_server_from_string(&exe)
|
2022-05-12 17:35:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn is_win_server() -> bool {
|
|
|
|
unsafe { is_windows_server() > 0 }
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn bootstrap() {
|
|
|
|
if let Ok(lic) = get_license_from_exe_name() {
|
2023-07-19 14:45:11 +08:00
|
|
|
*config::EXE_RENDEZVOUS_SERVER.write().unwrap() = lic.host.clone();
|
2022-05-12 17:35:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn create_shortcut(id: &str) -> ResultType<()> {
|
|
|
|
let exe = std::env::current_exe()?.to_str().unwrap_or("").to_owned();
|
|
|
|
let shortcut = write_cmds(
|
|
|
|
format!(
|
|
|
|
"
|
|
|
|
Set oWS = WScript.CreateObject(\"WScript.Shell\")
|
|
|
|
strDesktop = oWS.SpecialFolders(\"Desktop\")
|
|
|
|
Set objFSO = CreateObject(\"Scripting.FileSystemObject\")
|
|
|
|
sLinkFile = objFSO.BuildPath(strDesktop, \"{id}.lnk\")
|
|
|
|
Set oLink = oWS.CreateShortcut(sLinkFile)
|
|
|
|
oLink.TargetPath = \"{exe}\"
|
|
|
|
oLink.Arguments = \"--connect {id}\"
|
|
|
|
oLink.Save
|
2023-06-05 20:27:48 +08:00
|
|
|
"
|
2022-05-12 17:35:25 +08:00
|
|
|
),
|
|
|
|
"vbs",
|
2022-06-27 00:36:31 +08:00
|
|
|
"connect_shortcut",
|
2022-05-12 17:35:25 +08:00
|
|
|
)?
|
|
|
|
.to_str()
|
|
|
|
.unwrap_or("")
|
|
|
|
.to_owned();
|
|
|
|
std::process::Command::new("cscript")
|
|
|
|
.arg(&shortcut)
|
|
|
|
.output()?;
|
|
|
|
allow_err!(std::fs::remove_file(shortcut));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn enable_lowlevel_keyboard(hwnd: HWND) {
|
|
|
|
let ret = unsafe { win32_enable_lowlevel_keyboard(hwnd) };
|
|
|
|
if ret != 0 {
|
|
|
|
log::error!("Failure grabbing keyboard");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn disable_lowlevel_keyboard(hwnd: HWND) {
|
|
|
|
unsafe { win32_disable_lowlevel_keyboard(hwnd) };
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn stop_system_key_propagate(v: bool) {
|
|
|
|
unsafe { win_stop_system_key_propagate(if v { TRUE } else { FALSE }) };
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_win_key_state() -> bool {
|
|
|
|
unsafe { is_win_down() == TRUE }
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn quit_gui() {
|
2022-05-18 16:12:50 +08:00
|
|
|
std::process::exit(0);
|
|
|
|
// unsafe { PostQuitMessage(0) }; // some how not work
|
2022-05-13 17:56:36 +08:00
|
|
|
}
|
2022-04-25 12:28:28 +08:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-08-19 15:44:19 +08:00
|
|
|
|
2022-11-13 18:38:24 +08:00
|
|
|
pub fn run_background(exe: &str, arg: &str) -> ResultType<bool> {
|
|
|
|
let wexe = wide_string(exe);
|
|
|
|
let warg;
|
|
|
|
unsafe {
|
|
|
|
let ret = ShellExecuteW(
|
|
|
|
NULL as _,
|
|
|
|
NULL as _,
|
|
|
|
wexe.as_ptr() as _,
|
|
|
|
if arg.is_empty() {
|
|
|
|
NULL as _
|
|
|
|
} else {
|
|
|
|
warg = wide_string(arg);
|
|
|
|
warg.as_ptr() as _
|
|
|
|
},
|
|
|
|
NULL as _,
|
|
|
|
SW_HIDE,
|
|
|
|
);
|
|
|
|
return Ok(ret as i32 > 32);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-26 16:23:09 +08:00
|
|
|
pub fn run_uac(exe: &str, arg: &str) -> ResultType<bool> {
|
2022-10-31 16:08:51 +08:00
|
|
|
let wop = wide_string("runas");
|
|
|
|
let wexe = wide_string(exe);
|
|
|
|
let warg;
|
2022-08-19 15:44:19 +08:00
|
|
|
unsafe {
|
2022-10-31 16:08:51 +08:00
|
|
|
let ret = ShellExecuteW(
|
2022-08-19 15:44:19 +08:00
|
|
|
NULL as _,
|
2022-10-31 16:08:51 +08:00
|
|
|
wop.as_ptr() as _,
|
|
|
|
wexe.as_ptr() as _,
|
2022-09-26 16:23:09 +08:00
|
|
|
if arg.is_empty() {
|
|
|
|
NULL as _
|
|
|
|
} else {
|
2022-10-31 16:08:51 +08:00
|
|
|
warg = wide_string(arg);
|
|
|
|
warg.as_ptr() as _
|
2022-09-26 16:23:09 +08:00
|
|
|
},
|
2022-08-19 15:44:19 +08:00
|
|
|
NULL as _,
|
|
|
|
SW_SHOWNORMAL,
|
|
|
|
);
|
|
|
|
return Ok(ret as i32 > 32);
|
|
|
|
}
|
|
|
|
}
|
2022-09-26 16:23:09 +08:00
|
|
|
|
|
|
|
pub fn check_super_user_permission() -> ResultType<bool> {
|
2022-10-22 16:56:21 +08:00
|
|
|
run_uac(
|
|
|
|
std::env::current_exe()?
|
|
|
|
.to_string_lossy()
|
|
|
|
.to_string()
|
|
|
|
.as_str(),
|
|
|
|
"--version",
|
|
|
|
)
|
2022-09-26 16:23:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn elevate(arg: &str) -> ResultType<bool> {
|
|
|
|
run_uac(
|
|
|
|
std::env::current_exe()?
|
|
|
|
.to_string_lossy()
|
|
|
|
.to_string()
|
|
|
|
.as_str(),
|
|
|
|
arg,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn run_as_system(arg: &str) -> ResultType<()> {
|
|
|
|
let exe = std::env::current_exe()?.to_string_lossy().to_string();
|
|
|
|
if impersonate_system::run_as_system(&exe, arg).is_err() {
|
|
|
|
bail!(format!("Failed to run {} as system", exe));
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-09-29 09:00:04 +08:00
|
|
|
pub fn elevate_or_run_as_system(is_setup: bool, is_elevate: bool, is_run_as_system: bool) {
|
2022-10-31 16:08:51 +08:00
|
|
|
// avoid possible run recursively due to failed run.
|
2022-11-16 20:32:22 +08:00
|
|
|
log::info!(
|
2023-11-18 09:47:08 +08:00
|
|
|
"elevate: {} -> {:?}, run_as_system: {} -> {}",
|
2022-11-16 20:32:22 +08:00
|
|
|
is_elevate,
|
|
|
|
is_elevated(None),
|
|
|
|
is_run_as_system,
|
|
|
|
crate::username(),
|
|
|
|
);
|
2022-09-29 09:00:04 +08:00
|
|
|
let arg_elevate = if is_setup {
|
|
|
|
"--noinstall --elevate"
|
|
|
|
} else {
|
|
|
|
"--elevate"
|
|
|
|
};
|
|
|
|
let arg_run_as_system = if is_setup {
|
|
|
|
"--noinstall --run-as-system"
|
2022-09-26 16:23:09 +08:00
|
|
|
} else {
|
2022-09-29 09:00:04 +08:00
|
|
|
"--run-as-system"
|
|
|
|
};
|
2022-10-31 16:08:51 +08:00
|
|
|
if is_root() {
|
2022-11-10 10:27:13 +08:00
|
|
|
if is_run_as_system {
|
|
|
|
log::info!("run portable service");
|
|
|
|
crate::portable_service::server::run_portable_service();
|
|
|
|
}
|
2022-09-29 09:00:04 +08:00
|
|
|
} else {
|
2022-10-31 16:08:51 +08:00
|
|
|
match is_elevated(None) {
|
|
|
|
Ok(elevated) => {
|
|
|
|
if elevated {
|
|
|
|
if !is_run_as_system {
|
|
|
|
if run_as_system(arg_run_as_system).is_ok() {
|
|
|
|
std::process::exit(0);
|
|
|
|
} else {
|
2023-11-20 17:15:15 +08:00
|
|
|
log::error!(
|
2023-11-20 17:38:53 +08:00
|
|
|
"Failed to run as system, error {}",
|
2023-11-20 17:15:15 +08:00
|
|
|
io::Error::last_os_error()
|
|
|
|
);
|
2022-10-31 16:08:51 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if !is_elevate {
|
|
|
|
if let Ok(true) = elevate(arg_elevate) {
|
|
|
|
std::process::exit(0);
|
|
|
|
} else {
|
2023-11-20 17:38:53 +08:00
|
|
|
log::error!("Failed to elevate, error {}", io::Error::last_os_error());
|
2022-10-31 16:08:51 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-09-29 09:00:04 +08:00
|
|
|
}
|
2023-11-20 17:15:15 +08:00
|
|
|
Err(_) => log::error!(
|
2023-11-20 17:38:53 +08:00
|
|
|
"Failed to get elevation status, error {}",
|
2023-11-20 17:15:15 +08:00
|
|
|
io::Error::last_os_error()
|
|
|
|
),
|
2022-09-29 09:00:04 +08:00
|
|
|
}
|
2022-09-26 16:23:09 +08:00
|
|
|
}
|
|
|
|
}
|
2022-09-28 20:48:14 +08:00
|
|
|
|
|
|
|
pub fn is_elevated(process_id: Option<DWORD>) -> ResultType<bool> {
|
2023-07-20 21:16:38 +08:00
|
|
|
use hbb_common::platform::windows::RAIIHandle;
|
2022-09-28 20:48:14 +08:00
|
|
|
unsafe {
|
|
|
|
let handle: HANDLE = match process_id {
|
|
|
|
Some(process_id) => OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id),
|
|
|
|
None => GetCurrentProcess(),
|
|
|
|
};
|
|
|
|
if handle == NULL {
|
2023-11-20 17:15:15 +08:00
|
|
|
bail!(
|
2023-11-20 17:38:53 +08:00
|
|
|
"Failed to open process, error {}",
|
2023-11-20 17:15:15 +08:00
|
|
|
io::Error::last_os_error()
|
|
|
|
)
|
2022-09-28 20:48:14 +08:00
|
|
|
}
|
|
|
|
let _handle = RAIIHandle(handle);
|
|
|
|
let mut token: HANDLE = mem::zeroed();
|
|
|
|
if OpenProcessToken(handle, TOKEN_QUERY, &mut token) == FALSE {
|
2023-11-20 17:15:15 +08:00
|
|
|
bail!(
|
2023-11-20 17:38:53 +08:00
|
|
|
"Failed to open process token, error {}",
|
2023-11-20 17:15:15 +08:00
|
|
|
io::Error::last_os_error()
|
|
|
|
)
|
2022-09-28 20:48:14 +08:00
|
|
|
}
|
|
|
|
let _token = RAIIHandle(token);
|
|
|
|
let mut token_elevation: TOKEN_ELEVATION = mem::zeroed();
|
|
|
|
let mut size: DWORD = 0;
|
|
|
|
if GetTokenInformation(
|
|
|
|
token,
|
|
|
|
TokenElevation,
|
|
|
|
(&mut token_elevation) as *mut _ as *mut c_void,
|
|
|
|
mem::size_of::<TOKEN_ELEVATION>() as _,
|
|
|
|
&mut size,
|
|
|
|
) == FALSE
|
|
|
|
{
|
2023-11-20 17:15:15 +08:00
|
|
|
bail!(
|
2023-11-20 17:38:53 +08:00
|
|
|
"Failed to get token information, error {}",
|
2023-11-20 17:15:15 +08:00
|
|
|
io::Error::last_os_error()
|
|
|
|
)
|
2022-09-28 20:48:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(token_elevation.TokenIsElevated != 0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_foreground_window_elevated() -> ResultType<bool> {
|
|
|
|
unsafe {
|
|
|
|
let mut process_id: DWORD = 0;
|
|
|
|
GetWindowThreadProcessId(GetForegroundWindow(), &mut process_id);
|
|
|
|
if process_id == 0 {
|
2023-11-20 17:15:15 +08:00
|
|
|
bail!(
|
|
|
|
"Failed to get processId, error {}",
|
|
|
|
io::Error::last_os_error()
|
|
|
|
)
|
2022-09-28 20:48:14 +08:00
|
|
|
}
|
2023-09-30 19:47:59 +08:00
|
|
|
is_elevated(Some(process_id))
|
2022-09-28 20:48:14 +08:00
|
|
|
}
|
|
|
|
}
|
2022-10-30 18:45:44 +08:00
|
|
|
|
|
|
|
fn get_current_pid() -> u32 {
|
|
|
|
unsafe { GetCurrentProcessId() }
|
|
|
|
}
|
2022-11-02 11:32:30 +08:00
|
|
|
|
|
|
|
pub fn get_double_click_time() -> u32 {
|
|
|
|
unsafe { GetDoubleClickTime() }
|
|
|
|
}
|
2022-10-31 16:08:51 +08:00
|
|
|
|
2024-03-06 17:57:05 +08:00
|
|
|
pub fn wide_string(s: &str) -> Vec<u16> {
|
2022-10-31 16:08:51 +08:00
|
|
|
use std::os::windows::prelude::OsStrExt;
|
|
|
|
std::ffi::OsStr::new(s)
|
|
|
|
.encode_wide()
|
|
|
|
.chain(Some(0).into_iter())
|
|
|
|
.collect()
|
|
|
|
}
|
2022-11-14 19:48:42 +08:00
|
|
|
|
|
|
|
/// send message to currently shown window
|
|
|
|
pub fn send_message_to_hnwd(
|
|
|
|
class_name: &str,
|
|
|
|
window_name: &str,
|
|
|
|
dw_data: usize,
|
|
|
|
data: &str,
|
|
|
|
show_window: bool,
|
|
|
|
) -> bool {
|
|
|
|
unsafe {
|
|
|
|
let class_name_utf16 = wide_string(class_name);
|
|
|
|
let window_name_utf16 = wide_string(window_name);
|
|
|
|
let window = FindWindowW(class_name_utf16.as_ptr(), window_name_utf16.as_ptr());
|
|
|
|
if window.is_null() {
|
|
|
|
log::warn!("no such window {}:{}", class_name, window_name);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
let mut data_struct = COPYDATASTRUCT::default();
|
|
|
|
data_struct.dwData = dw_data;
|
|
|
|
let mut data_zero: String = data.chars().chain(Some('\0').into_iter()).collect();
|
|
|
|
println!("send {:?}", data_zero);
|
|
|
|
data_struct.cbData = data_zero.len() as _;
|
|
|
|
data_struct.lpData = data_zero.as_mut_ptr() as _;
|
|
|
|
SendMessageW(
|
|
|
|
window,
|
|
|
|
WM_COPYDATA,
|
|
|
|
0,
|
|
|
|
&data_struct as *const COPYDATASTRUCT as _,
|
|
|
|
);
|
|
|
|
if show_window {
|
|
|
|
ShowWindow(window, SW_NORMAL);
|
|
|
|
SetForegroundWindow(window);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2023-01-12 21:03:05 +08:00
|
|
|
|
|
|
|
pub fn create_process_with_logon(user: &str, pwd: &str, exe: &str, arg: &str) -> ResultType<()> {
|
2023-03-17 12:36:35 +01:00
|
|
|
let last_error_table = HashMap::from([
|
2023-03-30 10:08:05 +08:00
|
|
|
(
|
|
|
|
ERROR_LOGON_FAILURE,
|
|
|
|
"The user name or password is incorrect.",
|
|
|
|
),
|
|
|
|
(ERROR_ACCESS_DENIED, "Access is denied."),
|
2023-03-17 12:36:35 +01:00
|
|
|
]);
|
|
|
|
|
2023-01-12 21:03:05 +08:00
|
|
|
unsafe {
|
2023-03-17 12:36:35 +01:00
|
|
|
let user_split = user.split("\\").collect::<Vec<&str>>();
|
|
|
|
let wuser = wide_string(user_split.get(1).unwrap_or(&user));
|
2023-03-30 10:08:05 +08:00
|
|
|
let wpc = wide_string(user_split.get(0).unwrap_or(&""));
|
2023-01-12 21:03:05 +08:00
|
|
|
let wpwd = wide_string(pwd);
|
|
|
|
let cmd = if arg.is_empty() {
|
|
|
|
format!("\"{}\"", exe)
|
|
|
|
} else {
|
|
|
|
format!("\"{}\" {}", exe, arg)
|
|
|
|
};
|
|
|
|
let mut wcmd = wide_string(&cmd);
|
|
|
|
let mut si: STARTUPINFOW = mem::zeroed();
|
|
|
|
si.wShowWindow = SW_HIDE as _;
|
|
|
|
si.lpDesktop = NULL as _;
|
|
|
|
si.cb = std::mem::size_of::<STARTUPINFOW>() as _;
|
|
|
|
si.dwFlags = STARTF_USESHOWWINDOW;
|
|
|
|
let mut pi: PROCESS_INFORMATION = mem::zeroed();
|
|
|
|
let wexe = wide_string(exe);
|
|
|
|
if FALSE
|
|
|
|
== CreateProcessWithLogonW(
|
|
|
|
wuser.as_ptr(),
|
|
|
|
wpc.as_ptr(),
|
|
|
|
wpwd.as_ptr(),
|
|
|
|
LOGON_WITH_PROFILE,
|
|
|
|
wexe.as_ptr(),
|
|
|
|
wcmd.as_mut_ptr(),
|
|
|
|
CREATE_UNICODE_ENVIRONMENT,
|
|
|
|
NULL,
|
|
|
|
NULL as _,
|
|
|
|
&mut si as *mut STARTUPINFOW,
|
|
|
|
&mut pi as *mut PROCESS_INFORMATION,
|
|
|
|
)
|
|
|
|
{
|
2023-03-17 12:36:35 +01:00
|
|
|
let last_error = GetLastError();
|
|
|
|
bail!(
|
2023-11-20 17:42:34 +08:00
|
|
|
"CreateProcessWithLogonW failed : \"{}\", error {}",
|
2023-03-17 12:36:35 +01:00
|
|
|
last_error_table
|
|
|
|
.get(&last_error)
|
|
|
|
.unwrap_or(&"Unknown error"),
|
2023-11-20 17:22:14 +08:00
|
|
|
io::Error::from_raw_os_error(last_error as _)
|
2023-03-17 12:36:35 +01:00
|
|
|
);
|
2023-01-12 21:03:05 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return Ok(());
|
|
|
|
}
|
2023-01-29 17:36:37 +08:00
|
|
|
|
|
|
|
pub fn set_path_permission(dir: &PathBuf, permission: &str) -> ResultType<()> {
|
|
|
|
std::process::Command::new("icacls")
|
|
|
|
.arg(dir.as_os_str())
|
|
|
|
.arg("/grant")
|
2023-03-17 12:37:11 +01:00
|
|
|
.arg(format!("*S-1-1-0:(OI)(CI){}", permission))
|
2023-01-29 17:36:37 +08:00
|
|
|
.arg("/T")
|
|
|
|
.spawn()?;
|
|
|
|
Ok(())
|
|
|
|
}
|
2023-02-09 15:53:51 +08:00
|
|
|
|
2023-05-18 21:25:48 +08:00
|
|
|
#[inline]
|
|
|
|
fn str_to_device_name(name: &str) -> [u16; 32] {
|
|
|
|
let mut device_name: Vec<u16> = wide_string(name);
|
|
|
|
if device_name.len() < 32 {
|
|
|
|
device_name.resize(32, 0);
|
|
|
|
}
|
|
|
|
let mut result = [0; 32];
|
|
|
|
result.copy_from_slice(&device_name[..32]);
|
|
|
|
result
|
|
|
|
}
|
|
|
|
|
2023-02-09 15:53:51 +08:00
|
|
|
pub fn resolutions(name: &str) -> Vec<Resolution> {
|
|
|
|
unsafe {
|
|
|
|
let mut dm: DEVMODEW = std::mem::zeroed();
|
|
|
|
let mut v = vec![];
|
|
|
|
let mut num = 0;
|
2023-05-18 21:25:48 +08:00
|
|
|
let device_name = str_to_device_name(name);
|
2023-02-09 15:53:51 +08:00
|
|
|
loop {
|
2023-05-18 21:25:48 +08:00
|
|
|
if EnumDisplaySettingsW(device_name.as_ptr(), num, &mut dm) == 0 {
|
2023-02-09 15:53:51 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
let r = Resolution {
|
|
|
|
width: dm.dmPelsWidth as _,
|
|
|
|
height: dm.dmPelsHeight as _,
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
if !v.contains(&r) {
|
|
|
|
v.push(r);
|
|
|
|
}
|
|
|
|
num += 1;
|
|
|
|
}
|
|
|
|
v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn current_resolution(name: &str) -> ResultType<Resolution> {
|
2023-05-24 22:50:53 +08:00
|
|
|
let device_name = str_to_device_name(name);
|
2023-02-09 15:53:51 +08:00
|
|
|
unsafe {
|
|
|
|
let mut dm: DEVMODEW = std::mem::zeroed();
|
|
|
|
dm.dmSize = std::mem::size_of::<DEVMODEW>() as _;
|
2023-05-18 21:25:48 +08:00
|
|
|
if EnumDisplaySettingsW(device_name.as_ptr(), ENUM_CURRENT_SETTINGS, &mut dm) == 0 {
|
2023-02-09 15:53:51 +08:00
|
|
|
bail!(
|
2024-04-18 14:39:38 +08:00
|
|
|
"failed to get current resolution, error {}",
|
2023-11-20 17:15:15 +08:00
|
|
|
io::Error::last_os_error()
|
2023-02-09 15:53:51 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
let r = Resolution {
|
|
|
|
width: dm.dmPelsWidth as _,
|
|
|
|
height: dm.dmPelsHeight as _,
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
Ok(r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-05 15:17:05 +08:00
|
|
|
pub(super) fn change_resolution_directly(
|
|
|
|
name: &str,
|
|
|
|
width: usize,
|
|
|
|
height: usize,
|
|
|
|
) -> ResultType<()> {
|
2023-05-18 21:25:48 +08:00
|
|
|
let device_name = str_to_device_name(name);
|
2023-02-09 15:53:51 +08:00
|
|
|
unsafe {
|
|
|
|
let mut dm: DEVMODEW = std::mem::zeroed();
|
2023-06-05 18:34:29 +08:00
|
|
|
dm.dmSize = std::mem::size_of::<DEVMODEW>() as _;
|
2023-02-09 15:53:51 +08:00
|
|
|
dm.dmPelsWidth = width as _;
|
|
|
|
dm.dmPelsHeight = height as _;
|
|
|
|
dm.dmFields = DM_PELSHEIGHT | DM_PELSWIDTH;
|
|
|
|
let res = ChangeDisplaySettingsExW(
|
2023-05-18 21:25:48 +08:00
|
|
|
device_name.as_ptr(),
|
2023-02-09 15:53:51 +08:00
|
|
|
&mut dm,
|
|
|
|
NULL as _,
|
|
|
|
CDS_UPDATEREGISTRY | CDS_GLOBAL | CDS_RESET,
|
|
|
|
NULL,
|
|
|
|
);
|
|
|
|
if res != DISP_CHANGE_SUCCESSFUL {
|
|
|
|
bail!(
|
2023-11-20 17:38:53 +08:00
|
|
|
"ChangeDisplaySettingsExW failed, res={}, error {}",
|
2023-02-09 15:53:51 +08:00
|
|
|
res,
|
2023-11-20 17:15:15 +08:00
|
|
|
io::Error::last_os_error()
|
2023-02-09 15:53:51 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2023-02-27 19:22:52 +08:00
|
|
|
|
|
|
|
pub fn user_accessible_folder() -> ResultType<PathBuf> {
|
|
|
|
let disk = std::env::var("SystemDrive").unwrap_or("C:".to_string());
|
|
|
|
let dir1 = PathBuf::from(format!("{}\\ProgramData", disk));
|
|
|
|
// NOTICE: "C:\Windows\Temp" requires permanent authorization.
|
|
|
|
let dir2 = PathBuf::from(format!("{}\\Windows\\Temp", disk));
|
|
|
|
let dir;
|
|
|
|
if dir1.exists() {
|
|
|
|
dir = dir1;
|
|
|
|
} else if dir2.exists() {
|
|
|
|
dir = dir2;
|
|
|
|
} else {
|
|
|
|
bail!("no vaild user accessible folder");
|
|
|
|
}
|
|
|
|
Ok(dir)
|
|
|
|
}
|
2023-03-07 19:10:37 +08:00
|
|
|
|
2023-06-05 15:17:05 +08:00
|
|
|
#[inline]
|
|
|
|
pub fn uninstall_cert() -> ResultType<()> {
|
|
|
|
cert::uninstall_cert()
|
|
|
|
}
|
|
|
|
|
2023-03-07 19:10:37 +08:00
|
|
|
mod cert {
|
2024-04-06 16:22:59 +08:00
|
|
|
use hbb_common::ResultType;
|
2023-03-07 19:10:37 +08:00
|
|
|
|
2024-04-06 16:22:59 +08:00
|
|
|
extern "C" {
|
|
|
|
fn DeleteRustDeskTestCertsW();
|
2023-03-07 19:10:37 +08:00
|
|
|
}
|
2023-06-05 15:17:05 +08:00
|
|
|
pub fn uninstall_cert() -> ResultType<()> {
|
2024-04-06 16:22:59 +08:00
|
|
|
unsafe {
|
|
|
|
DeleteRustDeskTestCertsW();
|
2023-03-07 19:10:37 +08:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-01 18:09:53 +08:00
|
|
|
#[inline]
|
|
|
|
pub fn get_char_from_vk(vk: u32) -> Option<char> {
|
|
|
|
get_char_from_unicode(get_unicode_from_vk(vk)?)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_char_from_unicode(unicode: u16) -> Option<char> {
|
|
|
|
let buff = [unicode];
|
|
|
|
if let Some(chr) = String::from_utf16(&buff[..1]).ok()?.chars().next() {
|
|
|
|
if chr.is_control() {
|
|
|
|
return None;
|
|
|
|
} else {
|
|
|
|
Some(chr)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_unicode_from_vk(vk: u32) -> Option<u16> {
|
2023-03-20 10:13:06 +08:00
|
|
|
const BUF_LEN: i32 = 32;
|
2023-03-18 20:57:19 -07:00
|
|
|
let mut buff = [0_u16; BUF_LEN as usize];
|
|
|
|
let buff_ptr = buff.as_mut_ptr();
|
|
|
|
let len = unsafe {
|
|
|
|
let current_window_thread_id = GetWindowThreadProcessId(GetForegroundWindow(), null_mut());
|
|
|
|
let layout = GetKeyboardLayout(current_window_thread_id);
|
|
|
|
|
|
|
|
// refs: https://github.com/fufesou/rdev/blob/25a99ce71ab42843ad253dd51e6a35e83e87a8a4/src/windows/keyboard.rs#L115
|
|
|
|
let press_state = 129;
|
|
|
|
let mut state: [BYTE; 256] = [0; 256];
|
|
|
|
let shift_left = rdev::get_modifier(rdev::Key::ShiftLeft);
|
|
|
|
let shift_right = rdev::get_modifier(rdev::Key::ShiftRight);
|
|
|
|
if shift_left {
|
|
|
|
state[VK_LSHIFT as usize] = press_state;
|
|
|
|
}
|
|
|
|
if shift_right {
|
|
|
|
state[VK_RSHIFT as usize] = press_state;
|
|
|
|
}
|
|
|
|
if shift_left || shift_right {
|
|
|
|
state[VK_SHIFT as usize] = press_state;
|
|
|
|
}
|
|
|
|
ToUnicodeEx(vk, 0x00, &state as _, buff_ptr, BUF_LEN, 0, layout)
|
|
|
|
};
|
|
|
|
if len == 1 {
|
2023-04-01 18:09:53 +08:00
|
|
|
Some(buff[0])
|
2023-03-18 20:57:19 -07:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-13 23:00:24 +08:00
|
|
|
pub fn is_process_consent_running() -> ResultType<bool> {
|
|
|
|
let output = std::process::Command::new("cmd")
|
|
|
|
.args(&["/C", "tasklist | findstr consent.exe"])
|
|
|
|
.creation_flags(CREATE_NO_WINDOW)
|
|
|
|
.output()?;
|
|
|
|
Ok(output.status.success() && !output.stdout.is_empty())
|
|
|
|
}
|
2023-12-05 08:32:44 -05:00
|
|
|
pub struct WakeLock(u32);
|
2023-06-05 20:24:16 +08:00
|
|
|
// Failed to compile keepawake-rs on i686
|
|
|
|
impl WakeLock {
|
|
|
|
pub fn new(display: bool, idle: bool, sleep: bool) -> Self {
|
|
|
|
let mut flag = ES_CONTINUOUS;
|
|
|
|
if display {
|
|
|
|
flag |= ES_DISPLAY_REQUIRED;
|
|
|
|
}
|
|
|
|
if idle {
|
|
|
|
flag |= ES_SYSTEM_REQUIRED;
|
|
|
|
}
|
|
|
|
if sleep {
|
|
|
|
flag |= ES_AWAYMODE_REQUIRED;
|
|
|
|
}
|
|
|
|
unsafe { SetThreadExecutionState(flag) };
|
2023-12-05 08:32:44 -05:00
|
|
|
WakeLock(flag)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_display(&mut self, display: bool) -> ResultType<()> {
|
|
|
|
let flag = if display {
|
|
|
|
self.0 | ES_DISPLAY_REQUIRED
|
|
|
|
} else {
|
|
|
|
self.0 & !ES_DISPLAY_REQUIRED
|
|
|
|
};
|
|
|
|
if flag != self.0 {
|
|
|
|
unsafe { SetThreadExecutionState(flag) };
|
|
|
|
self.0 = flag;
|
|
|
|
}
|
|
|
|
Ok(())
|
2023-06-05 20:24:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for WakeLock {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe { SetThreadExecutionState(ES_CONTINUOUS) };
|
|
|
|
}
|
|
|
|
}
|
2023-04-13 23:00:24 +08:00
|
|
|
|
2024-03-27 00:38:13 +08:00
|
|
|
pub fn uninstall_service(show_new_window: bool, _: bool) -> bool {
|
2023-06-07 14:25:34 +08:00
|
|
|
log::info!("Uninstalling service...");
|
2023-06-05 20:27:48 +08:00
|
|
|
let filter = format!(" /FI \"PID ne {}\"", get_current_pid());
|
|
|
|
Config::set_option("stop-service".into(), "Y".into());
|
|
|
|
let cmds = format!(
|
|
|
|
"
|
|
|
|
chcp 65001
|
|
|
|
sc stop {app_name}
|
|
|
|
sc delete {app_name}
|
|
|
|
if exist \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" del /f /q \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\"
|
2023-07-08 16:01:25 +08:00
|
|
|
taskkill /F /IM {broker_exe}
|
2023-06-05 20:27:48 +08:00
|
|
|
taskkill /F /IM {app_name}.exe{filter}
|
|
|
|
",
|
|
|
|
app_name = crate::get_app_name(),
|
2023-11-19 15:26:02 +08:00
|
|
|
broker_exe = WIN_TOPMOST_INJECTED_PROCESS_EXE,
|
2023-06-05 20:27:48 +08:00
|
|
|
);
|
|
|
|
if let Err(err) = run_cmds(cmds, false, "uninstall") {
|
|
|
|
Config::set_option("stop-service".into(), "".into());
|
2023-06-07 23:08:50 +08:00
|
|
|
log::debug!("{err}");
|
|
|
|
return true;
|
2023-06-05 20:27:48 +08:00
|
|
|
}
|
2023-06-07 14:25:34 +08:00
|
|
|
run_after_run_cmds(!show_new_window);
|
2023-06-10 18:24:03 +08:00
|
|
|
std::process::exit(0);
|
2023-06-05 20:27:48 +08:00
|
|
|
}
|
|
|
|
|
2023-06-07 23:08:50 +08:00
|
|
|
pub fn install_service() -> bool {
|
2023-06-07 14:25:34 +08:00
|
|
|
log::info!("Installing service...");
|
2023-09-11 16:04:51 +08:00
|
|
|
let _installing = crate::platform::InstallingService::new();
|
2023-06-05 20:27:48 +08:00
|
|
|
let (_, _, _, exe) = get_install_info();
|
|
|
|
let tmp_path = std::env::temp_dir().to_string_lossy().to_string();
|
2023-06-07 23:33:05 +08:00
|
|
|
let tray_shortcut = get_tray_shortcut(&exe, &tmp_path).unwrap_or_default();
|
2023-06-05 20:27:48 +08:00
|
|
|
let filter = format!(" /FI \"PID ne {}\"", get_current_pid());
|
|
|
|
Config::set_option("stop-service".into(), "".into());
|
2023-06-07 14:25:34 +08:00
|
|
|
crate::ipc::EXIT_RECV_CLOSE.store(false, Ordering::Relaxed);
|
2023-06-05 20:27:48 +08:00
|
|
|
let cmds = format!(
|
|
|
|
"
|
|
|
|
chcp 65001
|
2023-06-07 14:25:34 +08:00
|
|
|
taskkill /F /IM {app_name}.exe{filter}
|
2023-06-05 20:27:48 +08:00
|
|
|
cscript \"{tray_shortcut}\"
|
|
|
|
copy /Y \"{tmp_path}\\{app_name} Tray.lnk\" \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\\"
|
|
|
|
{import_config}
|
|
|
|
{create_service}
|
|
|
|
if exist \"{tray_shortcut}\" del /f /q \"{tray_shortcut}\"
|
|
|
|
",
|
|
|
|
app_name = crate::get_app_name(),
|
|
|
|
import_config = get_import_config(&exe),
|
|
|
|
create_service = get_create_service(&exe),
|
|
|
|
);
|
|
|
|
if let Err(err) = run_cmds(cmds, false, "install") {
|
|
|
|
Config::set_option("stop-service".into(), "Y".into());
|
2023-06-07 14:25:34 +08:00
|
|
|
crate::ipc::EXIT_RECV_CLOSE.store(true, Ordering::Relaxed);
|
2023-06-07 23:08:50 +08:00
|
|
|
log::debug!("{err}");
|
|
|
|
return true;
|
2023-06-05 20:27:48 +08:00
|
|
|
}
|
2023-06-07 14:25:34 +08:00
|
|
|
run_after_run_cmds(false);
|
2023-06-05 20:27:48 +08:00
|
|
|
std::process::exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_tray_shortcut(exe: &str, tmp_path: &str) -> ResultType<String> {
|
|
|
|
Ok(write_cmds(
|
|
|
|
format!(
|
|
|
|
"
|
|
|
|
Set oWS = WScript.CreateObject(\"WScript.Shell\")
|
|
|
|
sLinkFile = \"{tmp_path}\\{app_name} Tray.lnk\"
|
|
|
|
|
|
|
|
Set oLink = oWS.CreateShortcut(sLinkFile)
|
|
|
|
oLink.TargetPath = \"{exe}\"
|
|
|
|
oLink.Arguments = \"--tray\"
|
|
|
|
oLink.Save
|
|
|
|
",
|
|
|
|
app_name = crate::get_app_name(),
|
|
|
|
),
|
|
|
|
"vbs",
|
|
|
|
"tray_shortcut",
|
|
|
|
)?
|
|
|
|
.to_str()
|
|
|
|
.unwrap_or("")
|
|
|
|
.to_owned())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_import_config(exe: &str) -> String {
|
2024-03-12 21:47:29 +08:00
|
|
|
if config::is_outgoing_only() {
|
|
|
|
return "".to_string();
|
|
|
|
}
|
2023-06-05 20:27:48 +08:00
|
|
|
format!("
|
2023-06-07 14:25:34 +08:00
|
|
|
sc stop {app_name}
|
|
|
|
sc delete {app_name}
|
2023-06-05 20:27:48 +08:00
|
|
|
sc create {app_name} binpath= \"\\\"{exe}\\\" --import-config \\\"{config_path}\\\"\" start= auto DisplayName= \"{app_name} Service\"
|
|
|
|
sc start {app_name}
|
|
|
|
sc stop {app_name}
|
|
|
|
sc delete {app_name}
|
|
|
|
",
|
|
|
|
app_name = crate::get_app_name(),
|
|
|
|
config_path=Config::file().to_str().unwrap_or(""),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_create_service(exe: &str) -> String {
|
2024-03-12 21:47:29 +08:00
|
|
|
if config::is_outgoing_only() {
|
|
|
|
return "".to_string();
|
|
|
|
}
|
2023-06-05 20:27:48 +08:00
|
|
|
let stop = Config::get_option("stop-service") == "Y";
|
|
|
|
if stop {
|
|
|
|
format!("
|
|
|
|
if exist \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" del /f /q \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\"
|
|
|
|
", app_name = crate::get_app_name())
|
|
|
|
} else {
|
|
|
|
format!("
|
|
|
|
sc create {app_name} binpath= \"\\\"{exe}\\\" --service\" start= auto DisplayName= \"{app_name} Service\"
|
|
|
|
sc start {app_name}
|
|
|
|
",
|
|
|
|
app_name = crate::get_app_name())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-07 14:25:34 +08:00
|
|
|
fn run_after_run_cmds(silent: bool) {
|
2023-06-05 20:27:48 +08:00
|
|
|
let (_, _, _, exe) = get_install_info();
|
2024-02-25 13:29:06 +08:00
|
|
|
let app = crate::get_app_name().to_lowercase();
|
2023-06-05 20:27:48 +08:00
|
|
|
if !silent {
|
2023-06-07 14:25:34 +08:00
|
|
|
log::debug!("Spawn new window");
|
|
|
|
allow_err!(std::process::Command::new("cmd")
|
|
|
|
.arg("/c")
|
2024-02-27 15:42:35 +08:00
|
|
|
.arg(format!("timeout /t 2 & start {app}://"))
|
2023-06-07 14:25:34 +08:00
|
|
|
.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW)
|
|
|
|
.spawn());
|
2023-06-05 20:27:48 +08:00
|
|
|
}
|
|
|
|
if Config::get_option("stop-service") != "Y" {
|
2023-06-07 14:25:34 +08:00
|
|
|
allow_err!(std::process::Command::new(&exe).arg("--tray").spawn());
|
2023-06-05 20:27:48 +08:00
|
|
|
}
|
2023-06-07 23:33:05 +08:00
|
|
|
std::thread::sleep(std::time::Duration::from_millis(300));
|
2023-06-05 20:27:48 +08:00
|
|
|
}
|
|
|
|
|
2023-07-08 15:26:24 +08:00
|
|
|
#[inline]
|
|
|
|
pub fn try_kill_broker() {
|
2023-07-11 12:19:28 +08:00
|
|
|
allow_err!(std::process::Command::new("cmd")
|
|
|
|
.arg("/c")
|
2023-11-19 15:26:02 +08:00
|
|
|
.arg(&format!(
|
|
|
|
"taskkill /F /IM {}",
|
|
|
|
WIN_TOPMOST_INJECTED_PROCESS_EXE
|
|
|
|
))
|
2023-07-11 12:19:28 +08:00
|
|
|
.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW)
|
|
|
|
.spawn());
|
2023-07-08 15:26:24 +08:00
|
|
|
}
|
|
|
|
|
2023-03-07 19:10:37 +08:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
|
|
fn test_uninstall_cert() {
|
2023-06-05 15:17:05 +08:00
|
|
|
println!("uninstall driver certs: {:?}", cert::uninstall_cert());
|
2023-03-07 19:10:37 +08:00
|
|
|
}
|
2023-03-18 20:57:19 -07:00
|
|
|
|
|
|
|
#[test]
|
2023-04-01 18:09:53 +08:00
|
|
|
fn test_get_unicode_char_by_vk() {
|
|
|
|
let chr = get_char_from_vk(0x41); // VK_A
|
2023-03-18 20:57:19 -07:00
|
|
|
assert_eq!(chr, Some('a'));
|
2023-04-01 18:09:53 +08:00
|
|
|
let chr = get_char_from_vk(VK_ESCAPE as u32); // VK_ESC
|
2023-03-18 20:57:19 -07:00
|
|
|
assert_eq!(chr, None)
|
|
|
|
}
|
2023-03-07 19:10:37 +08:00
|
|
|
}
|
2023-07-08 12:34:40 +08:00
|
|
|
|
|
|
|
pub fn message_box(text: &str) {
|
|
|
|
let mut text = text.to_owned();
|
2023-07-27 10:46:48 +08:00
|
|
|
let nodialog = std::env::var("NO_DIALOG").unwrap_or_default() == "Y";
|
|
|
|
if !text.ends_with("!") || nodialog {
|
2023-07-08 12:34:40 +08:00
|
|
|
use arboard::Clipboard as ClipboardContext;
|
|
|
|
match ClipboardContext::new() {
|
|
|
|
Ok(mut ctx) => {
|
|
|
|
ctx.set_text(&text).ok();
|
2023-07-27 10:46:48 +08:00
|
|
|
if !nodialog {
|
|
|
|
text = format!("{}\n\nAbove text has been copied to clipboard", &text);
|
|
|
|
}
|
2023-07-08 12:34:40 +08:00
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
2023-07-27 10:46:48 +08:00
|
|
|
if nodialog {
|
|
|
|
if std::env::var("PRINT_OUT").unwrap_or_default() == "Y" {
|
|
|
|
println!("{text}");
|
|
|
|
}
|
|
|
|
if let Ok(x) = std::env::var("WRITE_TO_FILE") {
|
|
|
|
if !x.is_empty() {
|
|
|
|
allow_err!(std::fs::write(x, text));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2023-07-08 12:34:40 +08:00
|
|
|
let text = text
|
|
|
|
.encode_utf16()
|
|
|
|
.chain(std::iter::once(0))
|
|
|
|
.collect::<Vec<u16>>();
|
|
|
|
let caption = "RustDesk Output"
|
|
|
|
.encode_utf16()
|
|
|
|
.chain(std::iter::once(0))
|
|
|
|
.collect::<Vec<u16>>();
|
|
|
|
unsafe { MessageBoxW(std::ptr::null_mut(), text.as_ptr(), caption.as_ptr(), MB_OK) };
|
|
|
|
}
|
2023-07-17 18:52:43 +08:00
|
|
|
|
|
|
|
pub fn alloc_console() {
|
|
|
|
unsafe {
|
|
|
|
alloc_console_and_redirect();
|
|
|
|
}
|
|
|
|
}
|
2023-08-04 11:27:33 +08:00
|
|
|
|
2024-02-26 19:01:42 +08:00
|
|
|
fn get_license() -> Option<CustomServer> {
|
|
|
|
let mut lic: CustomServer = Default::default();
|
2023-08-04 11:27:33 +08:00
|
|
|
if let Ok(tmp) = get_license_from_exe_name() {
|
|
|
|
lic = tmp;
|
|
|
|
} else {
|
|
|
|
// for back compatibility from migrating from <= 1.2.1 to 1.2.2
|
|
|
|
lic.key = get_reg("Key");
|
|
|
|
lic.host = get_reg("Host");
|
|
|
|
lic.api = get_reg("Api");
|
|
|
|
}
|
|
|
|
if lic.key.is_empty() || lic.host.is_empty() {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
Some(lic)
|
|
|
|
}
|
2023-10-11 19:03:34 +08:00
|
|
|
|
|
|
|
fn get_sid_of_user(username: &str) -> ResultType<String> {
|
|
|
|
let mut output = Command::new("wmic")
|
|
|
|
.args(&[
|
|
|
|
"useraccount",
|
|
|
|
"where",
|
|
|
|
&format!("name='{}'", username),
|
|
|
|
"get",
|
|
|
|
"sid",
|
|
|
|
"/value",
|
|
|
|
])
|
|
|
|
.creation_flags(CREATE_NO_WINDOW)
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
.spawn()?
|
|
|
|
.stdout
|
|
|
|
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Failed to open stdout"))?;
|
|
|
|
let mut result = String::new();
|
|
|
|
output.read_to_string(&mut result)?;
|
|
|
|
let sid_start_index = result
|
|
|
|
.find('=')
|
|
|
|
.map(|i| i + 1)
|
|
|
|
.ok_or(anyhow!("bad output format"))?;
|
|
|
|
if sid_start_index > 0 && sid_start_index < result.len() + 1 {
|
|
|
|
Ok(result[sid_start_index..].trim().to_string())
|
|
|
|
} else {
|
|
|
|
bail!("bad output format");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct WallPaperRemover {
|
|
|
|
old_path: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl WallPaperRemover {
|
|
|
|
pub fn new() -> ResultType<Self> {
|
|
|
|
let start = std::time::Instant::now();
|
|
|
|
if !Self::need_remove() {
|
|
|
|
bail!("already solid color");
|
|
|
|
}
|
|
|
|
let old_path = match Self::get_recent_wallpaper() {
|
|
|
|
Ok(old_path) => old_path,
|
|
|
|
Err(e) => {
|
2023-11-18 09:47:08 +08:00
|
|
|
log::info!("Failed to get recent wallpaper: {:?}, use fallback", e);
|
2023-10-11 19:03:34 +08:00
|
|
|
wallpaper::get().map_err(|e| anyhow!(e.to_string()))?
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Self::set_wallpaper(None)?;
|
|
|
|
log::info!(
|
2023-11-18 09:47:08 +08:00
|
|
|
"created wallpaper remover, old_path: {:?}, elapsed: {:?}",
|
2023-10-11 19:03:34 +08:00
|
|
|
old_path,
|
|
|
|
start.elapsed(),
|
|
|
|
);
|
|
|
|
Ok(Self { old_path })
|
|
|
|
}
|
|
|
|
|
2023-10-14 18:50:41 +08:00
|
|
|
pub fn support() -> bool {
|
|
|
|
wallpaper::get().is_ok() || !Self::get_recent_wallpaper().unwrap_or_default().is_empty()
|
|
|
|
}
|
|
|
|
|
2023-10-11 19:03:34 +08:00
|
|
|
fn get_recent_wallpaper() -> ResultType<String> {
|
|
|
|
// SystemParametersInfoW may return %appdata%\Microsoft\Windows\Themes\TranscodedWallpaper, not real path and may not real cache
|
|
|
|
// https://www.makeuseof.com/find-desktop-wallpapers-file-location-windows-11/
|
|
|
|
// https://superuser.com/questions/1218413/write-to-current-users-registry-through-a-different-admin-account
|
|
|
|
let (hkcu, sid) = if is_root() {
|
|
|
|
let username = get_active_username();
|
2024-02-19 10:32:13 +08:00
|
|
|
if username.is_empty() {
|
|
|
|
bail!("failed to get username");
|
|
|
|
}
|
2023-10-11 19:03:34 +08:00
|
|
|
let sid = get_sid_of_user(&username)?;
|
2023-11-18 09:47:08 +08:00
|
|
|
log::info!("username: {username}, sid: {sid}");
|
2023-10-11 19:03:34 +08:00
|
|
|
(RegKey::predef(HKEY_USERS), format!("{}\\", sid))
|
|
|
|
} else {
|
|
|
|
(RegKey::predef(HKEY_CURRENT_USER), "".to_string())
|
|
|
|
};
|
|
|
|
let explorer_key = hkcu.open_subkey_with_flags(
|
|
|
|
&format!(
|
|
|
|
"{}Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Wallpapers",
|
|
|
|
sid
|
|
|
|
),
|
|
|
|
KEY_READ,
|
|
|
|
)?;
|
|
|
|
Ok(explorer_key.get_value("BackgroundHistoryPath0")?)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn need_remove() -> bool {
|
|
|
|
if let Ok(wallpaper) = wallpaper::get() {
|
|
|
|
return !wallpaper.is_empty();
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_wallpaper(path: Option<String>) -> ResultType<()> {
|
|
|
|
wallpaper::set_from_path(&path.unwrap_or_default()).map_err(|e| anyhow!(e.to_string()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for WallPaperRemover {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
// If the old background is a slideshow, it will be converted into an image. AnyDesk does the same.
|
|
|
|
allow_err!(Self::set_wallpaper(Some(self.old_path.clone())));
|
|
|
|
}
|
|
|
|
}
|
2024-04-19 11:31:52 +08:00
|
|
|
|
2024-04-23 17:00:41 +08:00
|
|
|
fn get_uninstall_amyuni_idd() -> String {
|
2024-04-22 10:37:08 +08:00
|
|
|
match std::env::current_exe() {
|
|
|
|
Ok(path) => format!("\"{}\" --uninstall-amyuni-idd", path.to_str().unwrap_or("")),
|
|
|
|
Err(e) => {
|
|
|
|
log::warn!("Failed to get current exe path, cannot get command of uninstalling idd, Zzerror: {:?}", e);
|
|
|
|
"".to_string()
|
|
|
|
}
|
2024-04-19 11:31:52 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn is_self_service_running() -> bool {
|
|
|
|
is_service_running(&crate::get_app_name())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_service_running(service_name: &str) -> bool {
|
|
|
|
unsafe {
|
|
|
|
let service_name = wide_string(service_name);
|
|
|
|
is_service_running_w(service_name.as_ptr() as _)
|
|
|
|
}
|
|
|
|
}
|
2024-04-23 17:00:41 +08:00
|
|
|
|
|
|
|
pub fn is_x64() -> bool {
|
|
|
|
const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9;
|
|
|
|
|
|
|
|
let mut sys_info = SYSTEM_INFO::default();
|
|
|
|
unsafe {
|
|
|
|
GetNativeSystemInfo(&mut sys_info as _);
|
|
|
|
}
|
|
|
|
unsafe { sys_info.u.s().wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 }
|
|
|
|
}
|