Implement CLI and silent install / uninstall

This commit is contained in:
Disyer
2026-03-31 22:47:13 +03:00
parent 9547be7f16
commit 4683064637
20 changed files with 2953 additions and 69 deletions
@@ -10,6 +10,9 @@ clean_flash_ui = { path = "../clean_flash_ui" }
minifb = { workspace = true }
windows-sys = { workspace = true }
sevenz-rust2 = { workspace = true }
argh = { workspace = true }
ratatui = { workspace = true }
crossterm = { workspace = true }
[build-dependencies]
winresource = "0.1"
@@ -1,4 +1,18 @@
fn main() {
// On macOS, embed Info.plist into the binary so the OS recognises it as a GUI app.
if cfg!(target_os = "macos") {
let plist = std::path::Path::new("../../resources/macos/installer-Info.plist");
if plist.exists() {
println!("cargo:rustc-link-arg=-sectcreate");
println!("cargo:rustc-link-arg=__TEXT");
println!("cargo:rustc-link-arg=__info_plist");
println!(
"cargo:rustc-link-arg={}",
plist.canonicalize().unwrap().display()
);
}
}
// Set application icon from .ico resource (if present).
if cfg!(target_os = "windows") {
let mut res = winresource::WindowsResource::new();
@@ -0,0 +1,70 @@
use argh::FromArgs;
/// Clean Flash Player Installer
///
/// Install Clean Flash Player with GUI, TUI, or silent mode.
#[derive(FromArgs)]
pub struct InstallerArgs {
/// use the terminal UI (ratatui) instead of the graphical window
#[argh(switch)]
pub cli: bool,
/// run the installation non-interactively (no GUI or TUI)
#[argh(switch)]
pub install: bool,
/// suppress all output (only valid with --install)
#[argh(switch)]
pub silent: bool,
/// install the Modern Browsers (MV3) native host
#[argh(switch)]
pub native_host: bool,
/// install the Pepper API (PPAPI) plugin
#[argh(switch)]
pub pepper: bool,
/// install the Netscape API (NPAPI) plugin
#[argh(switch)]
pub netscape: bool,
/// install the ActiveX (OCX) plugin
#[argh(switch)]
pub activex: bool,
/// install the standalone Flash Player
#[argh(switch)]
pub player: bool,
/// create desktop shortcuts for standalone player (requires --player)
#[argh(switch)]
pub player_desktop: bool,
/// create start menu shortcuts for standalone player (requires --player)
#[argh(switch)]
pub player_start_menu: bool,
/// install the debug version
#[argh(switch)]
pub debug: bool,
}
/// The mode in which the application should run.
pub enum RunMode {
Gui,
Tui,
Silent,
}
impl InstallerArgs {
pub fn determine_mode(&self) -> RunMode {
if self.install {
RunMode::Silent
} else if self.cli {
RunMode::Tui
} else {
RunMode::Gui
}
}
}
@@ -5,6 +5,8 @@ use clean_flash_common::{
use std::env;
use std::fs;
use std::io::{self, Cursor};
#[cfg(windows)]
use std::os::windows::ffi::OsStrExt;
use std::path::{Path, PathBuf};
/// Metadata for a single installable component.
@@ -46,35 +48,170 @@ pub fn register_activex(filename: &str) -> Result<(), InstallError> {
Ok(())
}
/// Create a Windows shortcut (.lnk) using a PowerShell one-liner.
/// Create a Windows shortcut (.lnk) using native COM APIs (IShellLinkW + IPersistFile).
#[cfg(windows)]
fn create_shortcut(
folder: &Path,
executable: &Path,
name: &str,
description: &str,
) -> Result<(), InstallError> {
let lnk_path = folder.join(format!("{}.lnk", name));
let exe_str = executable.to_string_lossy();
let lnk_str = lnk_path.to_string_lossy();
use std::ffi::c_void;
// Use PowerShell to create the shortcut via WScript.Shell COM.
let script = format!(
"$ws = New-Object -ComObject WScript.Shell; \
$s = $ws.CreateShortcut('{}'); \
$s.TargetPath = '{}'; \
$s.Description = '{}'; \
$s.IconLocation = '{}'; \
$s.Save()",
lnk_str, exe_str, description, exe_str
);
// COM GUIDs.
const CLSID_SHELL_LINK: windows_sys::core::GUID =
windows_sys::core::GUID::from_u128(0x00021401_0000_0000_c000_000000000046);
const IID_ISHELL_LINK_W: windows_sys::core::GUID =
windows_sys::core::GUID::from_u128(0x000214F9_0000_0000_C000_000000000046);
const IID_IPERSIST_FILE: windows_sys::core::GUID =
windows_sys::core::GUID::from_u128(0x0000010b_0000_0000_C000_000000000046);
let result = process_utils::run_process("powershell.exe", &["-NoProfile", "-Command", &script]);
if !result.is_successful() {
return Err(InstallError::new(format!(
"Failed to create shortcut: {}",
result.output
)));
// IShellLinkW vtable layout — we only need SetPath, SetDescription, SetIconLocation.
// Vtable indices (0-based from IUnknown):
// 0: QueryInterface, 1: AddRef, 2: Release,
// 3: GetPath, 4: GetIDList, 5: SetIDList, 6: GetDescription,
// 7: SetDescription, 8: GetWorkingDirectory, 9: SetWorkingDirectory,
// 10: GetArguments, 11: SetArguments, 12: GetHotkey, 13: SetHotkey,
// 14: GetShowCmd, 15: SetShowCmd, 16: GetIconLocation, 17: SetIconLocation,
// 18: SetRelativePath, 19: Resolve, 20: SetPath
const VTBL_SET_DESCRIPTION: usize = 7;
const VTBL_SET_ICON_LOCATION: usize = 17;
const VTBL_SET_PATH: usize = 20;
// IPersistFile vtable layout:
// 0: QueryInterface, 1: AddRef, 2: Release,
// 3: GetClassID (IPersist),
// 4: IsDirty, 5: Load, 6: Save, 7: SaveCompleted, 8: GetCurFile
const VTBL_SAVE: usize = 6;
type ComMethodFn = unsafe extern "system" fn() -> i32;
unsafe {
// Initialize COM (apartment-threaded).
let hr = CoInitializeEx(std::ptr::null(), 0x2 /* COINIT_APARTMENTTHREADED */);
if hr < 0 {
return Err(InstallError::new(format!(
"CoInitializeEx failed: 0x{:08X}",
hr
)));
}
let mut shell_link: *mut c_void = std::ptr::null_mut();
let hr = CoCreateInstance(
&CLSID_SHELL_LINK,
std::ptr::null(),
1, // CLSCTX_INPROC_SERVER
&IID_ISHELL_LINK_W,
&mut shell_link,
);
if hr < 0 {
CoUninitialize();
return Err(InstallError::new(format!(
"CoCreateInstance for IShellLinkW failed: 0x{:08X}",
hr
)));
}
// Get the vtable.
let vtable = *(shell_link as *const *const *const ComMethodFn);
// SetPath
let exe_wide: Vec<u16> = executable
.as_os_str()
.encode_wide()
.chain(std::iter::once(0))
.collect();
let set_path: unsafe extern "system" fn(*mut c_void, *const u16) -> i32 =
std::mem::transmute(*vtable.add(VTBL_SET_PATH));
set_path(shell_link, exe_wide.as_ptr());
// SetDescription
let desc_wide: Vec<u16> = description.encode_utf16().chain(std::iter::once(0)).collect();
let set_description: unsafe extern "system" fn(*mut c_void, *const u16) -> i32 =
std::mem::transmute(*vtable.add(VTBL_SET_DESCRIPTION));
set_description(shell_link, desc_wide.as_ptr());
// SetIconLocation
let set_icon_location: unsafe extern "system" fn(*mut c_void, *const u16, i32) -> i32 =
std::mem::transmute(*vtable.add(VTBL_SET_ICON_LOCATION));
set_icon_location(shell_link, exe_wide.as_ptr(), 0);
// QueryInterface for IPersistFile.
let query_interface: unsafe extern "system" fn(
*mut c_void,
*const windows_sys::core::GUID,
*mut *mut c_void,
) -> i32 = std::mem::transmute(*vtable.add(0));
let mut persist_file: *mut c_void = std::ptr::null_mut();
let hr = query_interface(shell_link, &IID_IPERSIST_FILE, &mut persist_file);
if hr < 0 {
// Release IShellLinkW.
let release: unsafe extern "system" fn(*mut c_void) -> u32 =
std::mem::transmute(*vtable.add(2));
release(shell_link);
CoUninitialize();
return Err(InstallError::new(format!(
"QueryInterface for IPersistFile failed: 0x{:08X}",
hr
)));
}
// Save the .lnk file.
let lnk_path = folder.join(format!("{}.lnk", name));
let lnk_wide: Vec<u16> = lnk_path
.as_os_str()
.encode_wide()
.chain(std::iter::once(0))
.collect();
let pf_vtable = *(persist_file as *const *const *const ComMethodFn);
let save: unsafe extern "system" fn(*mut c_void, *const u16, i32) -> i32 =
std::mem::transmute(*pf_vtable.add(VTBL_SAVE));
let hr = save(persist_file, lnk_wide.as_ptr(), 1 /* TRUE */);
// Release IPersistFile.
let pf_release: unsafe extern "system" fn(*mut c_void) -> u32 =
std::mem::transmute(*pf_vtable.add(2));
pf_release(persist_file);
// Release IShellLinkW.
let release: unsafe extern "system" fn(*mut c_void) -> u32 =
std::mem::transmute(*vtable.add(2));
release(shell_link);
CoUninitialize();
if hr < 0 {
return Err(InstallError::new(format!(
"IPersistFile::Save failed: 0x{:08X}",
hr
)));
}
}
Ok(())
}
#[cfg(windows)]
#[link(name = "ole32")]
extern "system" {
fn CoInitializeEx(pvreserved: *const std::ffi::c_void, dwcoinit: u32) -> i32;
fn CoCreateInstance(
rclsid: *const windows_sys::core::GUID,
punkouter: *const std::ffi::c_void,
dwclscontext: u32,
riid: *const windows_sys::core::GUID,
ppv: *mut *mut std::ffi::c_void,
) -> i32;
fn CoUninitialize();
}
#[cfg(not(windows))]
fn create_shortcut(
_folder: &Path,
_executable: &Path,
_name: &str,
_description: &str,
) -> Result<(), InstallError> {
Ok(())
}
+35 -1
View File
@@ -1,7 +1,10 @@
#![windows_subsystem = "windows"]
mod cli_args;
mod install_flags;
mod install_form;
mod silent_installer;
mod tui_installer;
#[cfg(windows)]
#[path = "installer_windows.rs"]
@@ -11,11 +14,12 @@ mod installer;
#[path = "installer_linux.rs"]
mod installer;
use cli_args::{InstallerArgs, RunMode};
use install_form::{InstallForm, HEIGHT, WIDTH};
use clean_flash_ui::renderer::Renderer;
use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions};
fn main() {
fn run_gui() {
let title = format!(
"Clean Flash Player {} Installer",
clean_flash_common::update_checker::FLASH_VERSION
@@ -64,3 +68,33 @@ fn main() {
.expect("Failed to update window buffer");
}
}
fn main() {
let args: InstallerArgs = argh::from_env();
match args.determine_mode() {
RunMode::Silent => {
if let Err(e) = silent_installer::run_silent_install(&args) {
eprintln!("[!] Fatal error: {}", e);
std::process::exit(1);
}
}
RunMode::Tui => {
if let Err(e) = tui_installer::run_tui_installer() {
eprintln!("[!] TUI error: {}", e);
std::process::exit(1);
}
}
RunMode::Gui => {
// Try GUI; fall back to TUI if window creation fails.
let gui_result = std::panic::catch_unwind(run_gui);
if gui_result.is_err() {
eprintln!("GUI failed to initialize, falling back to terminal UI...");
if let Err(e) = tui_installer::run_tui_installer() {
eprintln!("[!] TUI error: {}", e);
std::process::exit(1);
}
}
}
}
}
@@ -0,0 +1,81 @@
use crate::cli_args::InstallerArgs;
use crate::install_flags::{self, InstallFlags};
use crate::installer;
use clean_flash_common::{uninstaller, redirection, update_checker, ProgressCallback, InstallError};
struct SilentProgressCallback {
silent: bool,
}
impl ProgressCallback for SilentProgressCallback {
fn update_progress_label(&self, text: &str, _tick: bool) {
if !self.silent {
eprintln!("[*] {}", text);
}
}
fn tick_progress(&self) {}
}
pub fn run_silent_install(args: &InstallerArgs) -> Result<(), Box<dyn std::error::Error>> {
let version = update_checker::FLASH_VERSION;
let mut flags = InstallFlags::new();
flags.set_conditionally(args.native_host, install_flags::NATIVE_HOST);
flags.set_conditionally(args.pepper, install_flags::PEPPER);
flags.set_conditionally(args.netscape, install_flags::NETSCAPE);
flags.set_conditionally(args.activex, install_flags::ACTIVEX);
flags.set_conditionally(args.player, install_flags::PLAYER);
flags.set_conditionally(args.player_desktop, install_flags::PLAYER_DESKTOP);
flags.set_conditionally(args.player_start_menu, install_flags::PLAYER_START_MENU);
flags.set_conditionally(args.debug, install_flags::DEBUG);
if flags.is_none_set() {
if !args.silent {
eprintln!("No components selected. Use flags to select what to install:");
eprintln!(" --native-host Modern Browsers (MV3)");
eprintln!(" --pepper Pepper API (PPAPI)");
eprintln!(" --netscape Netscape API (NPAPI)");
eprintln!(" --activex ActiveX (OCX)");
eprintln!(" --player Standalone Flash Player");
eprintln!(" --player-desktop Desktop shortcuts (requires --player)");
eprintln!(" --player-start-menu Start Menu shortcuts (requires --player)");
eprintln!(" --debug Debug version");
eprintln!("\nExample: clean_flash_installer --install --native-host --pepper");
}
std::process::exit(1);
}
if !args.silent {
eprintln!("Clean Flash Player {} - Installation", version);
}
let callback = SilentProgressCallback {
silent: args.silent,
};
let redir = redirection::disable_redirection();
let result = (|| -> Result<(), InstallError> {
uninstaller::uninstall(&callback)?;
installer::install(&callback, &mut flags)?;
Ok(())
})();
redirection::enable_redirection(redir);
match result {
Ok(()) => {
if !args.silent {
eprintln!("[+] Installation completed successfully.");
}
Ok(())
}
Err(e) => {
if !args.silent {
eprintln!("[!] Installation failed: {}", e);
}
std::process::exit(1);
}
}
}
@@ -0,0 +1,639 @@
use crate::install_flags::{self, InstallFlags};
use crate::installer;
use clean_flash_common::{uninstaller, redirection, update_checker, ProgressCallback, InstallError};
use crossterm::{
event::{self, Event, KeyCode, KeyEventKind},
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
execute,
};
use ratatui::{
prelude::*,
widgets::{Block, Borders, Gauge, List, ListItem, Paragraph, Wrap},
};
use std::io::{self, stdout};
use std::sync::{Arc, Mutex};
const FLASH_VERSION: &str = update_checker::FLASH_VERSION;
#[derive(Clone, Copy, PartialEq, Eq)]
enum Panel {
Disclaimer,
Choice,
PlayerChoice,
DebugChoice,
BeforeInstall,
Install,
Complete,
Failure,
}
struct ProgressState {
label: String,
value: u32,
maximum: u32,
done: bool,
error: Option<String>,
}
struct TuiInstallState {
panel: Panel,
// Disclaimer
disclaimer_accepted: bool,
// Choice
native_host: bool,
pepper: bool,
netscape: bool,
activex: bool,
choice_cursor: usize,
// Player choice
player: bool,
player_desktop: bool,
player_start_menu: bool,
player_cursor: usize,
// Debug
debug_chosen: bool,
// Progress
progress: Arc<Mutex<ProgressState>>,
// Failure
failure_detail: String,
}
impl TuiInstallState {
fn new() -> Self {
Self {
panel: Panel::Disclaimer,
disclaimer_accepted: false,
native_host: true,
pepper: false,
netscape: false,
activex: false,
choice_cursor: 0,
player: false,
player_desktop: false,
player_start_menu: false,
player_cursor: 0,
debug_chosen: false,
progress: Arc::new(Mutex::new(ProgressState {
label: "Preparing...".into(),
value: 0,
maximum: 10,
done: false,
error: None,
})),
failure_detail: String::new(),
}
}
fn choice_items(&self) -> Vec<(&str, bool, bool)> {
let is_windows = cfg!(target_os = "windows");
vec![
("Modern Browsers (MV3) - Chrome/Firefox/Edge", self.native_host, true),
("Pepper API (PPAPI) - Chrome/Opera/Brave", self.pepper, is_windows),
("Netscape API (NPAPI) - Firefox/ESR/Waterfox", self.netscape, is_windows),
("ActiveX (OCX) - IE/Embedded/Desktop", self.activex, is_windows),
]
}
fn player_items(&self) -> Vec<(&str, bool, bool)> {
vec![
("Install Standalone Flash Player", self.player, true),
("Create Desktop Shortcuts", self.player_desktop, self.player),
("Create Start Menu Shortcuts", self.player_start_menu, self.player),
]
}
fn toggle_choice(&mut self) {
let is_windows = cfg!(target_os = "windows");
match self.choice_cursor {
0 => self.native_host = !self.native_host,
1 if is_windows => self.pepper = !self.pepper,
2 if is_windows => self.netscape = !self.netscape,
3 if is_windows => self.activex = !self.activex,
_ => {}
}
}
fn toggle_player(&mut self) {
match self.player_cursor {
0 => {
self.player = !self.player;
if !self.player {
self.player_desktop = false;
self.player_start_menu = false;
}
}
1 if self.player => self.player_desktop = !self.player_desktop,
2 if self.player => self.player_start_menu = !self.player_start_menu,
_ => {}
}
}
fn build_flags(&self) -> InstallFlags {
let mut flags = InstallFlags::new();
flags.set_conditionally(self.native_host, install_flags::NATIVE_HOST);
flags.set_conditionally(self.pepper, install_flags::PEPPER);
flags.set_conditionally(self.netscape, install_flags::NETSCAPE);
flags.set_conditionally(self.activex, install_flags::ACTIVEX);
flags.set_conditionally(self.player, install_flags::PLAYER);
flags.set_conditionally(self.player_desktop, install_flags::PLAYER_DESKTOP);
flags.set_conditionally(self.player_start_menu, install_flags::PLAYER_START_MENU);
flags.set_conditionally(self.debug_chosen, install_flags::DEBUG);
flags
}
fn start_install(&mut self) {
self.panel = Panel::Install;
let mut flags = self.build_flags();
let max_ticks = flags.get_ticks();
{
let mut state = self.progress.lock().unwrap();
state.label = "Preparing...".into();
state.value = 0;
state.maximum = max_ticks;
state.done = false;
state.error = None;
}
let progress = Arc::clone(&self.progress);
std::thread::spawn(move || {
let callback = ThreadProgressCallback {
state: Arc::clone(&progress),
};
let redir = redirection::disable_redirection();
let result = (|| -> Result<(), InstallError> {
uninstaller::uninstall(&callback)?;
installer::install(&callback, &mut flags)?;
Ok(())
})();
redirection::enable_redirection(redir);
let mut state = progress.lock().unwrap();
if let Err(e) = result {
state.error = Some(e.to_string());
}
state.done = true;
});
}
fn summary_text(&self) -> String {
let mut parts = Vec::new();
if self.native_host { parts.push("Modern Browsers (via extension)"); }
if self.pepper { parts.push("Google Chrome"); }
if self.netscape { parts.push("Mozilla Firefox"); }
if self.activex { parts.push("Internet Explorer"); }
if self.player { parts.push("Standalone Player"); }
if parts.is_empty() {
"You are about to uninstall Clean Flash Player.\n\
The installer will completely remove all versions of Flash Player."
.to_string()
} else {
format!(
"You are about to install Clean Flash Player for:\n {}\n\n\
The installer will close browsers running Flash, uninstall previous versions, and install.",
parts.join(", ")
)
}
}
}
struct ThreadProgressCallback {
state: Arc<Mutex<ProgressState>>,
}
impl ProgressCallback for ThreadProgressCallback {
fn update_progress_label(&self, text: &str, tick: bool) {
let mut state = self.state.lock().unwrap();
state.label = text.to_string();
if tick {
state.value += 1;
}
}
fn tick_progress(&self) {
let mut state = self.state.lock().unwrap();
state.value += 1;
}
}
pub fn run_tui_installer() -> Result<(), Box<dyn std::error::Error>> {
enable_raw_mode()?;
let mut stdout = stdout();
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let result = run_app(&mut terminal);
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
terminal.show_cursor()?;
result
}
fn run_app(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>) -> Result<(), Box<dyn std::error::Error>> {
let mut state = TuiInstallState::new();
#[cfg(not(target_os = "windows"))]
{
state.pepper = false;
state.netscape = false;
state.activex = false;
}
loop {
terminal.draw(|f| draw_ui(f, &state))?;
// While installing, poll both events (with timeout) and progress
if state.panel == Panel::Install {
// Poll progress
{
let ps = state.progress.lock().unwrap();
if ps.done {
if let Some(ref err) = ps.error {
state.failure_detail = err.clone();
drop(ps);
state.panel = Panel::Failure;
} else {
drop(ps);
state.panel = Panel::Complete;
}
continue;
}
}
if event::poll(std::time::Duration::from_millis(100))? {
if let Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press && (key.code == KeyCode::Char('q') || key.code == KeyCode::Esc) {
break;
}
}
}
continue;
}
if let Event::Key(key) = event::read()? {
if key.kind != KeyEventKind::Press {
continue;
}
match key.code {
KeyCode::Char('q') | KeyCode::Esc => {
match state.panel {
Panel::Install => {} // can't quit during install
_ => break,
}
}
KeyCode::Enter => {
match state.panel {
Panel::Disclaimer => {
if state.disclaimer_accepted {
state.panel = Panel::Choice;
}
}
Panel::Choice => {
if cfg!(target_os = "windows") {
state.panel = Panel::PlayerChoice;
} else {
state.panel = Panel::BeforeInstall;
}
}
Panel::PlayerChoice => state.panel = Panel::DebugChoice,
Panel::DebugChoice => state.panel = Panel::BeforeInstall,
Panel::BeforeInstall => state.start_install(),
Panel::Complete => break,
Panel::Failure => state.start_install(), // retry
Panel::Install => {} // handled by progress polling above
}
}
KeyCode::Backspace => {
match state.panel {
Panel::Choice => state.panel = Panel::Disclaimer,
Panel::PlayerChoice => state.panel = Panel::Choice,
Panel::DebugChoice => state.panel = Panel::PlayerChoice,
Panel::BeforeInstall => {
if cfg!(target_os = "windows") {
state.panel = Panel::DebugChoice;
} else {
state.panel = Panel::Choice;
}
}
_ => {}
}
}
KeyCode::Char(' ') => {
match state.panel {
Panel::Disclaimer => {
state.disclaimer_accepted = !state.disclaimer_accepted;
}
Panel::Choice => state.toggle_choice(),
Panel::PlayerChoice => state.toggle_player(),
Panel::DebugChoice => {
state.debug_chosen = !state.debug_chosen;
}
_ => {}
}
}
KeyCode::Up => {
match state.panel {
Panel::Choice => {
if state.choice_cursor > 0 {
state.choice_cursor -= 1;
}
}
Panel::PlayerChoice => {
if state.player_cursor > 0 {
state.player_cursor -= 1;
}
}
_ => {}
}
}
KeyCode::Down => {
match state.panel {
Panel::Choice => {
let max = state.choice_items().len().saturating_sub(1);
if state.choice_cursor < max {
state.choice_cursor += 1;
}
}
Panel::PlayerChoice => {
let max = state.player_items().len().saturating_sub(1);
if state.player_cursor < max {
state.player_cursor += 1;
}
}
_ => {}
}
}
_ => {}
}
}
}
Ok(())
}
fn draw_ui(f: &mut Frame, state: &TuiInstallState) {
let area = f.area();
// Layout: title area, main panel, footer
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3), // title
Constraint::Min(5), // content
Constraint::Length(3), // footer
])
.split(area);
// Title bar
let title = format!(
" Clean Flash Player {} Installer ",
FLASH_VERSION
);
let title_block = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Cyan))
.title(title)
.title_alignment(Alignment::Center);
f.render_widget(title_block, chunks[0]);
// Content
let content_block = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::DarkGray));
let inner = content_block.inner(chunks[1]);
f.render_widget(content_block, chunks[1]);
match state.panel {
Panel::Disclaimer => draw_disclaimer(f, inner, state),
Panel::Choice => draw_choice(f, inner, state),
Panel::PlayerChoice => draw_player_choice(f, inner, state),
Panel::DebugChoice => draw_debug_choice(f, inner, state),
Panel::BeforeInstall => draw_before_install(f, inner, state),
Panel::Install => draw_install(f, inner, state),
Panel::Complete => draw_complete(f, inner, state),
Panel::Failure => draw_failure(f, inner, state),
}
// Footer
let footer_text = match state.panel {
Panel::Disclaimer => "[Space] Toggle accept [Enter] Next [q/Esc] Quit",
Panel::Choice => "[↑↓] Move [Space] Toggle [Enter] Next [Backspace] Back [q] Quit",
Panel::PlayerChoice => "[↑↓] Move [Space] Toggle [Enter] Next [Backspace] Back [q] Quit",
Panel::DebugChoice => "[Space] Toggle debug [Enter] Next [Backspace] Back [q] Quit",
Panel::BeforeInstall => "[Enter] Install [Backspace] Back [q] Quit",
Panel::Install => "Installing...",
Panel::Complete => "[Enter/q] Quit",
Panel::Failure => "[Enter] Retry [q] Quit",
};
let footer = Paragraph::new(footer_text)
.alignment(Alignment::Center)
.block(Block::default().borders(Borders::ALL).border_style(Style::default().fg(Color::DarkGray)));
f.render_widget(footer, chunks[2]);
}
fn draw_disclaimer(f: &mut Frame, area: Rect, state: &TuiInstallState) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(3), Constraint::Length(1)])
.split(area);
let text = "I am aware that Adobe Flash Player is no longer supported, nor provided by Adobe Inc.\n\
Clean Flash Player is a third-party version of Flash Player built from the latest Flash Player \
version with adware removed.\n\n\
Adobe is not required by any means to provide support for this version of Flash Player.";
let paragraph = Paragraph::new(text)
.wrap(Wrap { trim: false })
.style(Style::default().fg(Color::White));
f.render_widget(paragraph, chunks[0]);
let checkbox = if state.disclaimer_accepted {
"[x] I accept the above disclaimer"
} else {
"[ ] I accept the above disclaimer"
};
let cb_style = if state.disclaimer_accepted {
Style::default().fg(Color::Green)
} else {
Style::default().fg(Color::Yellow)
};
let cb = Paragraph::new(checkbox).style(cb_style);
f.render_widget(cb, chunks[1]);
}
fn draw_choice(f: &mut Frame, area: Rect, state: &TuiInstallState) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(2), Constraint::Min(3)])
.split(area);
let header = Paragraph::new("Which browser plugins would you like to install?")
.style(Style::default().fg(Color::White).add_modifier(Modifier::BOLD));
f.render_widget(header, chunks[0]);
let items: Vec<ListItem> = state
.choice_items()
.iter()
.enumerate()
.map(|(i, (label, checked, enabled))| {
let marker = if *checked { "[x]" } else { "[ ]" };
let text = format!("{} {}", marker, label);
let style = if !enabled {
Style::default().fg(Color::DarkGray)
} else if i == state.choice_cursor {
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)
} else {
Style::default().fg(Color::White)
};
ListItem::new(text).style(style)
})
.collect();
let list = List::new(items);
f.render_widget(list, chunks[1]);
}
fn draw_player_choice(f: &mut Frame, area: Rect, state: &TuiInstallState) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(2), Constraint::Min(3)])
.split(area);
let header = Paragraph::new("Would you like to install the standalone Flash Player?")
.style(Style::default().fg(Color::White).add_modifier(Modifier::BOLD));
f.render_widget(header, chunks[0]);
let items: Vec<ListItem> = state
.player_items()
.iter()
.enumerate()
.map(|(i, (label, checked, enabled))| {
let marker = if *checked { "[x]" } else { "[ ]" };
let text = format!("{} {}", marker, label);
let style = if !enabled {
Style::default().fg(Color::DarkGray)
} else if i == state.player_cursor {
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)
} else {
Style::default().fg(Color::White)
};
ListItem::new(text).style(style)
})
.collect();
let list = List::new(items);
f.render_widget(list, chunks[1]);
}
fn draw_debug_choice(f: &mut Frame, area: Rect, state: &TuiInstallState) {
let text = "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 Enter to continue.";
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(3), Constraint::Length(1)])
.split(area);
let paragraph = Paragraph::new(text)
.wrap(Wrap { trim: false })
.style(Style::default().fg(Color::White));
f.render_widget(paragraph, chunks[0]);
let checkbox = if state.debug_chosen {
"[x] Install debug version"
} else {
"[ ] Install debug version"
};
let cb_style = if state.debug_chosen {
Style::default().fg(Color::Yellow)
} else {
Style::default().fg(Color::White)
};
let cb = Paragraph::new(checkbox).style(cb_style);
f.render_widget(cb, chunks[1]);
}
fn draw_before_install(f: &mut Frame, area: Rect, state: &TuiInstallState) {
let text = state.summary_text();
let paragraph = Paragraph::new(text)
.wrap(Wrap { trim: false })
.style(Style::default().fg(Color::White));
f.render_widget(paragraph, area);
}
fn draw_install(f: &mut Frame, area: Rect, state: &TuiInstallState) {
let ps = state.progress.lock().unwrap();
let label = ps.label.clone();
let ratio = if ps.maximum > 0 {
(ps.value as f64 / ps.maximum as f64).min(1.0)
} else {
0.0
};
drop(ps);
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(2),
Constraint::Length(1),
Constraint::Length(3),
Constraint::Min(0),
])
.split(area);
let header = Paragraph::new("Installation in progress...")
.style(Style::default().fg(Color::White).add_modifier(Modifier::BOLD));
f.render_widget(header, chunks[0]);
let status = Paragraph::new(label).style(Style::default().fg(Color::White));
f.render_widget(status, chunks[1]);
let gauge = Gauge::default()
.block(Block::default().borders(Borders::ALL))
.gauge_style(Style::default().fg(Color::Cyan).bg(Color::DarkGray))
.ratio(ratio);
f.render_widget(gauge, chunks[2]);
}
fn draw_complete(f: &mut Frame, area: Rect, _state: &TuiInstallState) {
let text = "Clean Flash Player has been successfully installed!\n\n\
Don't forget, Flash Player is no longer compatible with new browsers.\n\
For browser recommendations and Flash Player updates, check out Clean Flash Player's website!";
let paragraph = Paragraph::new(text)
.wrap(Wrap { trim: false })
.style(Style::default().fg(Color::Green));
f.render_widget(paragraph, area);
}
fn draw_failure(f: &mut Frame, area: Rect, state: &TuiInstallState) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(3), Constraint::Min(2)])
.split(area);
let header = Paragraph::new(
"Oops! The installation process has encountered an unexpected problem.\nPress Enter to retry or q to quit.",
)
.wrap(Wrap { trim: false })
.style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD));
f.render_widget(header, chunks[0]);
let detail_text = if state.failure_detail.len() > 500 {
&state.failure_detail[..500]
} else {
&state.failure_detail
};
let detail = Paragraph::new(detail_text.to_string())
.wrap(Wrap { trim: false })
.style(Style::default().fg(Color::White));
f.render_widget(detail, chunks[1]);
}