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)
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,12 @@ fn main() {
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
"#);
|
||||
let _ = res.compile();
|
||||
|
||||
@@ -54,6 +54,7 @@ pub struct ProgressState {
|
||||
|
||||
/// Full application state for the installer form.
|
||||
pub struct InstallForm {
|
||||
scale: f32,
|
||||
pub panel: Panel,
|
||||
// Header
|
||||
pub title_text: String,
|
||||
@@ -109,7 +110,24 @@ pub struct InstallForm {
|
||||
}
|
||||
|
||||
impl InstallForm {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(scale: f32) -> Self {
|
||||
// Integer coordinate scaler.
|
||||
let s = |v: i32| (v as f32 * scale).round() as i32;
|
||||
// Font size / spacing scaler.
|
||||
let sf = |v: f32| v * scale;
|
||||
// Scaled label helper: font_size is the logical total (base+offset).
|
||||
let lbl = |x: i32, y: i32, text: &str, size: f32| {
|
||||
let mut l = Label::new(s(x), s(y), text, sf(size));
|
||||
l.line_spacing = sf(2.0);
|
||||
l
|
||||
};
|
||||
// Scaled button helper.
|
||||
let btn = |x: i32, y: i32, w: i32, h: i32, text: &str| GradientButton {
|
||||
font_size: sf(13.0),
|
||||
..GradientButton::new(s(x), s(y), s(w), s(h), text)
|
||||
};
|
||||
// Scaled checkbox helper.
|
||||
let chk = |x: i32, y: i32| ImageCheckBox::with_size(s(x), s(y), s(21));
|
||||
let version = update_checker::FLASH_VERSION;
|
||||
let title_text = "Clean Flash Player".to_string();
|
||||
let subtitle_text = format!("built from version {} (China)", version);
|
||||
@@ -123,102 +141,63 @@ impl InstallForm {
|
||||
let fonts = FontManager::new();
|
||||
|
||||
Self {
|
||||
scale,
|
||||
panel: Panel::Disclaimer,
|
||||
title_text,
|
||||
subtitle_text,
|
||||
flash_logo,
|
||||
checkbox_on,
|
||||
checkbox_off,
|
||||
prev_button: GradientButton::new(90, 286, 138, 31, "QUIT"),
|
||||
next_button: GradientButton::new(497, 286, 138, 31, "AGREE"),
|
||||
prev_button: btn(90, 286, 138, 31, "QUIT"),
|
||||
next_button: btn(497, 286, 138, 31, "AGREE"),
|
||||
// Disclaimer panel
|
||||
disclaimer_label: Label::new(PANEL_X + 25, PANEL_Y, DISCLAIMER_TEXT, 13.0),
|
||||
disclaimer_box: ImageCheckBox::new(PANEL_X, PANEL_Y),
|
||||
disclaimer_label: lbl(PANEL_X + 25, PANEL_Y, DISCLAIMER_TEXT, 15.0),
|
||||
disclaimer_box: chk(PANEL_X, PANEL_Y),
|
||||
// Choice panel
|
||||
browser_ask_label: Label::new(
|
||||
PANEL_X - 2,
|
||||
PANEL_Y + 2,
|
||||
"Which browser plugins would you like to install?",
|
||||
13.0,
|
||||
),
|
||||
pepper_box: ImageCheckBox::new(PANEL_X, PANEL_Y + 47),
|
||||
pepper_label: Label::new(
|
||||
PANEL_X + 24,
|
||||
PANEL_Y + 47,
|
||||
"Pepper API (PPAPI)\n(Chrome/Opera/Brave)",
|
||||
13.0,
|
||||
),
|
||||
netscape_box: ImageCheckBox::new(PANEL_X + 186, PANEL_Y + 47),
|
||||
netscape_label: Label::new(
|
||||
PANEL_X + 210,
|
||||
PANEL_Y + 47,
|
||||
"Netscape API (NPAPI)\n(Firefox/ESR/Waterfox)",
|
||||
13.0,
|
||||
),
|
||||
activex_box: ImageCheckBox::new(PANEL_X + 365, PANEL_Y + 47),
|
||||
activex_label: Label::new(
|
||||
PANEL_X + 389,
|
||||
PANEL_Y + 47,
|
||||
"ActiveX (OCX)\n(IE/Embedded/Desktop)",
|
||||
13.0,
|
||||
),
|
||||
browser_ask_label: lbl(PANEL_X - 2, PANEL_Y + 2, "Which browser plugins would you like to install?", 15.0),
|
||||
pepper_box: chk(PANEL_X, PANEL_Y + 47),
|
||||
pepper_label: lbl(PANEL_X + 24, PANEL_Y + 47, "Pepper API (PPAPI)\n(Chrome/Opera/Brave)", 15.0),
|
||||
netscape_box: chk(PANEL_X + 186, PANEL_Y + 47),
|
||||
netscape_label: lbl(PANEL_X + 210, PANEL_Y + 47, "Netscape API (NPAPI)\n(Firefox/ESR/Waterfox)", 15.0),
|
||||
activex_box: chk(PANEL_X + 365, PANEL_Y + 47),
|
||||
activex_label: lbl(PANEL_X + 389, PANEL_Y + 47, "ActiveX (OCX)\n(IE/Embedded/Desktop)", 15.0),
|
||||
// Player choice panel
|
||||
player_ask_label: Label::new(
|
||||
PANEL_X - 2,
|
||||
PANEL_Y + 2,
|
||||
"Would you like to install the standalone Flash Player?",
|
||||
13.0,
|
||||
),
|
||||
player_box: ImageCheckBox::new(PANEL_X, PANEL_Y + 47),
|
||||
player_label: Label::new(
|
||||
PANEL_X + 24,
|
||||
PANEL_Y + 47,
|
||||
"Install Standalone\nFlash Player",
|
||||
13.0,
|
||||
),
|
||||
player_desktop_box: ImageCheckBox::new(PANEL_X + 186, PANEL_Y + 47),
|
||||
player_desktop_label: Label::new(
|
||||
PANEL_X + 210,
|
||||
PANEL_Y + 47,
|
||||
"Create Shortcuts\non Desktop",
|
||||
13.0,
|
||||
),
|
||||
player_start_menu_box: ImageCheckBox::new(PANEL_X + 365, PANEL_Y + 47),
|
||||
player_start_menu_label: Label::new(
|
||||
PANEL_X + 389,
|
||||
PANEL_Y + 47,
|
||||
"Create Shortcuts\nin Start Menu",
|
||||
13.0,
|
||||
),
|
||||
player_ask_label: lbl(PANEL_X - 2, PANEL_Y + 2, "Would you like to install the standalone Flash Player?", 15.0),
|
||||
player_box: chk(PANEL_X, PANEL_Y + 47),
|
||||
player_label: lbl(PANEL_X + 24, PANEL_Y + 47, "Install Standalone\nFlash Player", 15.0),
|
||||
player_desktop_box: chk(PANEL_X + 186, PANEL_Y + 47),
|
||||
player_desktop_label: lbl(PANEL_X + 210, PANEL_Y + 47, "Create Shortcuts\non Desktop", 15.0),
|
||||
player_start_menu_box: chk(PANEL_X + 365, PANEL_Y + 47),
|
||||
player_start_menu_label: lbl(PANEL_X + 389, PANEL_Y + 47, "Create Shortcuts\nin Start Menu", 15.0),
|
||||
// Debug choice panel
|
||||
debug_ask_label: Label::new(
|
||||
debug_ask_label: lbl(
|
||||
PANEL_X - 2,
|
||||
PANEL_Y + 2,
|
||||
"Would you like to install the debug version of Clean Flash Player?\n\
|
||||
You should only choose the debug version if you are planning to create Flash applications.\n\
|
||||
If you are not sure, simply press NEXT.",
|
||||
13.0,
|
||||
15.0,
|
||||
),
|
||||
debug_button: GradientButton::new(PANEL_X + 186, PANEL_Y + 65, 176, 31, "INSTALL DEBUG VERSION"),
|
||||
debug_button: btn(PANEL_X + 186, PANEL_Y + 65, 176, 31, "INSTALL DEBUG VERSION"),
|
||||
debug_chosen: false,
|
||||
// Before install panel
|
||||
before_install_label: Label::new(PANEL_X + 3, PANEL_Y + 2, "", 13.0),
|
||||
before_install_label: lbl(PANEL_X + 3, PANEL_Y + 2, "", 15.0),
|
||||
// Install panel
|
||||
install_header_label: Label::new(PANEL_X + 3, PANEL_Y, "Installation in progress...", 13.0),
|
||||
progress_label: Label::new(PANEL_X + 46, PANEL_Y + 30, "Preparing...", 13.0),
|
||||
progress_bar: ProgressBar::new(PANEL_X + 49, PANEL_Y + 58, 451, 23),
|
||||
install_header_label: lbl(PANEL_X + 3, PANEL_Y, "Installation in progress...", 15.0),
|
||||
progress_label: lbl(PANEL_X + 46, PANEL_Y + 30, "Preparing...", 15.0),
|
||||
progress_bar: ProgressBar::new(s(PANEL_X + 49), s(PANEL_Y + 58), s(451), s(23)),
|
||||
// Complete panel
|
||||
complete_label: Label::new(PANEL_X, PANEL_Y, "", 13.0),
|
||||
complete_label: lbl(PANEL_X, PANEL_Y, "", 15.0),
|
||||
// Failure panel
|
||||
failure_text_label: Label::new(
|
||||
failure_text_label: lbl(
|
||||
PANEL_X + 3,
|
||||
PANEL_Y + 2,
|
||||
"Oops! The installation process has encountered an unexpected problem.\n\
|
||||
The following details could be useful. Press the Retry button to try again.",
|
||||
13.0,
|
||||
15.0,
|
||||
),
|
||||
failure_detail: String::new(),
|
||||
copy_error_button: GradientButton::new(PANEL_X + 441, PANEL_Y + 58, 104, 31, "COPY"),
|
||||
copy_error_button: btn(PANEL_X + 441, PANEL_Y + 58, 104, 31, "COPY"),
|
||||
progress_state: Arc::new(Mutex::new(ProgressState {
|
||||
label: "Preparing...".into(),
|
||||
value: 0,
|
||||
@@ -230,6 +209,11 @@ The following details could be useful. Press the Retry button to try again.",
|
||||
}
|
||||
}
|
||||
|
||||
/// Scale a logical integer coordinate to physical pixels.
|
||||
fn s(&self, v: i32) -> i32 { (v as f32 * self.scale).round() as i32 }
|
||||
/// Scale a logical float value to physical pixels.
|
||||
fn sf(&self, v: f32) -> f32 { v * self.scale }
|
||||
|
||||
/// Called each frame: handle input, update state, draw.
|
||||
pub fn update_and_draw(
|
||||
&mut self,
|
||||
@@ -257,30 +241,32 @@ The following details could be useful. Press the Retry button to try again.",
|
||||
renderer.clear(BG_COLOR);
|
||||
|
||||
// Header: flash logo.
|
||||
renderer.draw_image(90, 36, &self.flash_logo);
|
||||
let lw = (self.flash_logo.width as f32 * self.scale) as i32;
|
||||
let lh = (self.flash_logo.height as f32 * self.scale) as i32;
|
||||
renderer.draw_image_scaled(self.s(90), self.s(36), lw, lh, &self.flash_logo);
|
||||
|
||||
// Title.
|
||||
self.fonts.draw_text(
|
||||
renderer,
|
||||
233,
|
||||
54,
|
||||
self.s(233),
|
||||
self.s(54),
|
||||
&self.title_text,
|
||||
32.0, // ~24pt Segoe UI
|
||||
self.sf(32.0),
|
||||
FG_COLOR,
|
||||
);
|
||||
|
||||
// Subtitle.
|
||||
self.fonts.draw_text(
|
||||
renderer,
|
||||
280,
|
||||
99,
|
||||
self.s(280),
|
||||
self.s(99),
|
||||
&self.subtitle_text,
|
||||
17.0, // ~13pt Segoe UI
|
||||
self.sf(17.0),
|
||||
FG_COLOR,
|
||||
);
|
||||
|
||||
// Separator line at y=270.
|
||||
renderer.fill_rect(0, 270, WIDTH as i32, 1, Renderer::rgb(105, 105, 105));
|
||||
// Separator line.
|
||||
renderer.fill_rect(0, self.s(270), renderer.width as i32, self.s(1).max(1), Renderer::rgb(105, 105, 105));
|
||||
|
||||
// Draw current panel.
|
||||
match self.panel {
|
||||
@@ -628,7 +614,6 @@ including Clean Flash Player and older versions of Adobe Flash Player."
|
||||
fn draw_failure(&self, r: &mut Renderer) {
|
||||
self.failure_text_label.draw(r, &self.fonts);
|
||||
// Draw error detail as clipped text.
|
||||
let detail_y = PANEL_Y + 44;
|
||||
let detail_text = if self.failure_detail.len() > 300 {
|
||||
&self.failure_detail[..300]
|
||||
} else {
|
||||
@@ -636,12 +621,12 @@ including Clean Flash Player and older versions of Adobe Flash Player."
|
||||
};
|
||||
self.fonts.draw_text_multiline(
|
||||
r,
|
||||
PANEL_X + 4,
|
||||
detail_y,
|
||||
self.s(PANEL_X + 4),
|
||||
self.s(PANEL_Y + 44),
|
||||
detail_text,
|
||||
11.0,
|
||||
self.sf(11.0),
|
||||
FG_COLOR,
|
||||
1.0,
|
||||
self.sf(1.0),
|
||||
);
|
||||
self.copy_error_button.draw(r, &self.fonts);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![windows_subsystem = "windows"]
|
||||
//#![windows_subsystem = "windows"]
|
||||
|
||||
mod install_flags;
|
||||
mod install_form;
|
||||
@@ -14,10 +14,16 @@ fn main() {
|
||||
clean_flash_common::update_checker::FLASH_VERSION
|
||||
);
|
||||
|
||||
// Query DPI before creating any window. The manifest already marks the process
|
||||
// as per-monitor v2 DPI aware, so GetDpiForSystem returns the real hardware DPI.
|
||||
let scale = clean_flash_ui::get_dpi_scale();
|
||||
let phys_w = (WIDTH as f32 * scale).round() as usize;
|
||||
let phys_h = (HEIGHT as f32 * scale).round() as usize;
|
||||
|
||||
let mut window = Window::new(
|
||||
&title,
|
||||
WIDTH,
|
||||
HEIGHT,
|
||||
phys_w,
|
||||
phys_h,
|
||||
WindowOptions {
|
||||
resize: false,
|
||||
..WindowOptions::default()
|
||||
@@ -31,8 +37,9 @@ fn main() {
|
||||
// Cap at ~60 fps.
|
||||
window.set_target_fps(60);
|
||||
|
||||
let mut renderer = Renderer::new(WIDTH, HEIGHT);
|
||||
let mut form = InstallForm::new();
|
||||
// Renderer operates at physical resolution; the form layout is scaled accordingly.
|
||||
let mut renderer = Renderer::new(phys_w, phys_h);
|
||||
let mut form = InstallForm::new(scale);
|
||||
|
||||
while window.is_open() && !window.is_key_down(Key::Escape) {
|
||||
let (mx, my) = window
|
||||
@@ -43,7 +50,7 @@ fn main() {
|
||||
form.update_and_draw(&mut renderer, mx as i32, my as i32, mouse_down);
|
||||
|
||||
window
|
||||
.update_with_buffer(&renderer.buffer, WIDTH, HEIGHT)
|
||||
.update_with_buffer(&renderer.buffer, phys_w, phys_h)
|
||||
.expect("Failed to update window buffer");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,20 @@ pub mod widgets;
|
||||
pub use font::FontManager;
|
||||
pub use renderer::Renderer;
|
||||
|
||||
/// Query the system DPI scale factor (1.0 = 100%, 1.5 = 150%, 2.0 = 200%).
|
||||
/// The process must already be DPI-aware (declared in the manifest) for this
|
||||
/// to return the true hardware DPI rather than the virtualised 96.
|
||||
pub fn get_dpi_scale() -> f32 {
|
||||
#[cfg(target_os = "windows")]
|
||||
unsafe {
|
||||
use windows_sys::Win32::UI::HiDpi::GetDpiForSystem;
|
||||
let dpi = GetDpiForSystem();
|
||||
if dpi == 0 { 1.0 } else { dpi as f32 / 96.0 }
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{ 1.0 }
|
||||
}
|
||||
|
||||
/// Set the window icon from the icon resource already embedded in the binary
|
||||
/// by `winresource` (resource ID 1). No-op on non-Windows platforms.
|
||||
pub fn set_window_icon(window: &minifb::Window) {
|
||||
|
||||
@@ -109,6 +109,29 @@ impl Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw an RGBA image scaled to (w, h) at (x, y) using nearest-neighbor interpolation.
|
||||
pub fn draw_image_scaled(&mut self, x: i32, y: i32, w: i32, h: i32, img: &RgbaImage) {
|
||||
if w <= 0 || h <= 0 || img.width == 0 || img.height == 0 {
|
||||
return;
|
||||
}
|
||||
for dy in 0..h {
|
||||
for dx in 0..w {
|
||||
let src_x = (dx as f32 * img.width as f32 / w as f32) as usize;
|
||||
let src_y = (dy as f32 * img.height as f32 / h as f32) as usize;
|
||||
let idx = (src_y * img.width + src_x) * 4;
|
||||
let r = img.data[idx];
|
||||
let g = img.data[idx + 1];
|
||||
let b = img.data[idx + 2];
|
||||
let a = img.data[idx + 3];
|
||||
if a == 255 {
|
||||
self.set_pixel(x + dx, y + dy, Self::rgb(r, g, b));
|
||||
} else if a > 0 {
|
||||
self.blend_pixel(x + dx, y + dy, Self::rgb(r, g, b), a);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw an RGBA image onto the framebuffer at (x, y).
|
||||
pub fn draw_image(&mut self, x: i32, y: i32, img: &RgbaImage) {
|
||||
for iy in 0..img.height as i32 {
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::renderer::Renderer;
|
||||
pub struct GradientButton {
|
||||
pub rect: Rect,
|
||||
pub text: String,
|
||||
pub font_size: f32,
|
||||
pub color1: u32,
|
||||
pub color2: u32,
|
||||
pub back_color: u32,
|
||||
@@ -23,6 +24,7 @@ impl GradientButton {
|
||||
Self {
|
||||
rect: Rect::new(x, y, w, h),
|
||||
text: text.to_string(),
|
||||
font_size: 13.0,
|
||||
color1: Renderer::rgb(118, 118, 118),
|
||||
color2: Renderer::rgb(81, 81, 81),
|
||||
back_color: Renderer::rgb(0, 0, 0),
|
||||
@@ -77,7 +79,7 @@ impl GradientButton {
|
||||
renderer.draw_rect(r.x, r.y, r.w, r.h, bg);
|
||||
|
||||
// Measure text to centre it.
|
||||
let font_size = 13.0;
|
||||
let font_size = self.font_size;
|
||||
let (tw, th) = fonts.measure_text(&self.text, font_size);
|
||||
let tx = r.x + ((r.w as f32 - tw) / 2.0) as i32;
|
||||
let ty = r.y + ((r.h as f32 - th) / 2.0) as i32;
|
||||
|
||||
@@ -11,8 +11,12 @@ pub struct ImageCheckBox {
|
||||
|
||||
impl ImageCheckBox {
|
||||
pub fn new(x: i32, y: i32) -> Self {
|
||||
Self::with_size(x, y, 21)
|
||||
}
|
||||
|
||||
pub fn with_size(x: i32, y: i32, size: i32) -> Self {
|
||||
Self {
|
||||
rect: Rect::new(x, y, 21, 21),
|
||||
rect: Rect::new(x, y, size, size),
|
||||
checked: true,
|
||||
enabled: true,
|
||||
visible: true,
|
||||
@@ -43,7 +47,7 @@ impl ImageCheckBox {
|
||||
unchecked_img
|
||||
};
|
||||
if img.width > 0 && img.height > 0 {
|
||||
renderer.draw_image(self.rect.x, self.rect.y, img);
|
||||
renderer.draw_image_scaled(self.rect.x, self.rect.y, self.rect.w, self.rect.h, img);
|
||||
} else {
|
||||
// Fallback: draw a simple square.
|
||||
let bg = if self.checked {
|
||||
|
||||
@@ -8,6 +8,7 @@ pub struct Label {
|
||||
pub text: String,
|
||||
pub color: u32,
|
||||
pub font_size: f32,
|
||||
pub line_spacing: f32,
|
||||
pub visible: bool,
|
||||
}
|
||||
|
||||
@@ -18,6 +19,7 @@ impl Label {
|
||||
text: text.to_string(),
|
||||
color: Renderer::rgb(245, 245, 245),
|
||||
font_size,
|
||||
line_spacing: 2.0,
|
||||
visible: true,
|
||||
}
|
||||
}
|
||||
@@ -33,7 +35,7 @@ impl Label {
|
||||
&self.text,
|
||||
self.font_size,
|
||||
self.color,
|
||||
2.0,
|
||||
self.line_spacing,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,12 @@ fn main() {
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
"#);
|
||||
let _ = res.compile();
|
||||
|
||||
@@ -12,10 +12,14 @@ fn main() {
|
||||
clean_flash_common::update_checker::FLASH_VERSION
|
||||
);
|
||||
|
||||
let scale = clean_flash_ui::get_dpi_scale();
|
||||
let phys_w = (WIDTH as f32 * scale).round() as usize;
|
||||
let phys_h = (HEIGHT as f32 * scale).round() as usize;
|
||||
|
||||
let mut window = Window::new(
|
||||
&title,
|
||||
WIDTH,
|
||||
HEIGHT,
|
||||
phys_w,
|
||||
phys_h,
|
||||
WindowOptions {
|
||||
resize: false,
|
||||
..WindowOptions::default()
|
||||
@@ -28,8 +32,8 @@ fn main() {
|
||||
|
||||
window.set_target_fps(60);
|
||||
|
||||
let mut renderer = Renderer::new(WIDTH, HEIGHT);
|
||||
let mut form = UninstallForm::new();
|
||||
let mut renderer = Renderer::new(phys_w, phys_h);
|
||||
let mut form = UninstallForm::new(scale);
|
||||
|
||||
while window.is_open() && !window.is_key_down(Key::Escape) {
|
||||
let (mx, my) = window
|
||||
@@ -40,7 +44,7 @@ fn main() {
|
||||
form.update_and_draw(&mut renderer, mx as i32, my as i32, mouse_down);
|
||||
|
||||
window
|
||||
.update_with_buffer(&renderer.buffer, WIDTH, HEIGHT)
|
||||
.update_with_buffer(&renderer.buffer, phys_w, phys_h)
|
||||
.expect("Failed to update window buffer");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ struct ProgressState {
|
||||
}
|
||||
|
||||
pub struct UninstallForm {
|
||||
scale: f32,
|
||||
panel: Panel,
|
||||
title_text: String,
|
||||
subtitle_text: String,
|
||||
@@ -64,37 +65,45 @@ pub struct UninstallForm {
|
||||
}
|
||||
|
||||
impl UninstallForm {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(scale: f32) -> Self {
|
||||
let s = |v: i32| (v as f32 * scale).round() as i32;
|
||||
let sf = |v: f32| v * scale;
|
||||
let lbl = |x: i32, y: i32, text: &str, size: f32| {
|
||||
let mut l = Label::new(s(x), s(y), text, sf(size));
|
||||
l.line_spacing = sf(2.0);
|
||||
l
|
||||
};
|
||||
let btn = |x: i32, y: i32, w: i32, h: i32, text: &str| GradientButton {
|
||||
font_size: sf(13.0),
|
||||
..GradientButton::new(s(x), s(y), s(w), s(h), text)
|
||||
};
|
||||
|
||||
let version = update_checker::FLASH_VERSION;
|
||||
let flash_logo = load_resource_image("flashLogo.png");
|
||||
let fonts = FontManager::new();
|
||||
|
||||
Self {
|
||||
scale,
|
||||
panel: Panel::BeforeInstall,
|
||||
title_text: "Clean Flash Player".into(),
|
||||
subtitle_text: format!("built from version {} (China)", version),
|
||||
flash_logo,
|
||||
prev_button: GradientButton::new(90, 286, 138, 31, "QUIT"),
|
||||
next_button: GradientButton::new(497, 286, 138, 31, "UNINSTALL"),
|
||||
before_label: Label::new(PANEL_X + 3, PANEL_Y + 2, BEFORE_TEXT, 13.0),
|
||||
progress_header: Label::new(
|
||||
PANEL_X + 3,
|
||||
PANEL_Y,
|
||||
"Uninstallation in progress...",
|
||||
13.0,
|
||||
),
|
||||
progress_label: Label::new(PANEL_X + 46, PANEL_Y + 30, "Preparing...", 13.0),
|
||||
progress_bar: ProgressBar::new(PANEL_X + 49, PANEL_Y + 58, 451, 23),
|
||||
complete_label: Label::new(PANEL_X, PANEL_Y, COMPLETE_TEXT, 13.0),
|
||||
failure_text_label: Label::new(
|
||||
prev_button: btn(90, 286, 138, 31, "QUIT"),
|
||||
next_button: btn(497, 286, 138, 31, "UNINSTALL"),
|
||||
before_label: lbl(PANEL_X + 3, PANEL_Y + 2, BEFORE_TEXT, 15.0),
|
||||
progress_header: lbl(PANEL_X + 3, PANEL_Y, "Uninstallation in progress...", 15.0),
|
||||
progress_label: lbl(PANEL_X + 46, PANEL_Y + 30, "Preparing...", 15.0),
|
||||
progress_bar: ProgressBar::new(s(PANEL_X + 49), s(PANEL_Y + 58), s(451), s(23)),
|
||||
complete_label: lbl(PANEL_X, PANEL_Y, COMPLETE_TEXT, 15.0),
|
||||
failure_text_label: lbl(
|
||||
PANEL_X + 3,
|
||||
PANEL_Y + 2,
|
||||
"Oops! The installation process has encountered an unexpected problem.\n\
|
||||
The following details could be useful. Press the Retry button to try again.",
|
||||
13.0,
|
||||
15.0,
|
||||
),
|
||||
failure_detail: String::new(),
|
||||
copy_error_button: GradientButton::new(PANEL_X + 441, PANEL_Y + 58, 104, 31, "COPY"),
|
||||
copy_error_button: btn(PANEL_X + 441, PANEL_Y + 58, 104, 31, "COPY"),
|
||||
progress_state: Arc::new(Mutex::new(ProgressState {
|
||||
label: "Preparing...".into(),
|
||||
value: 0,
|
||||
@@ -147,15 +156,17 @@ The following details could be useful. Press the Retry button to try again.",
|
||||
|
||||
// Draw.
|
||||
renderer.clear(BG_COLOR);
|
||||
renderer.draw_image(90, 36, &self.flash_logo);
|
||||
let lw = (self.flash_logo.width as f32 * self.scale) as i32;
|
||||
let lh = (self.flash_logo.height as f32 * self.scale) as i32;
|
||||
renderer.draw_image_scaled(self.s(90), self.s(36), lw, lh, &self.flash_logo);
|
||||
|
||||
self.fonts
|
||||
.draw_text(renderer, 233, 54, &self.title_text, 32.0, FG_COLOR);
|
||||
.draw_text(renderer, self.s(233), self.s(54), &self.title_text, self.sf(32.0), FG_COLOR);
|
||||
self.fonts
|
||||
.draw_text(renderer, 280, 99, &self.subtitle_text, 17.0, FG_COLOR);
|
||||
.draw_text(renderer, self.s(280), self.s(99), &self.subtitle_text, self.sf(17.0), FG_COLOR);
|
||||
|
||||
// Separator.
|
||||
renderer.fill_rect(0, 270, WIDTH as i32, 1, 0x00696969);
|
||||
renderer.fill_rect(0, self.s(270), renderer.width as i32, self.s(1).max(1), 0x00696969);
|
||||
|
||||
match self.panel {
|
||||
Panel::BeforeInstall => self.before_label.draw(renderer, &self.fonts),
|
||||
@@ -174,12 +185,12 @@ The following details could be useful. Press the Retry button to try again.",
|
||||
};
|
||||
self.fonts.draw_text_multiline(
|
||||
renderer,
|
||||
PANEL_X + 4,
|
||||
PANEL_Y + 44,
|
||||
self.s(PANEL_X + 4),
|
||||
self.s(PANEL_Y + 44),
|
||||
detail,
|
||||
11.0,
|
||||
self.sf(11.0),
|
||||
FG_COLOR,
|
||||
1.0,
|
||||
self.sf(1.0),
|
||||
);
|
||||
self.copy_error_button.draw(renderer, &self.fonts);
|
||||
}
|
||||
@@ -189,6 +200,9 @@ The following details could be useful. Press the Retry button to try again.",
|
||||
self.next_button.draw(renderer, &self.fonts);
|
||||
}
|
||||
|
||||
fn s(&self, v: i32) -> i32 { (v as f32 * self.scale).round() as i32 }
|
||||
fn sf(&self, v: f32) -> f32 { v * self.scale }
|
||||
|
||||
fn start_uninstall(&mut self) {
|
||||
self.panel = Panel::Install;
|
||||
self.prev_button.enabled = false;
|
||||
|
||||
Reference in New Issue
Block a user