source code

This commit is contained in:
rustdesk
2021-03-29 15:59:14 +08:00
parent 002fce136c
commit d1013487e2
175 changed files with 35074 additions and 2 deletions

446
src/platform/linux.rs Normal file
View File

@@ -0,0 +1,446 @@
use super::{CursorData, ResultType};
use hbb_common::{allow_err, bail, log};
use libc::{c_char, c_int, c_void};
use std::{
cell::RefCell,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
type Xdo = *const c_void;
pub const PA_SAMPLE_RATE: u32 = 24000;
thread_local! {
static XDO: RefCell<Xdo> = RefCell::new(unsafe { xdo_new(std::ptr::null()) });
static DISPLAY: RefCell<*mut c_void> = RefCell::new(unsafe { XOpenDisplay(std::ptr::null())});
}
extern "C" {
fn xdo_get_mouse_location(
xdo: Xdo,
x: *mut c_int,
y: *mut c_int,
screen_num: *mut c_int,
) -> c_int;
fn xdo_new(display: *const c_char) -> Xdo;
}
#[link(name = "X11")]
extern "C" {
fn XOpenDisplay(display_name: *const c_char) -> *mut c_void;
// fn XCloseDisplay(d: *mut c_void) -> c_int;
}
#[link(name = "Xfixes")]
extern "C" {
// fn XFixesQueryExtension(dpy: *mut c_void, event: *mut c_int, error: *mut c_int) -> c_int;
fn XFixesGetCursorImage(dpy: *mut c_void) -> *const xcb_xfixes_get_cursor_image;
fn XFree(data: *mut c_void);
}
// /usr/include/X11/extensions/Xfixes.h
#[repr(C)]
pub struct xcb_xfixes_get_cursor_image {
pub x: i16,
pub y: i16,
pub width: u16,
pub height: u16,
pub xhot: u16,
pub yhot: u16,
pub cursor_serial: libc::c_long,
pub pixels: *const libc::c_long,
}
pub fn get_cursor_pos() -> Option<(i32, i32)> {
let mut res = None;
XDO.with(|xdo| {
if let Ok(xdo) = xdo.try_borrow_mut() {
if xdo.is_null() {
return;
}
let mut x: c_int = 0;
let mut y: c_int = 0;
unsafe {
xdo_get_mouse_location(*xdo, &mut x as _, &mut y as _, std::ptr::null_mut());
}
res = Some((x, y));
}
});
res
}
pub fn reset_input_cache() {}
pub fn get_cursor() -> ResultType<Option<u64>> {
let mut res = None;
DISPLAY.with(|conn| {
if let Ok(d) = conn.try_borrow_mut() {
if !d.is_null() {
unsafe {
let img = XFixesGetCursorImage(*d);
if !img.is_null() {
res = Some((*img).cursor_serial as u64);
XFree(img as _);
}
}
}
}
});
Ok(res)
}
pub fn get_cursor_data(hcursor: u64) -> ResultType<CursorData> {
let mut res = None;
DISPLAY.with(|conn| {
if let Ok(ref mut d) = conn.try_borrow_mut() {
if !d.is_null() {
unsafe {
let img = XFixesGetCursorImage(**d);
if !img.is_null() && hcursor == (*img).cursor_serial as u64 {
let mut cd: CursorData = Default::default();
cd.hotx = (*img).xhot as _;
cd.hoty = (*img).yhot as _;
cd.width = (*img).width as _;
cd.height = (*img).height as _;
// to-do: how about if it is 0
cd.id = (*img).cursor_serial as _;
let pixels =
std::slice::from_raw_parts((*img).pixels, (cd.width * cd.height) as _);
cd.colors.resize(pixels.len() * 4, 0);
for y in 0..cd.height {
for x in 0..cd.width {
let pos = (y * cd.width + x) as usize;
let p = pixels[pos];
let a = (p >> 24) & 0xff;
let r = (p >> 16) & 0xff;
let g = (p >> 8) & 0xff;
let b = (p >> 0) & 0xff;
if a == 0 {
continue;
}
let pos = pos * 4;
cd.colors[pos] = r as _;
cd.colors[pos + 1] = g as _;
cd.colors[pos + 2] = b as _;
cd.colors[pos + 3] = a as _;
}
}
res = Some(cd);
}
if !img.is_null() {
XFree(img as _);
}
}
}
}
});
match res {
Some(x) => Ok(x),
_ => bail!("Failed to get cursor image of {}", hcursor),
}
}
pub fn start_os_service() {
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
let mut uid = "".to_owned();
let mut server: Option<std::process::Child> = None;
if let Err(err) = ctrlc::set_handler(move || {
r.store(false, Ordering::SeqCst);
}) {
println!("Failed to set Ctrl-C handler: {}", err);
}
let mut cm0 = false;
let mut last_restart = std::time::Instant::now();
while running.load(Ordering::SeqCst) {
let cm = get_cm();
let tmp = get_active_userid();
let mut start_new = false;
if tmp != uid && !tmp.is_empty() {
uid = tmp;
log::info!("uid of seat0: {}", uid);
std::env::set_var("XAUTHORITY", format!("/run/user/{}/gdm/Xauthority", uid));
std::env::set_var("DISPLAY", get_display());
if let Some(ps) = server.as_mut() {
allow_err!(ps.kill());
std::thread::sleep(std::time::Duration::from_millis(30));
last_restart = std::time::Instant::now();
}
} else if !cm
&& ((cm0 && last_restart.elapsed().as_secs() > 60)
|| last_restart.elapsed().as_secs() > 3600)
{
// restart server if new connections all closed, or every one hour,
// as a workaround to resolve "SpotUdp" (dns resolve)
// and x server get displays failure issue
if let Some(ps) = server.as_mut() {
allow_err!(ps.kill());
std::thread::sleep(std::time::Duration::from_millis(30));
last_restart = std::time::Instant::now();
log::info!("restart server");
}
}
if let Some(ps) = server.as_mut() {
match ps.try_wait() {
Ok(Some(_)) => {
server = None;
start_new = true;
}
_ => {}
}
} else {
start_new = true;
}
if start_new {
match crate::run_me(vec!["--server"]) {
Ok(ps) => server = Some(ps),
Err(err) => {
log::error!("Failed to start server: {}", err);
}
}
}
cm0 = cm;
std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL));
}
if let Some(ps) = server.take().as_mut() {
allow_err!(ps.kill());
}
println!("Exit");
}
fn get_active_userid() -> String {
get_value_of_seat0(1)
}
fn is_active(sid: &str) -> bool {
if let Ok(output) = std::process::Command::new("loginctl")
.args(vec!["show-session", "-p", "State", sid])
.output()
{
String::from_utf8_lossy(&output.stdout).contains("active")
} else {
false
}
}
fn get_cm() -> bool {
if let Ok(output) = std::process::Command::new("ps").args(vec!["aux"]).output() {
for line in String::from_utf8_lossy(&output.stdout).lines() {
if line.contains(&format!(
"{} --cm",
std::env::current_exe()
.unwrap_or("".into())
.to_string_lossy()
)) {
return true;
}
}
}
false
}
fn get_display() -> String {
let user = get_active_username();
if let Ok(output) = std::process::Command::new("w").arg(&user).output() {
for line in String::from_utf8_lossy(&output.stdout).lines() {
let mut iter = line.split_whitespace();
let a = iter.nth(1);
let b = iter.next();
if a == b {
if let Some(b) = b {
if b.starts_with(":") {
return b.to_owned();
}
}
}
}
}
// above not work for gdm user
if let Ok(output) = std::process::Command::new("ls")
.args(vec!["-l", "/tmp/.X11-unix/"])
.output()
{
for line in String::from_utf8_lossy(&output.stdout).lines() {
let mut iter = line.split_whitespace();
if iter.nth(2) == Some(&user) {
if let Some(x) = iter.last() {
if x.starts_with("X") {
return x.replace("X", ":").to_owned();
}
}
}
}
}
"".to_owned()
}
fn get_value_of_seat0(i: usize) -> String {
if let Ok(output) = std::process::Command::new("loginctl").output() {
for line in String::from_utf8_lossy(&output.stdout).lines() {
if line.contains("seat0") {
if let Some(sid) = line.split_whitespace().nth(0) {
if is_active(sid) {
if let Some(uid) = line.split_whitespace().nth(i) {
return uid.to_owned();
}
}
}
}
}
}
return "".to_owned();
}
pub fn get_display_server() -> String {
let session = get_value_of_seat0(0);
if let Ok(output) = std::process::Command::new("loginctl")
.args(vec!["show-session", "-p", "Type", &session])
.output()
{
String::from_utf8_lossy(&output.stdout)
.replace("Type=", "")
.trim_end()
.into()
} else {
"".to_owned()
}
}
pub fn is_login_wayland() -> bool {
if let Ok(contents) = std::fs::read_to_string("/etc/gdm3/custom.conf") {
contents.contains("#WaylandEnable=false")
} else {
false
}
}
pub fn fix_login_wayland() {
match std::process::Command::new("pkexec")
.args(vec![
"sed",
"-i",
"s/#WaylandEnable=false/WaylandEnable=false/g",
"/etc/gdm3/custom.conf",
])
.output()
{
Ok(x) => {
let x = String::from_utf8_lossy(&x.stderr);
if !x.is_empty() {
log::error!("fix_login_wayland failed: {}", x);
}
}
Err(err) => {
log::error!("fix_login_wayland failed: {}", err);
}
}
}
// to-do: test the other display manager
fn _get_display_manager() -> String {
if let Ok(x) = std::fs::read_to_string("/etc/X11/default-display-manager") {
if let Some(x) = x.split("/").last() {
return x.to_owned();
}
}
"gdm3".to_owned()
}
pub fn get_active_username() -> String {
get_value_of_seat0(2)
}
pub fn is_prelogin() -> bool {
let n = get_active_userid().len();
n < 4 && n > 1
}
pub fn is_root() -> bool {
crate::username() == "root"
}
pub fn run_as_user(arg: &str) -> ResultType<Option<std::process::Child>> {
let uid = get_active_userid();
let cmd = std::env::current_exe()?;
let task = std::process::Command::new("sudo")
.args(vec![
&format!("XDG_RUNTIME_DIR=/run/user/{}", uid) as &str,
"-u",
&get_active_username(),
cmd.to_str().unwrap_or(""),
arg,
])
.spawn()?;
Ok(Some(task))
}
pub fn get_pa_monitor() -> String {
get_pa_sources()
.drain(..)
.map(|x| x.0)
.filter(|x| x.contains("monitor"))
.next()
.unwrap_or("".to_owned())
}
pub fn get_pa_source_name(desc: &str) -> String {
get_pa_sources()
.drain(..)
.filter(|x| x.1 == desc)
.map(|x| x.0)
.next()
.unwrap_or("".to_owned())
}
pub fn get_pa_sources() -> Vec<(String, String)> {
use pulsectl::controllers::*;
let mut out = Vec::new();
match SourceController::create() {
Ok(mut handler) => {
if let Ok(devices) = handler.list_devices() {
for dev in devices.clone() {
out.push((
dev.name.unwrap_or("".to_owned()),
dev.description.unwrap_or("".to_owned()),
));
}
}
}
Err(err) => {
log::error!("Failed to get_pa_sources: {:?}", err);
}
}
out
}
pub fn lock_screen() {
std::thread::spawn(move || {
use crate::server::input_service::handle_key;
use hbb_common::message_proto::*;
let mut evt = KeyEvent {
down: true,
modifiers: vec![ControlKey::Meta.into()],
..Default::default()
};
evt.set_chr('l' as _);
handle_key(&evt);
evt.down = false;
handle_key(&evt);
});
}
pub fn toggle_privacy_mode(_v: bool) {
// https://unix.stackexchange.com/questions/17170/disable-keyboard-mouse-input-on-unix-under-x
}
pub fn block_input(_v: bool) {
//
}
pub fn is_installed() -> bool {
true
}

335
src/platform/macos.rs Normal file
View File

@@ -0,0 +1,335 @@
// https://developer.apple.com/documentation/appkit/nscursor
// https://github.com/servo/core-foundation-rs
// https://github.com/rust-windowing/winit
use super::{CursorData, ResultType};
use cocoa::{
base::{id, nil, BOOL, NO, YES},
foundation::{NSDictionary, NSPoint, NSSize, NSString},
};
use core_foundation::{
array::{CFArrayGetCount, CFArrayGetValueAtIndex},
dictionary::CFDictionaryRef,
string::CFStringRef,
};
use core_graphics::{
display::{kCGNullWindowID, kCGWindowListOptionOnScreenOnly, CGWindowListCopyWindowInfo},
window::{kCGWindowName, kCGWindowOwnerPID},
};
use hbb_common::{allow_err, bail, log};
use objc::{class, msg_send, sel, sel_impl};
use scrap::{libc::c_void, quartz::ffi::*};
static mut LATEST_SEED: i32 = 0;
extern "C" {
fn CGSCurrentCursorSeed() -> i32;
fn CGEventCreate(r: *const c_void) -> *const c_void;
fn CGEventGetLocation(e: *const c_void) -> CGPoint;
static kAXTrustedCheckOptionPrompt: CFStringRef;
fn AXIsProcessTrustedWithOptions(options: CFDictionaryRef) -> BOOL;
}
pub fn is_process_trusted(prompt: bool) -> bool {
unsafe {
let value = if prompt { YES } else { NO };
let value: id = msg_send![class!(NSNumber), numberWithBool: value];
let options = NSDictionary::dictionaryWithObject_forKey_(
nil,
value,
kAXTrustedCheckOptionPrompt as _,
);
AXIsProcessTrustedWithOptions(options as _) == YES
}
}
// macOS >= 10.15
// https://stackoverflow.com/questions/56597221/detecting-screen-recording-settings-on-macos-catalina/
// remove just one app from all the permissions: tccutil reset All com.carriez.rustdesk
pub fn is_can_screen_recording(prompt: bool) -> bool {
let mut can_record_screen: bool = false;
unsafe {
let our_pid: i32 = std::process::id() as _;
let our_pid: id = msg_send![class!(NSNumber), numberWithInteger: our_pid];
let window_list =
CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
let n = CFArrayGetCount(window_list);
let dock = NSString::alloc(nil).init_str("Dock");
for i in 0..n {
let w: id = CFArrayGetValueAtIndex(window_list, i) as _;
let name: id = msg_send![w, valueForKey: kCGWindowName as id];
if name.is_null() {
continue;
}
let pid: id = msg_send![w, valueForKey: kCGWindowOwnerPID as id];
let is_me: BOOL = msg_send![pid, isEqual: our_pid];
if is_me == YES {
continue;
}
let pid: i32 = msg_send![pid, intValue];
let p: id = msg_send![
class!(NSRunningApplication),
runningApplicationWithProcessIdentifier: pid
];
if p.is_null() {
// ignore processes we don't have access to, such as WindowServer, which manages the windows named "Menubar" and "Backstop Menubar"
continue;
}
let url: id = msg_send![p, executableURL];
let exe_name: id = msg_send![url, lastPathComponent];
if exe_name.is_null() {
continue;
}
let is_dock: BOOL = msg_send![exe_name, isEqual: dock];
if is_dock == YES {
// ignore the Dock, which provides the desktop picture
continue;
}
can_record_screen = true;
break;
}
}
if !can_record_screen && prompt {
use scrap::{Capturer, Display};
if let Ok(d) = Display::primary() {
Capturer::new(d, true).ok();
}
}
can_record_screen
}
pub fn get_cursor_pos() -> Option<(i32, i32)> {
unsafe {
let e = CGEventCreate(0 as _);
let point = CGEventGetLocation(e);
CFRelease(e);
Some((point.x as _, point.y as _))
}
/*
let mut pt: NSPoint = unsafe { msg_send![class!(NSEvent), mouseLocation] };
let screen: id = unsafe { msg_send![class!(NSScreen), currentScreenForMouseLocation] };
let frame: NSRect = unsafe { msg_send![screen, frame] };
pt.x -= frame.origin.x;
pt.y -= frame.origin.y;
Some((pt.x as _, pt.y as _))
*/
}
pub fn get_cursor() -> ResultType<Option<u64>> {
unsafe {
let seed = CGSCurrentCursorSeed();
if seed == LATEST_SEED {
return Ok(None);
}
LATEST_SEED = seed;
}
let c = get_cursor_id()?;
Ok(Some(c.1))
}
pub fn reset_input_cache() {
unsafe {
LATEST_SEED = 0;
}
}
fn get_cursor_id() -> ResultType<(id, u64)> {
unsafe {
let c: id = msg_send![class!(NSCursor), currentSystemCursor];
if c == nil {
bail!("Failed to call [NSCursor currentSystemCursor]");
}
let hotspot: NSPoint = msg_send![c, hotSpot];
let img: id = msg_send![c, image];
if img == nil {
bail!("Failed to call [NSCursor image]");
}
let size: NSSize = msg_send![img, size];
let tif: id = msg_send![img, TIFFRepresentation];
if tif == nil {
bail!("Failed to call [NSImage TIFFRepresentation]");
}
let rep: id = msg_send![class!(NSBitmapImageRep), imageRepWithData: tif];
if rep == nil {
bail!("Failed to call [NSBitmapImageRep imageRepWithData]");
}
let rep_size: NSSize = msg_send![rep, size];
let mut hcursor =
size.width + size.height + hotspot.x + hotspot.y + rep_size.width + rep_size.height;
let x = (rep_size.width * hotspot.x / size.width) as usize;
let y = (rep_size.height * hotspot.y / size.height) as usize;
for i in 0..2 {
let mut x2 = x + i;
if x2 >= rep_size.width as usize {
x2 = rep_size.width as usize - 1;
}
let mut y2 = y + i;
if y2 >= rep_size.height as usize {
y2 = rep_size.height as usize - 1;
}
let color: id = msg_send![rep, colorAtX:x2 y:y2];
if color != nil {
let r: f64 = msg_send![color, redComponent];
let g: f64 = msg_send![color, greenComponent];
let b: f64 = msg_send![color, blueComponent];
let a: f64 = msg_send![color, alphaComponent];
hcursor += (r + g + b + a) * (255 << i) as f64;
}
}
Ok((c, hcursor as _))
}
}
// https://github.com/stweil/OSXvnc/blob/master/OSXvnc-server/mousecursor.c
pub fn get_cursor_data(hcursor: u64) -> ResultType<CursorData> {
unsafe {
let (c, hcursor2) = get_cursor_id()?;
if hcursor != hcursor2 {
bail!("cursor changed");
}
let hotspot: NSPoint = msg_send![c, hotSpot];
let img: id = msg_send![c, image];
let size: NSSize = msg_send![img, size];
let reps: id = msg_send![img, representations];
if reps == nil {
bail!("Failed to call [NSImage representations]");
}
let nreps: usize = msg_send![reps, count];
if nreps == 0 {
bail!("Get empty [NSImage representations]");
}
let rep: id = msg_send![reps, objectAtIndex: 0];
/*
let n: id = msg_send![class!(NSNumber), numberWithFloat:1.0];
let props: id = msg_send![class!(NSDictionary), dictionaryWithObject:n forKey:NSString::alloc(nil).init_str("NSImageCompressionFactor")];
let image_data: id = msg_send![rep, representationUsingType:2 properties:props];
let () = msg_send![image_data, writeToFile:NSString::alloc(nil).init_str("cursor.jpg") atomically:0];
*/
let mut colors: Vec<u8> = Vec::new();
colors.reserve((size.height * size.width) as usize * 4);
// TIFF is rgb colrspace, no need to convert
// let cs: id = msg_send![class!(NSColorSpace), sRGBColorSpace];
for y in 0..(size.height as _) {
for x in 0..(size.width as _) {
let color: id = msg_send![rep, colorAtX:x y:y];
// let color: id = msg_send![color, colorUsingColorSpace: cs];
if color == nil {
continue;
}
let r: f64 = msg_send![color, redComponent];
let g: f64 = msg_send![color, greenComponent];
let b: f64 = msg_send![color, blueComponent];
let a: f64 = msg_send![color, alphaComponent];
colors.push((r * 255.) as _);
colors.push((g * 255.) as _);
colors.push((b * 255.) as _);
colors.push((a * 255.) as _);
}
}
Ok(CursorData {
id: hcursor,
colors,
hotx: hotspot.x as _,
hoty: hotspot.y as _,
width: size.width as _,
height: size.height as _,
..Default::default()
})
}
}
fn get_active_user(t: &str) -> String {
if let Ok(output) = std::process::Command::new("ls")
.args(vec![t, "/dev/console"])
.output()
{
for line in String::from_utf8_lossy(&output.stdout).lines() {
if let Some(n) = line.split_whitespace().nth(2) {
return n.to_owned();
}
}
}
"".to_owned()
}
pub fn get_active_username() -> String {
get_active_user("-l")
}
pub fn get_active_userid() -> String {
get_active_user("-n")
}
pub fn is_prelogin() -> bool {
get_active_userid() == "0"
}
pub fn is_root() -> bool {
crate::username() == "root"
}
pub fn run_as_user(arg: &str) -> ResultType<Option<std::process::Child>> {
let uid = get_active_userid();
let cmd = std::env::current_exe()?;
let task = std::process::Command::new("launchctl")
.args(vec!["asuser", &uid, cmd.to_str().unwrap_or(""), arg])
.spawn()?;
Ok(Some(task))
}
pub fn lock_screen() {
std::process::Command::new(
"/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/CGSession",
)
.arg("-suspend")
.output()
.ok();
}
pub fn start_os_service() {
let mut server: Option<std::process::Child> = None;
let mut uid = "".to_owned();
loop {
let tmp = get_active_userid();
let mut start_new = false;
if tmp != uid && !tmp.is_empty() {
uid = tmp;
log::info!("active uid: {}", uid);
if let Some(ps) = server.as_mut() {
allow_err!(ps.kill());
}
}
if let Some(ps) = server.as_mut() {
match ps.try_wait() {
Ok(Some(_)) => {
server = None;
start_new = true;
}
_ => {}
}
} else {
start_new = true;
}
if start_new {
match crate::run_me(vec!["--server"]) {
Ok(ps) => server = Some(ps),
Err(err) => {
log::error!("Failed to start server: {}", err);
}
}
}
std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL));
}
}
pub fn toggle_privacy_mode(_v: bool) {
// https://unix.stackexchange.com/questions/17115/disable-keyboard-mouse-temporarily
}
pub fn block_input(_v: bool) {
//
}
pub fn is_installed() -> bool {
true
}

46
src/platform/mod.rs Normal file
View File

@@ -0,0 +1,46 @@
#[cfg(target_os = "linux")]
pub use linux::*;
#[cfg(target_os = "macos")]
pub use macos::*;
#[cfg(windows)]
pub use windows::*;
#[cfg(windows)]
pub mod windows;
#[cfg(target_os = "macos")]
pub mod macos;
#[cfg(target_os = "linux")]
pub mod linux;
use hbb_common::{message_proto::CursorData, ResultType};
const SERVICE_INTERVAL: u64 = 300;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cursor_data() {
for _ in 0..30 {
if let Some(hc) = get_cursor().unwrap() {
let cd = get_cursor_data(hc).unwrap();
repng::encode(
std::fs::File::create("cursor.png").unwrap(),
cd.width as _,
cd.height as _,
&cd.colors[..],
)
.unwrap();
}
#[cfg(target_os = "macos")]
macos::is_process_trusted(false);
}
}
#[test]
fn test_get_cursor_pos() {
for _ in 0..30 {
assert!(!get_cursor_pos().is_none());
}
}
}

981
src/platform/windows.rs Normal file
View File

@@ -0,0 +1,981 @@
use super::{CursorData, ResultType};
use crate::ipc;
use hbb_common::{
allow_err, bail,
config::{Config, APP_NAME},
futures_util::stream::StreamExt,
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},
};
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<Option<u64>> {
unsafe {
let mut ci: CURSORINFO = mem::MaybeUninit::uninit().assume_init();
ci.cbSize = std::mem::size_of::<CURSORINFO>() 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<Self> {
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<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;
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,
) > 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);
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<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 {
bail!("Failed to get di bits: {}", get_error());
}
}
Ok(())
}
fn fix_cursor_mask(
mbits: &mut Vec<u8>,
cbits: &mut Vec<u8>,
width: usize,
height: usize,
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;
// Pack and invert bitmap data (mbits)
// borrow from tigervnc
for y in 0..height {
for x in 0..packed_width_bytes {
mbits[y * packed_width_bytes + x] = !mbits[y * width_bytes + x];
}
}
// 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);
let pix_idx = y * bytes_row + (x << 2);
if (mbits[mask_idx] & bitmask) == 0 {
for b1 in 0..4 {
if cbits[pix_idx + b1] != 0 {
mbits[mask_idx] ^= bitmask;
for b2 in b1..4 {
cbits[pix_idx + b2] = 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 alpha = if (mbits[mask_idx] << (x & 0x7)) & 0x80 == 0 {
0
} else {
255
};
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(APP_NAME, ffi_service_main) {
log::error!("start_service failed: {}", e);
}
}
const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS;
extern "C" {
fn LaunchProcessWin(cmd: *const u16, session_id: DWORD, as_user: BOOL) -> HANDLE;
fn selectInputDesktop() -> BOOL;
fn inputDesktopSelected() -> BOOL;
fn handleMask(out: *mut u8, mask: *const u8, width: i32, height: i32, bmWidthBytes: i32)
-> i32;
fn drawOutline(out: *mut u8, in_: *const u8, width: i32, height: i32);
fn get_di_bits(out: *mut u8, dc: HDC, hbmColor: HBITMAP, width: i32, height: i32) -> i32;
fn blank_screen(v: BOOL);
fn BlockInput(v: BOOL) -> BOOL;
}
#[tokio::main(basic_scheduler)]
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,
ServiceControl::Stop => {
send_close(crate::POSTFIX_SERVICE).ok();
ServiceControlHandlerResult::NoError
}
_ => ServiceControlHandlerResult::NotImplemented,
}
};
// Register system service event handler
let status_handle = service_control_handler::register(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 { WTSGetActiveConsoleSessionId() };
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 = WTSGetActiveConsoleSessionId();
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<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() {
log::error!("Failed to luanch server: {}", get_error());
}
Ok(h)
}
pub fn run_as_user(arg: &str) -> ResultType<Option<std::process::Child>> {
let cmd = format!(
"\"{}\" {}",
std::env::current_exe()?.to_str().unwrap_or(""),
arg,
);
let session_id = unsafe { WTSGetActiveConsoleSessionId() };
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!(
"Failed to launch {} with session id {}: {}",
arg,
session_id,
get_error()
);
}
Ok(None)
}
#[tokio::main(basic_scheduler)]
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 "C" {
pub fn SendSAS(AsUser: BOOL);
}
unsafe {
log::info!("SAS received");
SendSAS(FALSE);
}
}
lazy_static::lazy_static! {
static ref SUPRESS: 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 = SUPRESS.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<u16> = 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());
}
}
pub fn get_active_username() -> String {
let name = crate::username();
if name != "SYSTEM" {
return name;
}
if let Ok(output) = std::process::Command::new("query").arg("user").output() {
for line in String::from_utf8_lossy(&output.stdout).lines() {
if line.contains("Active") {
if let Some(name) = line.split_whitespace().next() {
if name.starts_with(">") {
return name.replace(">", "");
} else {
return name.to_string();
}
}
}
}
}
return "".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 "C" {
pub fn LockWorkStation() -> BOOL;
}
unsafe {
LockWorkStation();
}
}
pub fn get_install_info() -> (String, String, String, String) {
let subkey = format!(
"HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{}",
APP_NAME
);
let mut pf = "C:\\Program Files".to_owned();
if let Ok(output) = std::process::Command::new("echo")
.arg("%ProgramFiles%")
.output()
{
let tmp = String::from_utf8_lossy(&output.stdout);
if !tmp.starts_with("%") {
pf = tmp.to_string();
}
}
let path = format!("{}\\{}", pf, APP_NAME);
let start_menu = format!(
"%ProgramData%\\Microsoft\\Windows\\Start Menu\\Programs\\{}",
APP_NAME
);
let exe = format!("{}\\{}.exe", path, 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!(
"
sc stop {app_name}
taskkill /F /IM {app_name}.exe
copy /Y \"{src_exe}\" \"{exe}\"
sc start {app_name}
",
src_exe = src_exe,
exe = exe,
app_name = APP_NAME,
);
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).spawn()?;
std::process::Command::new(&exe)
.args(&["--remove", &src_exe])
.spawn()?;
Ok(())
}
pub fn install_me(options: &str) -> ResultType<()> {
let (subkey, path, start_menu, exe) = get_install_info();
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 = 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 = 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, 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 = APP_NAME
);
}
let meta = std::fs::symlink_metadata(std::env::current_exe()?)?;
let size = meta.len() / 1024;
// save_tmp is for ensuring not copying file while writing
let config_path = Config::save_tmp();
let ext = APP_NAME.to_lowercase();
// 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!(
"
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
reg add HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System /f /v SoftwareSASGeneration /t REG_DWORD /d 1
\"{mk_shortcut}\"
\"{uninstall_shortcut}\"
{shortcuts}
del /f \"{mk_shortcut}\"
del /f \"{uninstall_shortcut}\"
del /f \"{tmp_path}\\{app_name}.lnk\"
del /f \"{tmp_path}\\Uninstall {app_name}.lnk\"
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}\\\" --import-config \\\"{config_path}\\\"\" start= auto DisplayName= \"{app_name} Service\"
sc start {app_name}
sc stop {app_name}
sc delete {app_name}
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
del /f \"{config_path}\"
del /f \"{config2_path}\"
sc start {app_name}
",
path=path,
src_exe=std::env::current_exe()?.to_str().unwrap_or(""),
exe=exe,
subkey=subkey,
app_name=APP_NAME,
version=crate::VERSION,
major=version_major,
minor=version_minor,
build=version_build,
size=size,
mk_shortcut=mk_shortcut,
uninstall_shortcut=uninstall_shortcut,
tmp_path=tmp_path,
shortcuts=shortcuts,
config_path=config_path,
config2_path=config_path.replace(".toml", "2.toml"),
ext=ext,
);
run_cmds(cmds, false)?;
std::thread::sleep(std::time::Duration::from_millis(2000));
std::process::Command::new(exe).spawn()?;
std::thread::sleep(std::time::Duration::from_millis(1000));
Ok(())
}
pub fn uninstall_me() -> ResultType<()> {
let (subkey, path, start_menu, _) = get_install_info();
let ext = APP_NAME.to_lowercase();
let cmds = format!(
"
sc stop {app_name}
sc delete {app_name}
taskkill /F /IM {app_name}.exe
reg delete {subkey} /f
reg delete HKEY_CLASSES_ROOT\\.{ext} /f
rd /s /q \"{path}\"
rd /s /q \"{start_menu}\"
del /f /q \"%PUBLIC%\\Desktop\\{app_name}*\"
netsh advfirewall firewall delete rule name=\"{app_name} Service\"
",
app_name = APP_NAME,
path = path,
subkey = subkey,
start_menu = start_menu,
ext = ext,
);
run_cmds(cmds, true)
}
fn write_cmds(cmds: String, ext: &str) -> ResultType<std::path::PathBuf> {
let mut tmp = std::env::temp_dir();
tmp.push(format!("{}_{}.{}", APP_NAME, crate::get_time(), 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)?;
file.write_all(cmds.as_bytes())?;
file.sync_all()?;
return Ok(tmp);
}
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_privacy_mode(v: bool) {
let v = if v { TRUE } else { FALSE };
unsafe {
blank_screen(v);
BlockInput(v);
}
}
pub fn block_input(v: bool) {
let v = if v { TRUE } else { FALSE };
unsafe {
BlockInput(v);
}
}
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 {
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(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()
}