use super::{CursorData, ResultType}; use crate::ipc; use crate::license::*; use hbb_common::{ allow_err, bail, config::{self, Config}, log, sleep, timeout, tokio, }; use std::io::prelude::*; use std::{ ffi::OsString, io, mem, sync::{Arc, Mutex}, time::{Duration, Instant}, }; use winapi::{ shared::{minwindef::*, ntdef::NULL, windef::*}, um::{ errhandlingapi::GetLastError, handleapi::CloseHandle, minwinbase::STILL_ACTIVE, processthreadsapi::GetExitCodeProcess, winbase::*, wingdi::*, winnt::HANDLE, winuser::*, }, }; use windows_service::{ define_windows_service, service::{ ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus, ServiceType, }, service_control_handler::{self, ServiceControlHandlerResult}, }; use winreg::enums::*; use winreg::RegKey; pub fn get_cursor_pos() -> Option<(i32, i32)> { unsafe { 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> { unsafe { let mut ci: CURSORINFO = mem::MaybeUninit::uninit().assume_init(); ci.cbSize = std::mem::size_of::() as _; if GetCursorInfo(&mut ci) == FALSE { 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 { unsafe { 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 { 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 = Vec::new(); cbits.resize(cbits_size as _, 0); let mut mbits: Vec = 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, colors: cbits, 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 { unsafe { let mut bm: BITMAP = mem::zeroed(); if GetObjectA( handle as _, std::mem::size_of::() 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 { 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 { 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 { 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, ) -> 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 { bail!("Failed to get di bits: {}", get_error()); } } Ok(()) } fn fix_cursor_mask( mbits: &mut Vec, cbits: &mut Vec, 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) { 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 has_rdp_service() -> BOOL; fn get_current_session(rdp: BOOL) -> DWORD; fn LaunchProcessWin(cmd: *const u16, session_id: DWORD, as_user: BOOL) -> HANDLE; 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; } extern "system" { fn BlockInput(v: BOOL) -> BOOL; } #[tokio::main(flavor = "current_thread")] async fn run_service(_arguments: Vec) -> ResultType<()> { let event_handler = move |control_event| -> ServiceControlHandlerResult { log::info!("Got service control event: {:?}", control_event); match control_event { ServiceControl::Interrogate => ServiceControlHandlerResult::NoError, ServiceControl::Stop => { 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?; loop { 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(); } _ => {} } } } _ => {} }, Err(_) => { // timeout unsafe { let tmp = get_current_session(share_rdp()); if tmp == 0xFFFFFFFF { continue; } let mut close_sent = false; if tmp != session_id { 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 { 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 = 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() { log::error!("Failed to luanch server: {}", get_error()); } Ok(h) } pub fn run_as_user(arg: &str) -> ResultType> { let cmd = format!( "\"{}\" {}", std::env::current_exe()?.to_str().unwrap_or(""), arg, ); let session_id = unsafe { get_current_session(share_rdp()) }; use std::os::windows::ffi::OsStrExt; let wstr: Vec = 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!( "Failed to launch {} with session id {}: {}", arg, session_id, get_error() ); } 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 fn send_sas() { #[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> = 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) { log::error!("Failed to switch desktop: {}", get_error()); *s = Instant::now(); } } else { log::info!("Desktop switched"); } return res; } } return false; } fn get_error() -> String { unsafe { let buff_size = 256; let mut buff: Vec = Vec::with_capacity(buff_size); buff.resize(buff_size, 0); let errno = GetLastError(); let chars_copied = FormatMessageW( FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY, std::ptr::null(), errno, 0, buff.as_mut_ptr(), (buff_size + 1) as u32, std::ptr::null_mut(), ); if chars_copied == 0 { return "".to_owned(); } let mut curr_char: usize = chars_copied as usize; while curr_char > 0 { let ch = buff[curr_char]; if ch >= ' ' as u16 { break; } curr_char -= 1; } let sl = std::slice::from_raw_parts(buff.as_ptr(), curr_char); let err_msg = String::from_utf16(sl); return err_msg.unwrap_or("".to_owned()); } } fn share_rdp() -> BOOL { if get_reg("share_rdp") != "true" { FALSE } else { TRUE } } pub fn is_share_rdp() -> bool { share_rdp() == TRUE } pub fn set_share_rdp(enable: bool) { let (subkey, _, _, _) = get_install_info(); let cmd = format!( "reg add {} /f /v share_rdp /t REG_SZ /d \"{}\"", subkey, if enable { "true" } else { "false" } ); run_cmds(cmd, false).ok(); } pub fn get_active_username() -> String { let name = crate::username(); if name != "SYSTEM" { return name; } extern "C" { fn get_active_user(path: *mut u16, n: u32, rdp: BOOL) -> u32; } let buff_size = 256; let mut buff: Vec = 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() } pub fn is_prelogin() -> bool { let username = get_active_username(); username.is_empty() || username == "SYSTEM" } pub fn is_root() -> bool { crate::username() == "SYSTEM" } 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); } pub fn get_install_info() -> (String, String, String, String) { get_install_info_with_subkey(get_valid_subkey()) } fn get_default_install_info() -> (String, String, String, String) { 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(); if let Ok(x) = std::env::var("ProgramFiles") { if std::path::Path::new(&x).exists() { pf = x; } } #[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()) } fn get_install_info_with_subkey(subkey: String) -> (String, String, String, String) { 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()); (subkey, path, start_menu, exe) } pub fn update_me() -> ResultType<()> { let (_, _, _, exe) = get_install_info(); let src_exe = std::env::current_exe()?.to_str().unwrap_or("").to_owned(); let cmds = format!( " chcp 65001 sc stop {app_name} taskkill /F /IM {app_name}.exe copy /Y \"{src_exe}\" \"{exe}\" sc start {app_name} {lic} ", src_exe = src_exe, exe = exe, app_name = crate::get_app_name(), lic = register_licence(), ); std::thread::sleep(std::time::Duration::from_millis(1000)); run_cmds(cmds, false)?; std::thread::sleep(std::time::Duration::from_millis(2000)); std::process::Command::new(&exe).arg("--tray").spawn().ok(); std::process::Command::new(&exe).spawn().ok(); std::process::Command::new(&exe) .args(&["--remove", &src_exe]) .spawn()?; Ok(()) } fn get_after_install(exe: &str) -> String { let app_name = crate::get_app_name(); let ext = app_name.to_lowercase(); 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\\\"\" sc create {app_name} binpath= \"\\\"{exe}\\\" --service\" start= auto DisplayName= \"{app_name} Service\" netsh advfirewall firewall add rule name=\"{app_name} Service\" dir=in action=allow program=\"{exe}\" enable=yes sc start {app_name} reg add HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System /f /v SoftwareSASGeneration /t REG_DWORD /d 1 ", ext=ext, exe=exe, app_name=app_name) } pub fn install_me(options: &str, path: String) -> ResultType<()> { let uninstall_str = get_uninstall(); let mut path = path.trim_end_matches('\\').to_owned(); let (subkey, _path, start_menu, exe) = get_default_install_info(); 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]; } let tmp_path = "C:\\Windows\\temp"; 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 ", tmp_path = tmp_path, app_name = crate::get_app_name(), exe = exe, ), "vbs", )? .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 ", tmp_path = tmp_path, app_name = crate::get_app_name(), exe = exe, ), "vbs", )? .to_str() .unwrap_or("") .to_owned(); let tray_shortcut = 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 ", tmp_path = tmp_path, app_name = crate::get_app_name(), exe = exe, ), "vbs", )? .to_str() .unwrap_or("") .to_owned(); 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!( "{} md \"{start_menu}\" copy /Y \"{tmp_path}\\{app_name}.lnk\" \"{start_menu}\\\" copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{start_menu}\\\" ", shortcuts, start_menu = start_menu, tmp_path = tmp_path, app_name = crate::get_app_name(), ); } 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 let cmds = format!( " {uninstall_str} chcp 65001 md \"{path}\" copy /Y \"{src_exe}\" \"{exe}\" reg add {subkey} /f reg add {subkey} /f /v DisplayIcon /t REG_SZ /d \"{exe}\" reg add {subkey} /f /v DisplayName /t REG_SZ /d \"{app_name}\" reg add {subkey} /f /v Version /t REG_SZ /d \"{version}\" reg add {subkey} /f /v InstallLocation /t REG_SZ /d \"{path}\" reg add {subkey} /f /v Publisher /t REG_SZ /d \"{app_name}\" reg add {subkey} /f /v VersionMajor /t REG_DWORD /d {major} reg add {subkey} /f /v VersionMinor /t REG_DWORD /d {minor} reg add {subkey} /f /v VersionBuild /t REG_DWORD /d {build} 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 {lic} \"{mk_shortcut}\" \"{uninstall_shortcut}\" \"{tray_shortcut}\" copy /Y \"{tmp_path}\\{app_name} Tray.lnk\" \"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\\" {shortcuts} copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{path}\\\" del /f \"{mk_shortcut}\" del /f \"{uninstall_shortcut}\" del /f \"{tray_shortcut}\" del /f \"{tmp_path}\\{app_name}.lnk\" del /f \"{tmp_path}\\Uninstall {app_name}.lnk\" del /f \"{tmp_path}\\{app_name} Tray.lnk\" 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} {after_install} ", uninstall_str=uninstall_str, path=path, src_exe=std::env::current_exe()?.to_str().unwrap_or(""), exe=exe, subkey=subkey, app_name=crate::get_app_name(), version=crate::VERSION, major=version_major, minor=version_minor, build=version_build, size=size, mk_shortcut=mk_shortcut, uninstall_shortcut=uninstall_shortcut, tray_shortcut=tray_shortcut, tmp_path=tmp_path, shortcuts=shortcuts, config_path=Config::file().to_str().unwrap_or(""), lic=register_licence(), after_install=get_after_install(&exe), ); run_cmds(cmds, false)?; std::thread::sleep(std::time::Duration::from_millis(2000)); std::process::Command::new(&exe).spawn()?; std::process::Command::new(&exe).arg("--tray").spawn()?; std::thread::sleep(std::time::Duration::from_millis(1000)); Ok(()) } pub fn run_after_install() -> ResultType<()> { let (_, _, _, exe) = get_install_info(); run_cmds(get_after_install(&exe), true) } pub fn run_before_uninstall() -> ResultType<()> { run_cmds(get_before_uninstall(), true) } fn get_before_uninstall() -> String { let app_name = crate::get_app_name(); let ext = app_name.to_lowercase(); format!( " chcp 65001 sc stop {app_name} sc delete {app_name} taskkill /F /IM {app_name}.exe reg delete HKEY_CLASSES_ROOT\\.{ext} /f netsh advfirewall firewall delete rule name=\"{app_name} Service\" ", app_name = app_name, ext = ext ) } fn get_uninstall() -> String { let (subkey, path, start_menu, _) = get_install_info(); format!( " {before_uninstall} reg delete {subkey} /f rd /s /q \"{path}\" rd /s /q \"{start_menu}\" del /f /q \"%PUBLIC%\\Desktop\\{app_name}*\" del /f /q \"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" ", before_uninstall=get_before_uninstall(), subkey=subkey, app_name = crate::get_app_name(), path = path, start_menu = start_menu, ) } pub fn uninstall_me() -> ResultType<()> { run_cmds(get_uninstall(), true) } fn write_cmds(cmds: String, ext: &str) -> ResultType { let mut tmp = std::env::temp_dir(); tmp.push(format!( "{}_{:?}.{}", crate::get_app_name(), cmds.as_ptr(), ext )); let mut cmds = cmds; if ext == "cmd" { cmds = format!("{}\ndel /f \"{}\"", cmds, tmp.to_str().unwrap_or("")); } let mut file = std::fs::File::create(&tmp)?; // 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 let cmds = cmds.replace("\r\n", "\n").replace("\n", "\r\n"); if ext == "vbs" { let mut v: Vec = 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 } } fn run_cmds(cmds: String, show: bool) -> ResultType<()> { let tmp = write_cmds(cmds, "cmd")?; let res = runas::Command::new(tmp.to_str().unwrap_or("")) .show(show) .force_prompt(true) .status(); // double confirm delete, because below delete not work if program // exit immediately such as --uninstall allow_err!(std::fs::remove_file(tmp)); let _ = res?; Ok(()) } pub fn toggle_blank_screen(v: bool) { let v = if v { TRUE } else { FALSE }; unsafe { blank_screen(v); } } pub fn block_input(v: bool) -> bool { let v = if v { TRUE } else { FALSE }; unsafe { BlockInput(v) == TRUE } } pub fn add_recent_document(path: &str) { extern "C" { fn AddRecentDocument(path: *const u16); } use std::os::windows::ffi::OsStrExt; let wstr: Vec = 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 { use windows_service::{ service::ServiceAccess, service_manager::{ServiceManager, ServiceManagerAccess}, }; let (_, _, _, exe) = get_install_info(); if !std::fs::metadata(exe).is_ok() { return false; } let manager_access = ServiceManagerAccess::CONNECT; if let Ok(service_manager) = ServiceManager::local_computer(None::<&str>, manager_access) { if let Ok(_) = service_manager.open_service(crate::get_app_name(), ServiceAccess::QUERY_CONFIG) { return true; } } return false; } pub fn get_installed_version() -> String { let (_, _, _, exe) = get_install_info(); if let Ok(output) = std::process::Command::new(exe).arg("--version").output() { for line in String::from_utf8_lossy(&output.stdout).lines() { return line.to_owned(); } } "".to_owned() } fn get_reg(name: &str) -> String { let (subkey, _, _, _) = get_install_info(); 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() } fn get_license_from_exe_name() -> ResultType { let exe = std::env::current_exe()?.to_str().unwrap_or("").to_owned(); get_license_from_string(&exe) } #[inline] pub fn is_win_server() -> bool { unsafe { is_windows_server() > 0 } } pub fn get_license() -> Option { let mut lic: License = Default::default(); if let Ok(tmp) = get_license_from_exe_name() { lic = tmp; } else { 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) } pub fn bootstrap() { if let Some(lic) = get_license() { *config::PROD_RENDEZVOUS_SERVER.write().unwrap() = lic.host.clone(); #[cfg(feature = "hbbs")] { if !is_win_server() { return; } crate::hbbs::bootstrap(&lic.key, &lic.host); std::thread::spawn(move || loop { let tmp = Config::get_option("stop-rendezvous-service"); if tmp.is_empty() { crate::hbbs::start(); } else { crate::hbbs::stop(); } std::thread::sleep(std::time::Duration::from_millis(100)); }); } } } fn register_licence() -> String { let (subkey, _, _, _) = get_install_info(); if let Ok(lic) = get_license_from_exe_name() { format!( " reg add {subkey} /f /v Key /t REG_SZ /d \"{key}\" reg add {subkey} /f /v Host /t REG_SZ /d \"{host}\" reg add {subkey} /f /v Api /t REG_SZ /d \"{api}\" ", subkey = subkey, key = &lic.key, host = &lic.host, api = &lic.api, ) } else { "".to_owned() } } pub fn is_rdp_service_open() -> bool { unsafe { has_rdp_service() == TRUE } } 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 ", exe = exe, id = id, ), "vbs", )? .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() { std::process::exit(0); // unsafe { PostQuitMessage(0) }; // some how not work }