use crossterm::event::{KeyCode, KeyEvent};
#[cfg(not(target_os = "windows"))]
use crate::install::ExecutorRequest;
use crate::state::AppState;
#[cfg(not(target_os = "windows"))]
use crate::state::{PackageItem, Source};
#[allow(clippy::too_many_arguments)]
pub(super) fn handle_scan_config(
ke: KeyEvent,
app: &mut AppState,
do_clamav: &mut bool,
do_trivy: &mut bool,
do_semgrep: &mut bool,
do_shellcheck: &mut bool,
do_virustotal: &mut bool,
do_custom: &mut bool,
do_sleuth: &mut bool,
cursor: &mut usize,
) -> bool {
match ke.code {
KeyCode::Esc => {
if let Some(prev_modal) = app.previous_modal.take() {
app.modal = prev_modal;
} else {
app.modal = crate::state::Modal::None;
}
}
KeyCode::Up => {
if *cursor > 0 {
*cursor -= 1;
}
}
KeyCode::Down => {
if *cursor < 6 {
*cursor += 1;
}
}
KeyCode::Char(' ') => match *cursor {
0 => *do_clamav = !*do_clamav,
1 => *do_trivy = !*do_trivy,
2 => *do_semgrep = !*do_semgrep,
3 => *do_shellcheck = !*do_shellcheck,
4 => *do_virustotal = !*do_virustotal,
5 => *do_custom = !*do_custom,
6 => *do_sleuth = !*do_sleuth,
_ => {}
},
#[cfg(not(target_os = "windows"))]
KeyCode::Enter => {
let pending_names = app.pending_install_names.clone();
let new_modal = handle_scan_config_confirm(
app,
pending_names.as_ref(),
app.dry_run,
*do_clamav,
*do_trivy,
*do_semgrep,
*do_shellcheck,
*do_virustotal,
*do_custom,
*do_sleuth,
);
app.modal = new_modal;
}
_ => {}
}
false
}
pub(super) fn handle_virustotal_setup(
ke: KeyEvent,
app: &mut AppState,
input: &mut String,
cursor: &mut usize,
) -> bool {
match ke.code {
KeyCode::Esc => {
app.modal = crate::state::Modal::None;
}
KeyCode::Enter => {
let key = input.trim().to_string();
if key.is_empty() {
let url = "https://www.virustotal.com/gui/my-apikey";
crate::util::open_url(url);
} else {
crate::theme::save_virustotal_api_key(&key);
app.modal = crate::state::Modal::None;
}
}
KeyCode::Backspace => {
if *cursor > 0 && *cursor <= input.len() {
input.remove(*cursor - 1);
*cursor -= 1;
}
}
KeyCode::Left => {
if *cursor > 0 {
*cursor -= 1;
}
}
KeyCode::Right => {
if *cursor < input.len() {
*cursor += 1;
}
}
KeyCode::Home => {
*cursor = 0;
}
KeyCode::End => {
*cursor = input.len();
}
KeyCode::Char(ch) => {
if !ch.is_control() {
if *cursor <= input.len() {
input.insert(*cursor, ch);
*cursor += 1;
} else {
input.push(ch);
*cursor = input.len();
}
}
}
_ => {}
}
false
}
#[allow(
clippy::too_many_arguments,
clippy::fn_params_excessive_bools,
clippy::needless_pass_by_ref_mut
)]
#[cfg(not(target_os = "windows"))]
fn handle_scan_config_confirm(
app: &mut crate::state::AppState,
pending_install_names: Option<&Vec<String>>,
dry_run: bool,
do_clamav: bool,
do_trivy: bool,
do_semgrep: bool,
do_shellcheck: bool,
do_virustotal: bool,
do_custom: bool,
do_sleuth: bool,
) -> crate::state::Modal {
tracing::info!(
event = "scan_config_confirm",
dry_run,
do_clamav,
do_trivy,
do_semgrep,
do_shellcheck,
do_virustotal,
do_custom,
pending_count = pending_install_names.map_or(0, Vec::len),
"Scan Configuration confirmed"
);
crate::theme::save_scan_do_clamav(do_clamav);
crate::theme::save_scan_do_trivy(do_trivy);
crate::theme::save_scan_do_semgrep(do_semgrep);
crate::theme::save_scan_do_shellcheck(do_shellcheck);
crate::theme::save_scan_do_virustotal(do_virustotal);
crate::theme::save_scan_do_custom(do_custom);
crate::theme::save_scan_do_sleuth(do_sleuth);
#[cfg(not(target_os = "windows"))]
if let Some(names) = pending_install_names {
if names.is_empty() {
tracing::warn!("Scan confirmed but no pending AUR package names were found");
return crate::state::Modal::None;
}
tracing::info!(
names = ?names,
count = names.len(),
dry_run,
"Launching AUR scans via integrated process"
);
let first_pkg = &names[0];
let scan_item = PackageItem {
name: format!("scan:{first_pkg}"),
version: String::new(),
description: format!("Security scan for {first_pkg}"),
source: Source::Aur,
popularity: None,
out_of_date: None,
orphaned: false,
};
app.modal = crate::state::Modal::PreflightExec {
items: vec![scan_item],
action: crate::state::PreflightAction::Install, tab: crate::state::PreflightTab::Summary,
verbose: false,
log_lines: Vec::new(),
abortable: false,
header_chips: crate::state::modal::PreflightHeaderChips::default(),
success: None,
};
app.pending_executor_request = Some(ExecutorRequest::Scan {
package: first_pkg.clone(),
do_clamav,
do_trivy,
do_semgrep,
do_shellcheck,
do_virustotal,
do_custom,
dry_run,
});
if do_sleuth && !dry_run {
let sleuth_cmd = crate::install::build_sleuth_command_for_terminal(first_pkg);
crate::install::spawn_shell_commands_in_terminal(&[sleuth_cmd]);
}
if names.len() > 1 {
tracing::warn!(
"Multiple packages requested for scan, but only scanning first package: {}",
first_pkg
);
}
return crate::state::Modal::PreflightExec {
items: vec![PackageItem {
name: format!("scan:{first_pkg}"),
version: String::new(),
description: format!("Security scan for {first_pkg}"),
source: Source::Aur,
popularity: None,
out_of_date: None,
orphaned: false,
}],
action: crate::state::PreflightAction::Install,
tab: crate::state::PreflightTab::Summary,
verbose: false,
log_lines: Vec::new(),
abortable: false,
success: None,
header_chips: crate::state::modal::PreflightHeaderChips::default(),
};
}
tracing::warn!("Scan confirmed but no pending AUR package names were found");
crate::state::Modal::None
}
#[cfg(test)]
mod tests;