mirror of
https://gitlab.com/cleanflash/installer.git
synced 2026-08-13 02:41:38 +08:00
Implement HiDPI scaling
This commit is contained in:
@@ -102,16 +102,154 @@ fn try_clear_readonly_and_delete(path: &Path) -> bool {
|
||||
}
|
||||
|
||||
fn try_take_ownership_and_delete(path: &Path) -> bool {
|
||||
// On Windows we could use SetNamedSecurityInfo to take ownership.
|
||||
// For simplicity the Rust port clears read-only and retries.
|
||||
use windows_sys::Win32::Foundation::{CloseHandle, LocalFree};
|
||||
use windows_sys::Win32::Security::{
|
||||
GetTokenInformation, TokenUser, DACL_SECURITY_INFORMATION,
|
||||
OWNER_SECURITY_INFORMATION, TOKEN_QUERY, TOKEN_USER,
|
||||
};
|
||||
use windows_sys::Win32::Security::Authorization::{
|
||||
SetEntriesInAclW, SetNamedSecurityInfoW, EXPLICIT_ACCESS_W,
|
||||
SE_FILE_OBJECT, SET_ACCESS, TRUSTEE_IS_SID, TRUSTEE_IS_USER, TRUSTEE_W,
|
||||
};
|
||||
use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
|
||||
|
||||
// Ensure SeTakeOwnershipPrivilege is enabled (idempotent).
|
||||
crate::winapi_helpers::allow_modifications();
|
||||
|
||||
let path_wide: Vec<u16> = path
|
||||
.to_string_lossy()
|
||||
.encode_utf16()
|
||||
.chain(std::iter::once(0))
|
||||
.collect();
|
||||
|
||||
unsafe {
|
||||
let mut token = std::ptr::null_mut();
|
||||
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) == 0 {
|
||||
return try_clear_readonly_and_delete(path);
|
||||
}
|
||||
|
||||
// Retrieve the current user's SID from the process token.
|
||||
let mut buf = vec![0u8; 512];
|
||||
let mut returned = 0u32;
|
||||
let ok = GetTokenInformation(
|
||||
token,
|
||||
TokenUser,
|
||||
buf.as_mut_ptr() as *mut _,
|
||||
buf.len() as u32,
|
||||
&mut returned,
|
||||
);
|
||||
CloseHandle(token);
|
||||
|
||||
if ok == 0 {
|
||||
return try_clear_readonly_and_delete(path);
|
||||
}
|
||||
|
||||
let token_user = &*(buf.as_ptr() as *const TOKEN_USER);
|
||||
let sid = token_user.User.Sid;
|
||||
|
||||
// Transfer ownership of the file to the current user.
|
||||
SetNamedSecurityInfoW(
|
||||
path_wide.as_ptr() as *mut _,
|
||||
SE_FILE_OBJECT,
|
||||
OWNER_SECURITY_INFORMATION,
|
||||
sid,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
|
||||
// Build a new DACL that grants FullControl to the current user.
|
||||
let mut ea = EXPLICIT_ACCESS_W {
|
||||
grfAccessPermissions: 0x001F_01FF, // FILE_ALL_ACCESS
|
||||
grfAccessMode: SET_ACCESS,
|
||||
grfInheritance: 0, // NO_INHERITANCE
|
||||
Trustee: TRUSTEE_W {
|
||||
pMultipleTrustee: std::ptr::null_mut(),
|
||||
MultipleTrusteeOperation: 0, // NO_MULTIPLE_TRUSTEE
|
||||
TrusteeForm: TRUSTEE_IS_SID,
|
||||
TrusteeType: TRUSTEE_IS_USER,
|
||||
ptstrName: sid as *mut u16,
|
||||
},
|
||||
};
|
||||
let mut new_dacl: *mut windows_sys::Win32::Security::ACL = std::ptr::null_mut();
|
||||
SetEntriesInAclW(1, &mut ea, std::ptr::null_mut(), &mut new_dacl);
|
||||
|
||||
if !new_dacl.is_null() {
|
||||
SetNamedSecurityInfoW(
|
||||
path_wide.as_ptr() as *mut _,
|
||||
SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
new_dacl,
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
LocalFree(new_dacl as *mut _);
|
||||
}
|
||||
}
|
||||
|
||||
try_clear_readonly_and_delete(path)
|
||||
}
|
||||
|
||||
fn kill_locking_processes(path: &Path) {
|
||||
// Use taskkill as a best-effort approach.
|
||||
// The C# original enumerates all open handles, which requires complex
|
||||
// NT API calls. A simplified approach is acceptable for the port.
|
||||
let _ = path; // Locking-process detection is a best-effort no-op here.
|
||||
use windows_sys::Win32::Foundation::CloseHandle;
|
||||
use windows_sys::Win32::System::RestartManager::{
|
||||
RmEndSession, RmGetList, RmRegisterResources, RmStartSession, RM_PROCESS_INFO,
|
||||
};
|
||||
use windows_sys::Win32::System::Threading::{
|
||||
OpenProcess, TerminateProcess, WaitForSingleObject, PROCESS_TERMINATE,
|
||||
};
|
||||
|
||||
let path_wide: Vec<u16> = path
|
||||
.to_string_lossy()
|
||||
.encode_utf16()
|
||||
.chain(std::iter::once(0))
|
||||
.collect();
|
||||
|
||||
unsafe {
|
||||
let mut session: u32 = 0;
|
||||
// CCH_RM_SESSION_KEY = 32 chars; +1 for null terminator.
|
||||
let mut session_key = [0u16; 33];
|
||||
if RmStartSession(&mut session, 0, session_key.as_mut_ptr()) != 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let file_ptr = path_wide.as_ptr();
|
||||
let files = [file_ptr];
|
||||
RmRegisterResources(
|
||||
session,
|
||||
1,
|
||||
files.as_ptr(),
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
|
||||
let mut n_needed: u32 = 0;
|
||||
let mut n_info: u32 = 10;
|
||||
let mut procs: [RM_PROCESS_INFO; 10] = std::mem::zeroed();
|
||||
let mut reboot_reasons: u32 = 0;
|
||||
RmGetList(
|
||||
session,
|
||||
&mut n_needed,
|
||||
&mut n_info,
|
||||
procs.as_mut_ptr(),
|
||||
&mut reboot_reasons,
|
||||
);
|
||||
|
||||
for proc_info in procs.iter().take(n_info as usize) {
|
||||
let pid = proc_info.Process.dwProcessId;
|
||||
let handle = OpenProcess(PROCESS_TERMINATE, 0, pid);
|
||||
if !handle.is_null() {
|
||||
TerminateProcess(handle, 1);
|
||||
WaitForSingleObject(handle, 5000);
|
||||
CloseHandle(handle);
|
||||
}
|
||||
}
|
||||
|
||||
RmEndSession(session);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_dir_empty(path: &Path) -> bool {
|
||||
|
||||
@@ -10,7 +10,9 @@ pub fn apply_registry(entries: &[&str]) -> Result<(), InstallError> {
|
||||
let content = format!("Windows Registry Editor Version 5.00\n\n{}", filled);
|
||||
|
||||
let temp_dir = std::env::temp_dir();
|
||||
let reg_file = temp_dir.join("cleanflash_reg.tmp");
|
||||
// Include the process ID to avoid collisions when two instances run concurrently,
|
||||
// matching the unique-file guarantee of C#'s Path.GetTempFileName().
|
||||
let reg_file = temp_dir.join(format!("cleanflash_reg_{}.tmp", std::process::id()));
|
||||
|
||||
// Write as UTF-16LE with BOM (Windows .reg format).
|
||||
{
|
||||
|
||||
@@ -146,6 +146,28 @@ fn delete_flash_center() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove Quick Launch shortcuts from Internet Explorer.
|
||||
if let Ok(appdata) = env::var("APPDATA") {
|
||||
file_util::recursive_delete(
|
||||
&PathBuf::from(&appdata)
|
||||
.join("Microsoft")
|
||||
.join("Internet Explorer")
|
||||
.join("Quick Launch"),
|
||||
Some("Flash Center.lnk"),
|
||||
);
|
||||
}
|
||||
|
||||
// Remove Flash Player shortcut from the user's Start Menu root.
|
||||
if let Ok(appdata) = env::var("APPDATA") {
|
||||
file_util::delete_file(
|
||||
&PathBuf::from(appdata)
|
||||
.join("Microsoft")
|
||||
.join("Windows")
|
||||
.join("Start Menu")
|
||||
.join("Flash Player.lnk"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_flash_player() {
|
||||
@@ -189,7 +211,10 @@ fn stop_processes() {
|
||||
};
|
||||
|
||||
// Enumerate all processes via the snapshot API.
|
||||
let pids = enumerate_processes();
|
||||
let mut pids = enumerate_processes();
|
||||
|
||||
// Sort by creation time (oldest first), matching C#'s .OrderBy(o => o.StartTime).
|
||||
pids.sort_by_key(|(pid, _)| get_process_creation_time(*pid));
|
||||
|
||||
for (pid, name) in &pids {
|
||||
let lower = name.to_lowercase();
|
||||
@@ -211,6 +236,24 @@ fn stop_processes() {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_process_creation_time(pid: u32) -> u64 {
|
||||
use windows_sys::Win32::Foundation::{CloseHandle, FILETIME};
|
||||
use windows_sys::Win32::System::Threading::{GetProcessTimes, OpenProcess, PROCESS_QUERY_INFORMATION};
|
||||
unsafe {
|
||||
let handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, pid);
|
||||
if handle.is_null() {
|
||||
return 0;
|
||||
}
|
||||
let mut create = FILETIME { dwLowDateTime: 0, dwHighDateTime: 0 };
|
||||
let mut exit_time = FILETIME { dwLowDateTime: 0, dwHighDateTime: 0 };
|
||||
let mut kernel_time = FILETIME { dwLowDateTime: 0, dwHighDateTime: 0 };
|
||||
let mut user_time = FILETIME { dwLowDateTime: 0, dwHighDateTime: 0 };
|
||||
GetProcessTimes(handle, &mut create, &mut exit_time, &mut kernel_time, &mut user_time);
|
||||
CloseHandle(handle);
|
||||
((create.dwHighDateTime as u64) << 32) | create.dwLowDateTime as u64
|
||||
}
|
||||
}
|
||||
|
||||
fn enumerate_processes() -> Vec<(u32, String)> {
|
||||
use windows_sys::Win32::System::ProcessStatus::{EnumProcesses, GetModuleBaseNameW};
|
||||
use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ};
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
pub const FLASH_VERSION: &str = "34.0.0.330";
|
||||
pub const VERSION: &str = "34.0.0.330";
|
||||
|
||||
const API_HOST: &str = "api.github.com";
|
||||
const API_PATH: &str = "/repos/cleanflash/installer/releases/latest";
|
||||
const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36";
|
||||
|
||||
pub struct VersionInfo {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
@@ -8,7 +12,138 @@ pub struct VersionInfo {
|
||||
}
|
||||
|
||||
pub fn get_latest_version() -> Option<VersionInfo> {
|
||||
// The original fetches from the GitHub API.
|
||||
// Stubbed for the port; real implementation would use ureq or reqwest.
|
||||
None
|
||||
let json = fetch_https(API_HOST, API_PATH, USER_AGENT)?;
|
||||
let name = extract_json_string(&json, "name")?;
|
||||
let tag = extract_json_string(&json, "tag_name")?;
|
||||
let url = extract_json_string(&json, "html_url")?;
|
||||
|
||||
// Validate the URL to guard against a malicious/unexpected response.
|
||||
if !url.starts_with("https://") {
|
||||
return None;
|
||||
}
|
||||
|
||||
println!("Latest release: {} ({}) {}", name, tag, url);
|
||||
Some(VersionInfo { name, version: tag, url })
|
||||
}
|
||||
|
||||
/// Perform an HTTPS GET request using WinHTTP and return the response body as UTF-8.
|
||||
fn fetch_https(host: &str, path: &str, user_agent: &str) -> Option<String> {
|
||||
use windows_sys::Win32::Networking::WinHttp::{
|
||||
WinHttpCloseHandle, WinHttpConnect, WinHttpOpen, WinHttpOpenRequest,
|
||||
WinHttpQueryDataAvailable, WinHttpReadData, WinHttpReceiveResponse,
|
||||
WinHttpSendRequest, WINHTTP_FLAG_SECURE,
|
||||
};
|
||||
|
||||
let wide = |s: &str| -> Vec<u16> { s.encode_utf16().chain(std::iter::once(0)).collect() };
|
||||
|
||||
let agent_w = wide(user_agent);
|
||||
let host_w = wide(host);
|
||||
let path_w = wide(path);
|
||||
|
||||
unsafe {
|
||||
// Open session with system default proxy settings (WINHTTP_ACCESS_TYPE_DEFAULT_PROXY = 0).
|
||||
let session = WinHttpOpen(agent_w.as_ptr(), 0, std::ptr::null(), std::ptr::null(), 0);
|
||||
if session.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let connection = WinHttpConnect(session, host_w.as_ptr(), 443, 0);
|
||||
if connection.is_null() {
|
||||
WinHttpCloseHandle(session);
|
||||
return None;
|
||||
}
|
||||
|
||||
// Open a GET request over HTTPS (null verb = GET, null version = HTTP/1.1).
|
||||
let request = WinHttpOpenRequest(
|
||||
connection,
|
||||
std::ptr::null(),
|
||||
path_w.as_ptr(),
|
||||
std::ptr::null(),
|
||||
std::ptr::null(),
|
||||
std::ptr::null(),
|
||||
WINHTTP_FLAG_SECURE,
|
||||
);
|
||||
if request.is_null() {
|
||||
WinHttpCloseHandle(connection);
|
||||
WinHttpCloseHandle(session);
|
||||
return None;
|
||||
}
|
||||
|
||||
if WinHttpSendRequest(request, std::ptr::null(), 0, std::ptr::null(), 0, 0, 0) == 0 {
|
||||
WinHttpCloseHandle(request);
|
||||
WinHttpCloseHandle(connection);
|
||||
WinHttpCloseHandle(session);
|
||||
return None;
|
||||
}
|
||||
|
||||
if WinHttpReceiveResponse(request, std::ptr::null_mut()) == 0 {
|
||||
WinHttpCloseHandle(request);
|
||||
WinHttpCloseHandle(connection);
|
||||
WinHttpCloseHandle(session);
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut response: Vec<u8> = Vec::new();
|
||||
loop {
|
||||
let mut available: u32 = 0;
|
||||
if WinHttpQueryDataAvailable(request, &mut available) == 0 || available == 0 {
|
||||
break;
|
||||
}
|
||||
let offset = response.len();
|
||||
response.resize(offset + available as usize, 0);
|
||||
let mut read: u32 = 0;
|
||||
if WinHttpReadData(
|
||||
request,
|
||||
response[offset..].as_mut_ptr() as *mut _,
|
||||
available,
|
||||
&mut read,
|
||||
) == 0
|
||||
{
|
||||
break;
|
||||
}
|
||||
response.truncate(offset + read as usize);
|
||||
}
|
||||
|
||||
WinHttpCloseHandle(request);
|
||||
WinHttpCloseHandle(connection);
|
||||
WinHttpCloseHandle(session);
|
||||
|
||||
String::from_utf8(response).ok()
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract a JSON string value for the given key from a JSON object.
|
||||
/// Handles the escape sequences that appear in GitHub API responses.
|
||||
fn extract_json_string(json: &str, key: &str) -> Option<String> {
|
||||
let search = format!("\"{}\"", key);
|
||||
let key_pos = json.find(&search)?;
|
||||
let rest = &json[key_pos + search.len()..];
|
||||
let colon = rest.find(':')?;
|
||||
let rest = rest[colon + 1..].trim_start();
|
||||
if !rest.starts_with('"') {
|
||||
return None;
|
||||
}
|
||||
let rest = &rest[1..];
|
||||
let mut result = String::new();
|
||||
let mut chars = rest.chars();
|
||||
loop {
|
||||
match chars.next()? {
|
||||
'"' => break,
|
||||
'\\' => match chars.next()? {
|
||||
'"' => result.push('"'),
|
||||
'\\' => result.push('\\'),
|
||||
'/' => result.push('/'),
|
||||
'n' => result.push('\n'),
|
||||
'r' => result.push('\r'),
|
||||
't' => result.push('\t'),
|
||||
c => {
|
||||
result.push('\\');
|
||||
result.push(c);
|
||||
}
|
||||
},
|
||||
c => result.push(c),
|
||||
}
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user