mirror of
https://gitlab.com/cleanflash/installer.git
synced 2026-08-12 18:31:37 +08:00
Implement HiDPI scaling
This commit is contained in:
@@ -14,13 +14,16 @@ image = { version = "0.25", default-features = false, features = ["png"] }
|
|||||||
minifb = "0.28"
|
minifb = "0.28"
|
||||||
windows-sys = { version = "0.61", features = [
|
windows-sys = { version = "0.61", features = [
|
||||||
"Win32_Foundation",
|
"Win32_Foundation",
|
||||||
|
"Win32_Networking_WinHttp",
|
||||||
"Win32_Security",
|
"Win32_Security",
|
||||||
"Win32_Security_Authorization",
|
"Win32_Security_Authorization",
|
||||||
"Win32_System_LibraryLoader",
|
"Win32_System_LibraryLoader",
|
||||||
|
"Win32_System_RestartManager",
|
||||||
"Win32_System_Threading",
|
"Win32_System_Threading",
|
||||||
"Win32_System_ProcessStatus",
|
"Win32_System_ProcessStatus",
|
||||||
"Win32_Storage_FileSystem",
|
"Win32_Storage_FileSystem",
|
||||||
"Win32_System_SystemInformation",
|
"Win32_System_SystemInformation",
|
||||||
|
"Win32_UI_HiDpi",
|
||||||
"Win32_UI_Shell",
|
"Win32_UI_Shell",
|
||||||
"Win32_UI_WindowsAndMessaging",
|
"Win32_UI_WindowsAndMessaging",
|
||||||
] }
|
] }
|
||||||
|
|||||||
@@ -102,16 +102,154 @@ fn try_clear_readonly_and_delete(path: &Path) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn try_take_ownership_and_delete(path: &Path) -> bool {
|
fn try_take_ownership_and_delete(path: &Path) -> bool {
|
||||||
// On Windows we could use SetNamedSecurityInfo to take ownership.
|
use windows_sys::Win32::Foundation::{CloseHandle, LocalFree};
|
||||||
// For simplicity the Rust port clears read-only and retries.
|
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)
|
try_clear_readonly_and_delete(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kill_locking_processes(path: &Path) {
|
fn kill_locking_processes(path: &Path) {
|
||||||
// Use taskkill as a best-effort approach.
|
use windows_sys::Win32::Foundation::CloseHandle;
|
||||||
// The C# original enumerates all open handles, which requires complex
|
use windows_sys::Win32::System::RestartManager::{
|
||||||
// NT API calls. A simplified approach is acceptable for the port.
|
RmEndSession, RmGetList, RmRegisterResources, RmStartSession, RM_PROCESS_INFO,
|
||||||
let _ = path; // Locking-process detection is a best-effort no-op here.
|
};
|
||||||
|
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 {
|
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 content = format!("Windows Registry Editor Version 5.00\n\n{}", filled);
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
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).
|
// 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() {
|
fn delete_flash_player() {
|
||||||
@@ -189,7 +211,10 @@ fn stop_processes() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Enumerate all processes via the snapshot API.
|
// 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 {
|
for (pid, name) in &pids {
|
||||||
let lower = name.to_lowercase();
|
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)> {
|
fn enumerate_processes() -> Vec<(u32, String)> {
|
||||||
use windows_sys::Win32::System::ProcessStatus::{EnumProcesses, GetModuleBaseNameW};
|
use windows_sys::Win32::System::ProcessStatus::{EnumProcesses, GetModuleBaseNameW};
|
||||||
use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ};
|
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 FLASH_VERSION: &str = "34.0.0.330";
|
||||||
pub const 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 struct VersionInfo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
@@ -8,7 +12,138 @@ pub struct VersionInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_latest_version() -> Option<VersionInfo> {
|
pub fn get_latest_version() -> Option<VersionInfo> {
|
||||||
// The original fetches from the GitHub API.
|
let json = fetch_https(API_HOST, API_PATH, USER_AGENT)?;
|
||||||
// Stubbed for the port; real implementation would use ureq or reqwest.
|
let name = extract_json_string(&json, "name")?;
|
||||||
None
|
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}"/>
|
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||||
</application>
|
</application>
|
||||||
</compatibility>
|
</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>
|
</assembly>
|
||||||
"#);
|
"#);
|
||||||
let _ = res.compile();
|
let _ = res.compile();
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ pub struct ProgressState {
|
|||||||
|
|
||||||
/// Full application state for the installer form.
|
/// Full application state for the installer form.
|
||||||
pub struct InstallForm {
|
pub struct InstallForm {
|
||||||
|
scale: f32,
|
||||||
pub panel: Panel,
|
pub panel: Panel,
|
||||||
// Header
|
// Header
|
||||||
pub title_text: String,
|
pub title_text: String,
|
||||||
@@ -109,7 +110,24 @@ pub struct InstallForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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 version = update_checker::FLASH_VERSION;
|
||||||
let title_text = "Clean Flash Player".to_string();
|
let title_text = "Clean Flash Player".to_string();
|
||||||
let subtitle_text = format!("built from version {} (China)", version);
|
let subtitle_text = format!("built from version {} (China)", version);
|
||||||
@@ -123,102 +141,63 @@ impl InstallForm {
|
|||||||
let fonts = FontManager::new();
|
let fonts = FontManager::new();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
scale,
|
||||||
panel: Panel::Disclaimer,
|
panel: Panel::Disclaimer,
|
||||||
title_text,
|
title_text,
|
||||||
subtitle_text,
|
subtitle_text,
|
||||||
flash_logo,
|
flash_logo,
|
||||||
checkbox_on,
|
checkbox_on,
|
||||||
checkbox_off,
|
checkbox_off,
|
||||||
prev_button: GradientButton::new(90, 286, 138, 31, "QUIT"),
|
prev_button: btn(90, 286, 138, 31, "QUIT"),
|
||||||
next_button: GradientButton::new(497, 286, 138, 31, "AGREE"),
|
next_button: btn(497, 286, 138, 31, "AGREE"),
|
||||||
// Disclaimer panel
|
// Disclaimer panel
|
||||||
disclaimer_label: Label::new(PANEL_X + 25, PANEL_Y, DISCLAIMER_TEXT, 13.0),
|
disclaimer_label: lbl(PANEL_X + 25, PANEL_Y, DISCLAIMER_TEXT, 15.0),
|
||||||
disclaimer_box: ImageCheckBox::new(PANEL_X, PANEL_Y),
|
disclaimer_box: chk(PANEL_X, PANEL_Y),
|
||||||
// Choice panel
|
// Choice panel
|
||||||
browser_ask_label: Label::new(
|
browser_ask_label: lbl(PANEL_X - 2, PANEL_Y + 2, "Which browser plugins would you like to install?", 15.0),
|
||||||
PANEL_X - 2,
|
pepper_box: chk(PANEL_X, PANEL_Y + 47),
|
||||||
PANEL_Y + 2,
|
pepper_label: lbl(PANEL_X + 24, PANEL_Y + 47, "Pepper API (PPAPI)\n(Chrome/Opera/Brave)", 15.0),
|
||||||
"Which browser plugins would you like to install?",
|
netscape_box: chk(PANEL_X + 186, PANEL_Y + 47),
|
||||||
13.0,
|
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),
|
||||||
pepper_box: ImageCheckBox::new(PANEL_X, PANEL_Y + 47),
|
activex_label: lbl(PANEL_X + 389, PANEL_Y + 47, "ActiveX (OCX)\n(IE/Embedded/Desktop)", 15.0),
|
||||||
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,
|
|
||||||
),
|
|
||||||
// Player choice panel
|
// Player choice panel
|
||||||
player_ask_label: Label::new(
|
player_ask_label: lbl(PANEL_X - 2, PANEL_Y + 2, "Would you like to install the standalone Flash Player?", 15.0),
|
||||||
PANEL_X - 2,
|
player_box: chk(PANEL_X, PANEL_Y + 47),
|
||||||
PANEL_Y + 2,
|
player_label: lbl(PANEL_X + 24, PANEL_Y + 47, "Install Standalone\nFlash Player", 15.0),
|
||||||
"Would you like to install the standalone Flash Player?",
|
player_desktop_box: chk(PANEL_X + 186, PANEL_Y + 47),
|
||||||
13.0,
|
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_box: ImageCheckBox::new(PANEL_X, PANEL_Y + 47),
|
player_start_menu_label: lbl(PANEL_X + 389, PANEL_Y + 47, "Create Shortcuts\nin Start Menu", 15.0),
|
||||||
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,
|
|
||||||
),
|
|
||||||
// Debug choice panel
|
// Debug choice panel
|
||||||
debug_ask_label: Label::new(
|
debug_ask_label: lbl(
|
||||||
PANEL_X - 2,
|
PANEL_X - 2,
|
||||||
PANEL_Y + 2,
|
PANEL_Y + 2,
|
||||||
"Would you like to install the debug version of Clean Flash Player?\n\
|
"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\
|
You should only choose the debug version if you are planning to create Flash applications.\n\
|
||||||
If you are not sure, simply press NEXT.",
|
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,
|
debug_chosen: false,
|
||||||
// Before install panel
|
// 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 panel
|
||||||
install_header_label: Label::new(PANEL_X + 3, PANEL_Y, "Installation in progress...", 13.0),
|
install_header_label: lbl(PANEL_X + 3, PANEL_Y, "Installation in progress...", 15.0),
|
||||||
progress_label: Label::new(PANEL_X + 46, PANEL_Y + 30, "Preparing...", 13.0),
|
progress_label: lbl(PANEL_X + 46, PANEL_Y + 30, "Preparing...", 15.0),
|
||||||
progress_bar: ProgressBar::new(PANEL_X + 49, PANEL_Y + 58, 451, 23),
|
progress_bar: ProgressBar::new(s(PANEL_X + 49), s(PANEL_Y + 58), s(451), s(23)),
|
||||||
// Complete panel
|
// Complete panel
|
||||||
complete_label: Label::new(PANEL_X, PANEL_Y, "", 13.0),
|
complete_label: lbl(PANEL_X, PANEL_Y, "", 15.0),
|
||||||
// Failure panel
|
// Failure panel
|
||||||
failure_text_label: Label::new(
|
failure_text_label: lbl(
|
||||||
PANEL_X + 3,
|
PANEL_X + 3,
|
||||||
PANEL_Y + 2,
|
PANEL_Y + 2,
|
||||||
"Oops! The installation process has encountered an unexpected problem.\n\
|
"Oops! The installation process has encountered an unexpected problem.\n\
|
||||||
The following details could be useful. Press the Retry button to try again.",
|
The following details could be useful. Press the Retry button to try again.",
|
||||||
13.0,
|
15.0,
|
||||||
),
|
),
|
||||||
failure_detail: String::new(),
|
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 {
|
progress_state: Arc::new(Mutex::new(ProgressState {
|
||||||
label: "Preparing...".into(),
|
label: "Preparing...".into(),
|
||||||
value: 0,
|
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.
|
/// Called each frame: handle input, update state, draw.
|
||||||
pub fn update_and_draw(
|
pub fn update_and_draw(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -257,30 +241,32 @@ The following details could be useful. Press the Retry button to try again.",
|
|||||||
renderer.clear(BG_COLOR);
|
renderer.clear(BG_COLOR);
|
||||||
|
|
||||||
// Header: flash logo.
|
// 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.
|
// Title.
|
||||||
self.fonts.draw_text(
|
self.fonts.draw_text(
|
||||||
renderer,
|
renderer,
|
||||||
233,
|
self.s(233),
|
||||||
54,
|
self.s(54),
|
||||||
&self.title_text,
|
&self.title_text,
|
||||||
32.0, // ~24pt Segoe UI
|
self.sf(32.0),
|
||||||
FG_COLOR,
|
FG_COLOR,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Subtitle.
|
// Subtitle.
|
||||||
self.fonts.draw_text(
|
self.fonts.draw_text(
|
||||||
renderer,
|
renderer,
|
||||||
280,
|
self.s(280),
|
||||||
99,
|
self.s(99),
|
||||||
&self.subtitle_text,
|
&self.subtitle_text,
|
||||||
17.0, // ~13pt Segoe UI
|
self.sf(17.0),
|
||||||
FG_COLOR,
|
FG_COLOR,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Separator line at y=270.
|
// Separator line.
|
||||||
renderer.fill_rect(0, 270, WIDTH as i32, 1, Renderer::rgb(105, 105, 105));
|
renderer.fill_rect(0, self.s(270), renderer.width as i32, self.s(1).max(1), Renderer::rgb(105, 105, 105));
|
||||||
|
|
||||||
// Draw current panel.
|
// Draw current panel.
|
||||||
match self.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) {
|
fn draw_failure(&self, r: &mut Renderer) {
|
||||||
self.failure_text_label.draw(r, &self.fonts);
|
self.failure_text_label.draw(r, &self.fonts);
|
||||||
// Draw error detail as clipped text.
|
// Draw error detail as clipped text.
|
||||||
let detail_y = PANEL_Y + 44;
|
|
||||||
let detail_text = if self.failure_detail.len() > 300 {
|
let detail_text = if self.failure_detail.len() > 300 {
|
||||||
&self.failure_detail[..300]
|
&self.failure_detail[..300]
|
||||||
} else {
|
} else {
|
||||||
@@ -636,12 +621,12 @@ including Clean Flash Player and older versions of Adobe Flash Player."
|
|||||||
};
|
};
|
||||||
self.fonts.draw_text_multiline(
|
self.fonts.draw_text_multiline(
|
||||||
r,
|
r,
|
||||||
PANEL_X + 4,
|
self.s(PANEL_X + 4),
|
||||||
detail_y,
|
self.s(PANEL_Y + 44),
|
||||||
detail_text,
|
detail_text,
|
||||||
11.0,
|
self.sf(11.0),
|
||||||
FG_COLOR,
|
FG_COLOR,
|
||||||
1.0,
|
self.sf(1.0),
|
||||||
);
|
);
|
||||||
self.copy_error_button.draw(r, &self.fonts);
|
self.copy_error_button.draw(r, &self.fonts);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#![windows_subsystem = "windows"]
|
//#![windows_subsystem = "windows"]
|
||||||
|
|
||||||
mod install_flags;
|
mod install_flags;
|
||||||
mod install_form;
|
mod install_form;
|
||||||
@@ -14,10 +14,16 @@ fn main() {
|
|||||||
clean_flash_common::update_checker::FLASH_VERSION
|
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(
|
let mut window = Window::new(
|
||||||
&title,
|
&title,
|
||||||
WIDTH,
|
phys_w,
|
||||||
HEIGHT,
|
phys_h,
|
||||||
WindowOptions {
|
WindowOptions {
|
||||||
resize: false,
|
resize: false,
|
||||||
..WindowOptions::default()
|
..WindowOptions::default()
|
||||||
@@ -31,8 +37,9 @@ fn main() {
|
|||||||
// Cap at ~60 fps.
|
// Cap at ~60 fps.
|
||||||
window.set_target_fps(60);
|
window.set_target_fps(60);
|
||||||
|
|
||||||
let mut renderer = Renderer::new(WIDTH, HEIGHT);
|
// Renderer operates at physical resolution; the form layout is scaled accordingly.
|
||||||
let mut form = InstallForm::new();
|
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) {
|
while window.is_open() && !window.is_key_down(Key::Escape) {
|
||||||
let (mx, my) = window
|
let (mx, my) = window
|
||||||
@@ -43,7 +50,7 @@ fn main() {
|
|||||||
form.update_and_draw(&mut renderer, mx as i32, my as i32, mouse_down);
|
form.update_and_draw(&mut renderer, mx as i32, my as i32, mouse_down);
|
||||||
|
|
||||||
window
|
window
|
||||||
.update_with_buffer(&renderer.buffer, WIDTH, HEIGHT)
|
.update_with_buffer(&renderer.buffer, phys_w, phys_h)
|
||||||
.expect("Failed to update window buffer");
|
.expect("Failed to update window buffer");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,20 @@ pub mod widgets;
|
|||||||
pub use font::FontManager;
|
pub use font::FontManager;
|
||||||
pub use renderer::Renderer;
|
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
|
/// Set the window icon from the icon resource already embedded in the binary
|
||||||
/// by `winresource` (resource ID 1). No-op on non-Windows platforms.
|
/// by `winresource` (resource ID 1). No-op on non-Windows platforms.
|
||||||
pub fn set_window_icon(window: &minifb::Window) {
|
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).
|
/// Draw an RGBA image onto the framebuffer at (x, y).
|
||||||
pub fn draw_image(&mut self, x: i32, y: i32, img: &RgbaImage) {
|
pub fn draw_image(&mut self, x: i32, y: i32, img: &RgbaImage) {
|
||||||
for iy in 0..img.height as i32 {
|
for iy in 0..img.height as i32 {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::renderer::Renderer;
|
|||||||
pub struct GradientButton {
|
pub struct GradientButton {
|
||||||
pub rect: Rect,
|
pub rect: Rect,
|
||||||
pub text: String,
|
pub text: String,
|
||||||
|
pub font_size: f32,
|
||||||
pub color1: u32,
|
pub color1: u32,
|
||||||
pub color2: u32,
|
pub color2: u32,
|
||||||
pub back_color: u32,
|
pub back_color: u32,
|
||||||
@@ -23,6 +24,7 @@ impl GradientButton {
|
|||||||
Self {
|
Self {
|
||||||
rect: Rect::new(x, y, w, h),
|
rect: Rect::new(x, y, w, h),
|
||||||
text: text.to_string(),
|
text: text.to_string(),
|
||||||
|
font_size: 13.0,
|
||||||
color1: Renderer::rgb(118, 118, 118),
|
color1: Renderer::rgb(118, 118, 118),
|
||||||
color2: Renderer::rgb(81, 81, 81),
|
color2: Renderer::rgb(81, 81, 81),
|
||||||
back_color: Renderer::rgb(0, 0, 0),
|
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);
|
renderer.draw_rect(r.x, r.y, r.w, r.h, bg);
|
||||||
|
|
||||||
// Measure text to centre it.
|
// 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 (tw, th) = fonts.measure_text(&self.text, font_size);
|
||||||
let tx = r.x + ((r.w as f32 - tw) / 2.0) as i32;
|
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;
|
let ty = r.y + ((r.h as f32 - th) / 2.0) as i32;
|
||||||
|
|||||||
@@ -11,8 +11,12 @@ pub struct ImageCheckBox {
|
|||||||
|
|
||||||
impl ImageCheckBox {
|
impl ImageCheckBox {
|
||||||
pub fn new(x: i32, y: i32) -> Self {
|
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 {
|
Self {
|
||||||
rect: Rect::new(x, y, 21, 21),
|
rect: Rect::new(x, y, size, size),
|
||||||
checked: true,
|
checked: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
visible: true,
|
visible: true,
|
||||||
@@ -43,7 +47,7 @@ impl ImageCheckBox {
|
|||||||
unchecked_img
|
unchecked_img
|
||||||
};
|
};
|
||||||
if img.width > 0 && img.height > 0 {
|
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 {
|
} else {
|
||||||
// Fallback: draw a simple square.
|
// Fallback: draw a simple square.
|
||||||
let bg = if self.checked {
|
let bg = if self.checked {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ pub struct Label {
|
|||||||
pub text: String,
|
pub text: String,
|
||||||
pub color: u32,
|
pub color: u32,
|
||||||
pub font_size: f32,
|
pub font_size: f32,
|
||||||
|
pub line_spacing: f32,
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ impl Label {
|
|||||||
text: text.to_string(),
|
text: text.to_string(),
|
||||||
color: Renderer::rgb(245, 245, 245),
|
color: Renderer::rgb(245, 245, 245),
|
||||||
font_size,
|
font_size,
|
||||||
|
line_spacing: 2.0,
|
||||||
visible: true,
|
visible: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,7 +35,7 @@ impl Label {
|
|||||||
&self.text,
|
&self.text,
|
||||||
self.font_size,
|
self.font_size,
|
||||||
self.color,
|
self.color,
|
||||||
2.0,
|
self.line_spacing,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ fn main() {
|
|||||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||||
</application>
|
</application>
|
||||||
</compatibility>
|
</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>
|
</assembly>
|
||||||
"#);
|
"#);
|
||||||
let _ = res.compile();
|
let _ = res.compile();
|
||||||
|
|||||||
@@ -12,10 +12,14 @@ fn main() {
|
|||||||
clean_flash_common::update_checker::FLASH_VERSION
|
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(
|
let mut window = Window::new(
|
||||||
&title,
|
&title,
|
||||||
WIDTH,
|
phys_w,
|
||||||
HEIGHT,
|
phys_h,
|
||||||
WindowOptions {
|
WindowOptions {
|
||||||
resize: false,
|
resize: false,
|
||||||
..WindowOptions::default()
|
..WindowOptions::default()
|
||||||
@@ -28,8 +32,8 @@ fn main() {
|
|||||||
|
|
||||||
window.set_target_fps(60);
|
window.set_target_fps(60);
|
||||||
|
|
||||||
let mut renderer = Renderer::new(WIDTH, HEIGHT);
|
let mut renderer = Renderer::new(phys_w, phys_h);
|
||||||
let mut form = UninstallForm::new();
|
let mut form = UninstallForm::new(scale);
|
||||||
|
|
||||||
while window.is_open() && !window.is_key_down(Key::Escape) {
|
while window.is_open() && !window.is_key_down(Key::Escape) {
|
||||||
let (mx, my) = window
|
let (mx, my) = window
|
||||||
@@ -40,7 +44,7 @@ fn main() {
|
|||||||
form.update_and_draw(&mut renderer, mx as i32, my as i32, mouse_down);
|
form.update_and_draw(&mut renderer, mx as i32, my as i32, mouse_down);
|
||||||
|
|
||||||
window
|
window
|
||||||
.update_with_buffer(&renderer.buffer, WIDTH, HEIGHT)
|
.update_with_buffer(&renderer.buffer, phys_w, phys_h)
|
||||||
.expect("Failed to update window buffer");
|
.expect("Failed to update window buffer");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ struct ProgressState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct UninstallForm {
|
pub struct UninstallForm {
|
||||||
|
scale: f32,
|
||||||
panel: Panel,
|
panel: Panel,
|
||||||
title_text: String,
|
title_text: String,
|
||||||
subtitle_text: String,
|
subtitle_text: String,
|
||||||
@@ -64,37 +65,45 @@ pub struct UninstallForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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 version = update_checker::FLASH_VERSION;
|
||||||
let flash_logo = load_resource_image("flashLogo.png");
|
let flash_logo = load_resource_image("flashLogo.png");
|
||||||
let fonts = FontManager::new();
|
let fonts = FontManager::new();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
scale,
|
||||||
panel: Panel::BeforeInstall,
|
panel: Panel::BeforeInstall,
|
||||||
title_text: "Clean Flash Player".into(),
|
title_text: "Clean Flash Player".into(),
|
||||||
subtitle_text: format!("built from version {} (China)", version),
|
subtitle_text: format!("built from version {} (China)", version),
|
||||||
flash_logo,
|
flash_logo,
|
||||||
prev_button: GradientButton::new(90, 286, 138, 31, "QUIT"),
|
prev_button: btn(90, 286, 138, 31, "QUIT"),
|
||||||
next_button: GradientButton::new(497, 286, 138, 31, "UNINSTALL"),
|
next_button: btn(497, 286, 138, 31, "UNINSTALL"),
|
||||||
before_label: Label::new(PANEL_X + 3, PANEL_Y + 2, BEFORE_TEXT, 13.0),
|
before_label: lbl(PANEL_X + 3, PANEL_Y + 2, BEFORE_TEXT, 15.0),
|
||||||
progress_header: Label::new(
|
progress_header: lbl(PANEL_X + 3, PANEL_Y, "Uninstallation in progress...", 15.0),
|
||||||
PANEL_X + 3,
|
progress_label: lbl(PANEL_X + 46, PANEL_Y + 30, "Preparing...", 15.0),
|
||||||
PANEL_Y,
|
progress_bar: ProgressBar::new(s(PANEL_X + 49), s(PANEL_Y + 58), s(451), s(23)),
|
||||||
"Uninstallation in progress...",
|
complete_label: lbl(PANEL_X, PANEL_Y, COMPLETE_TEXT, 15.0),
|
||||||
13.0,
|
failure_text_label: lbl(
|
||||||
),
|
|
||||||
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(
|
|
||||||
PANEL_X + 3,
|
PANEL_X + 3,
|
||||||
PANEL_Y + 2,
|
PANEL_Y + 2,
|
||||||
"Oops! The installation process has encountered an unexpected problem.\n\
|
"Oops! The installation process has encountered an unexpected problem.\n\
|
||||||
The following details could be useful. Press the Retry button to try again.",
|
The following details could be useful. Press the Retry button to try again.",
|
||||||
13.0,
|
15.0,
|
||||||
),
|
),
|
||||||
failure_detail: String::new(),
|
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 {
|
progress_state: Arc::new(Mutex::new(ProgressState {
|
||||||
label: "Preparing...".into(),
|
label: "Preparing...".into(),
|
||||||
value: 0,
|
value: 0,
|
||||||
@@ -147,15 +156,17 @@ The following details could be useful. Press the Retry button to try again.",
|
|||||||
|
|
||||||
// Draw.
|
// Draw.
|
||||||
renderer.clear(BG_COLOR);
|
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
|
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
|
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.
|
// 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 {
|
match self.panel {
|
||||||
Panel::BeforeInstall => self.before_label.draw(renderer, &self.fonts),
|
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(
|
self.fonts.draw_text_multiline(
|
||||||
renderer,
|
renderer,
|
||||||
PANEL_X + 4,
|
self.s(PANEL_X + 4),
|
||||||
PANEL_Y + 44,
|
self.s(PANEL_Y + 44),
|
||||||
detail,
|
detail,
|
||||||
11.0,
|
self.sf(11.0),
|
||||||
FG_COLOR,
|
FG_COLOR,
|
||||||
1.0,
|
self.sf(1.0),
|
||||||
);
|
);
|
||||||
self.copy_error_button.draw(renderer, &self.fonts);
|
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);
|
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) {
|
fn start_uninstall(&mut self) {
|
||||||
self.panel = Panel::Install;
|
self.panel = Panel::Install;
|
||||||
self.prev_button.enabled = false;
|
self.prev_button.enabled = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user