Fix 29 audit findings across all severity tiers

Critical: fix unsquashfs arg order, quote Exec paths with spaces,
fix compare_versions antisymmetry, chunk-based signature detection,
bounded ELF header reads.

High: handle NULL CVE severity, prevent pipe deadlock in inspector,
fix glob_match edge case, fix backup archive path collisions, async
crash detection with stderr capture.

Medium: gate scan on auto-scan setting, fix window size persistence,
fix announce() for Stack containers, claim lightbox gesture, use
serde_json for CLI output, remove dead CSS @media blocks, add
detail-tab persistence, remove invalid metainfo categories, byte-level
fuse signature search.

Low: tighten Wayland env var detection, ELF magic validation,
timeout for update info extraction, quoted arg parsing, stop watcher
timer on window destroy, GSettings choices/range constraints, remove
unused CSS classes, define status-ok/status-attention CSS.
This commit is contained in:
lashman
2026-02-27 22:08:53 +02:00
parent f87403794e
commit e9343da249
27 changed files with 1737 additions and 250 deletions

View File

@@ -15,7 +15,6 @@ const MAX_CONCURRENT_ANALYSES: usize = 2;
static RUNNING_ANALYSES: AtomicUsize = AtomicUsize::new(0);
/// Returns the number of currently running background analyses.
#[allow(dead_code)]
pub fn running_count() -> usize {
RUNNING_ANALYSES.load(Ordering::Relaxed)
}
@@ -64,6 +63,10 @@ pub fn run_background_analysis(id: i64, path: PathBuf, appimage_type: AppImageTy
// Inspect metadata (app name, version, icon, desktop entry, AppStream, etc.)
if let Ok(meta) = inspector::inspect_appimage(&path, &appimage_type) {
log::debug!(
"Metadata for id={}: name={:?}, icon_name={:?}",
id, meta.app_name.as_deref(), meta.icon_name.as_deref(),
);
let categories = if meta.categories.is_empty() {
None
} else {

View File

@@ -405,7 +405,6 @@ fn summarize_content_rating(attrs: &[(String, String)]) -> String {
// AppStream catalog generation - writes catalog XML for GNOME Software/Discover
// ---------------------------------------------------------------------------
#[allow(dead_code)]
/// Generate an AppStream catalog XML from the Driftwood database.
/// This allows GNOME Software / KDE Discover to see locally managed AppImages.
pub fn generate_catalog(db: &Database) -> Result<String, AppStreamError> {
@@ -463,7 +462,6 @@ pub fn generate_catalog(db: &Database) -> Result<String, AppStreamError> {
Ok(xml)
}
#[allow(dead_code)]
/// Install the AppStream catalog to the local swcatalog directory.
/// GNOME Software reads from `~/.local/share/swcatalog/xml/`.
pub fn install_catalog(db: &Database) -> Result<PathBuf, AppStreamError> {
@@ -484,7 +482,6 @@ pub fn install_catalog(db: &Database) -> Result<PathBuf, AppStreamError> {
Ok(catalog_path)
}
#[allow(dead_code)]
/// Remove the AppStream catalog from the local swcatalog directory.
pub fn uninstall_catalog() -> Result<(), AppStreamError> {
let catalog_path = dirs::data_dir()
@@ -501,7 +498,6 @@ pub fn uninstall_catalog() -> Result<(), AppStreamError> {
Ok(())
}
#[allow(dead_code)]
/// Check if the AppStream catalog is currently installed.
pub fn is_catalog_installed() -> bool {
let catalog_path = dirs::data_dir()
@@ -515,7 +511,6 @@ pub fn is_catalog_installed() -> bool {
// --- Utility functions ---
#[allow(dead_code)]
fn make_component_id(name: &str) -> String {
name.chars()
.map(|c| if c.is_alphanumeric() || c == '-' || c == '.' { c.to_ascii_lowercase() } else { '_' })
@@ -524,7 +519,6 @@ fn make_component_id(name: &str) -> String {
.to_string()
}
#[allow(dead_code)]
fn xml_escape(s: &str) -> String {
s.replace('&', "&amp;")
.replace('<', "&lt;")
@@ -536,7 +530,6 @@ fn xml_escape(s: &str) -> String {
// --- Error types ---
#[derive(Debug)]
#[allow(dead_code)]
pub enum AppStreamError {
Database(String),
Io(String),

View File

@@ -119,16 +119,23 @@ pub fn create_backup(db: &Database, appimage_id: i64) -> Result<PathBuf, BackupE
"manifest.json".to_string(),
];
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("/"));
for entry in &entries {
let source = Path::new(&entry.original_path);
if source.exists() {
tar_args.push("-C".to_string());
tar_args.push(
source.parent().unwrap_or(Path::new("/")).to_string_lossy().to_string(),
);
tar_args.push(
source.file_name().unwrap_or_default().to_string_lossy().to_string(),
);
if let Ok(rel) = source.strip_prefix(&home_dir) {
tar_args.push("-C".to_string());
tar_args.push(home_dir.to_string_lossy().to_string());
tar_args.push(rel.to_string_lossy().to_string());
} else {
tar_args.push("-C".to_string());
tar_args.push(
source.parent().unwrap_or(Path::new("/")).to_string_lossy().to_string(),
);
tar_args.push(
source.file_name().unwrap_or_default().to_string_lossy().to_string(),
);
}
}
}
@@ -190,12 +197,16 @@ pub fn restore_backup(archive_path: &Path) -> Result<RestoreResult, BackupError>
// Restore each path
let mut restored = 0u32;
let mut skipped = 0u32;
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("/"));
for entry in &manifest.paths {
let source_name = Path::new(&entry.original_path)
.file_name()
.unwrap_or_default();
let extracted = temp_dir.path().join(source_name);
let source = Path::new(&entry.original_path);
let extracted = if let Ok(rel) = source.strip_prefix(&home_dir) {
temp_dir.path().join(rel)
} else {
let source_name = source.file_name().unwrap_or_default();
temp_dir.path().join(source_name)
};
let target = Path::new(&entry.original_path);
if !extracted.exists() {
@@ -269,7 +280,6 @@ pub fn delete_backup(db: &Database, backup_id: i64) -> Result<(), BackupError> {
}
/// Remove backups older than the specified number of days.
#[allow(dead_code)]
pub fn auto_cleanup_old_backups(db: &Database, retention_days: u32) -> Result<u32, BackupError> {
let backups = db.get_all_config_backups().unwrap_or_default();
let cutoff = chrono::Utc::now() - chrono::Duration::days(retention_days as i64);
@@ -292,7 +302,6 @@ pub fn auto_cleanup_old_backups(db: &Database, retention_days: u32) -> Result<u3
#[derive(Debug)]
pub struct BackupInfo {
pub id: i64,
#[allow(dead_code)]
pub appimage_id: i64,
pub app_version: Option<String>,
pub archive_path: String,
@@ -304,10 +313,8 @@ pub struct BackupInfo {
#[derive(Debug)]
pub struct RestoreResult {
#[allow(dead_code)]
pub manifest: BackupManifest,
pub paths_restored: u32,
#[allow(dead_code)]
pub paths_skipped: u32,
}

View File

@@ -184,34 +184,6 @@ pub struct ConfigBackupRecord {
pub last_restored_at: Option<String>,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct CatalogSourceRecord {
pub id: i64,
pub name: String,
pub url: String,
pub source_type: String,
pub enabled: bool,
pub last_synced: Option<String>,
pub app_count: i32,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct CatalogAppRecord {
pub id: i64,
pub source_id: i64,
pub name: String,
pub description: Option<String>,
pub categories: Option<String>,
pub latest_version: Option<String>,
pub download_url: String,
pub icon_url: Option<String>,
pub homepage: Option<String>,
pub file_size: Option<i64>,
pub architecture: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SandboxProfileRecord {
pub id: i64,
@@ -1374,7 +1346,9 @@ impl Database {
WHERE appimage_id = ?1 GROUP BY severity"
)?;
let rows = stmt.query_map(params![appimage_id], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?))
let severity: String = row.get::<_, Option<String>>(0)?
.unwrap_or_else(|| "MEDIUM".to_string());
Ok((severity, row.get::<_, i64>(1)?))
})?;
for row in rows {
let (severity, count) = row?;
@@ -1395,7 +1369,9 @@ impl Database {
"SELECT severity, COUNT(*) FROM cve_matches GROUP BY severity"
)?;
let rows = stmt.query_map([], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?))
let severity: String = row.get::<_, Option<String>>(0)?
.unwrap_or_else(|| "MEDIUM".to_string());
Ok((severity, row.get::<_, i64>(1)?))
})?;
for row in rows {
let (severity, count) = row?;

View File

@@ -330,9 +330,12 @@ fn build_name_group(name: &str, records: &[&AppImageRecord]) -> DuplicateGroup {
/// Compare two version strings for ordering.
fn compare_versions(a: &str, b: &str) -> std::cmp::Ordering {
use super::updater::version_is_newer;
use super::updater::{clean_version, version_is_newer};
if a == b {
let ca = clean_version(a);
let cb = clean_version(b);
if ca == cb {
std::cmp::Ordering::Equal
} else if version_is_newer(a, b) {
std::cmp::Ordering::Greater

View File

@@ -186,9 +186,20 @@ fn has_static_runtime(appimage_path: &Path) -> bool {
Err(_) => return false,
};
let data = &buf[..n];
let haystack = String::from_utf8_lossy(data).to_lowercase();
haystack.contains("type2-runtime")
|| haystack.contains("libfuse3")
// Search raw bytes directly - avoids allocating a UTF-8 string from binary data.
// Case-insensitive matching for the two known signatures.
bytes_contains_ci(data, b"type2-runtime")
|| bytes_contains_ci(data, b"libfuse3")
}
/// Case-insensitive byte-level substring search (ASCII only).
fn bytes_contains_ci(haystack: &[u8], needle: &[u8]) -> bool {
if needle.is_empty() || haystack.len() < needle.len() {
return false;
}
haystack.windows(needle.len()).any(|window| {
window.iter().zip(needle).all(|(h, n)| h.to_ascii_lowercase() == n.to_ascii_lowercase())
})
}
/// Check if --appimage-extract-and-run is supported.

View File

@@ -38,7 +38,6 @@ pub struct AppImageMetadata {
pub app_version: Option<String>,
pub description: Option<String>,
pub developer: Option<String>,
#[allow(dead_code)]
pub icon_name: Option<String>,
pub categories: Vec<String>,
pub desktop_entry_content: String,
@@ -246,7 +245,7 @@ fn extract_metadata_files(
.arg("usr/share/metainfo/*.xml")
.arg("usr/share/appdata/*.xml")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.status();
match status {
@@ -430,8 +429,20 @@ fn detect_architecture(path: &Path) -> Option<String> {
let mut header = [0u8; 20];
file.read_exact(&mut header).ok()?;
// ELF e_machine at offset 18 (little-endian)
let machine = u16::from_le_bytes([header[18], header[19]]);
// Validate ELF magic
if &header[0..4] != b"\x7FELF" {
return None;
}
// ELF e_machine at offset 18, endianness from byte 5
let machine = if header[5] == 2 {
// Big-endian
u16::from_be_bytes([header[18], header[19]])
} else {
// Little-endian (default)
u16::from_le_bytes([header[18], header[19]])
};
match machine {
0x03 => Some("i386".to_string()),
0x3E => Some("x86_64".to_string()),
@@ -529,12 +540,42 @@ fn find_appstream_file(extract_dir: &Path) -> Option<PathBuf> {
/// Check if an AppImage has a GPG signature by looking for the .sha256_sig section name.
fn detect_signature(path: &Path) -> bool {
let data = match fs::read(path) {
Ok(d) => d,
use std::io::{BufReader, Read};
let file = match fs::File::open(path) {
Ok(f) => f,
Err(_) => return false,
};
let needle = b".sha256_sig";
data.windows(needle.len()).any(|w| w == needle)
let mut reader = BufReader::new(file);
let mut buf = vec![0u8; 64 * 1024];
let mut carry = Vec::new();
loop {
let n = match reader.read(&mut buf) {
Ok(0) => break,
Ok(n) => n,
Err(_) => break,
};
// Prepend carry bytes from previous chunk to handle needle spanning chunks
let search_buf = if carry.is_empty() {
&buf[..n]
} else {
carry.extend_from_slice(&buf[..n]);
carry.as_slice()
};
if search_buf.windows(needle.len()).any(|w| w == needle) {
return true;
}
// Keep the last (needle.len - 1) bytes as carry for the next iteration
let keep = needle.len() - 1;
carry.clear();
if n >= keep {
carry.extend_from_slice(&buf[n - keep..n]);
} else {
carry.extend_from_slice(&buf[..n]);
}
}
false
}
/// Cache an icon file to the driftwood icons directory.

View File

@@ -94,7 +94,7 @@ pub fn integrate(record: &AppImageRecord) -> Result<IntegrationResult, Integrati
"[Desktop Entry]\n\
Type=Application\n\
Name={name}\n\
Exec={exec} %U\n\
Exec=\"{exec}\" %U\n\
Icon={icon}\n\
Categories={categories}\n\
Comment={comment}\n\
@@ -228,7 +228,7 @@ mod tests {
#[test]
fn test_integrate_creates_desktop_file() {
let dir = tempfile::tempdir().unwrap();
let _dir = tempfile::tempdir().unwrap();
// Override the applications dir for testing by creating the record
// with a specific path and testing the desktop content generation
let record = AppImageRecord {

View File

@@ -42,7 +42,6 @@ pub enum LaunchMethod {
/// Extract-and-run fallback (APPIMAGE_EXTRACT_AND_RUN=1)
ExtractAndRun,
/// Via firejail sandbox
#[allow(dead_code)]
Sandboxed,
}
@@ -68,7 +67,6 @@ pub enum LaunchResult {
Crashed {
exit_code: Option<i32>,
stderr: String,
#[allow(dead_code)]
method: LaunchMethod,
},
/// Failed to launch.
@@ -99,6 +97,22 @@ pub fn launch_appimage(
}
};
// Override with sandboxed launch if the user enabled firejail for this app
let method = if has_firejail() {
let sandbox = db
.get_appimage_by_id(record_id)
.ok()
.flatten()
.and_then(|r| r.sandbox_mode);
if sandbox.as_deref() == Some("firejail") {
LaunchMethod::Sandboxed
} else {
method
}
} else {
method
};
let result = execute_appimage(appimage_path, &method, extra_args, extra_env);
// Record the launch event regardless of success
@@ -163,42 +177,38 @@ fn execute_appimage(
cmd.env(key, value);
}
// Capture stderr to detect crash messages, stdin detached
// Detach stdin, pipe stderr so we can capture crash messages
cmd.stdin(Stdio::null());
cmd.stderr(Stdio::piped());
match cmd.spawn() {
Ok(mut child) => {
// Brief wait to detect immediate crashes (e.g. missing Qt plugins)
std::thread::sleep(std::time::Duration::from_millis(1500));
// Give the process a brief moment to fail on immediate errors
// (missing libs, exec format errors, Qt plugin failures, etc.)
std::thread::sleep(std::time::Duration::from_millis(150));
match child.try_wait() {
Ok(Some(status)) => {
// Process already exited - it crashed
let stderr = child
.stderr
.take()
.and_then(|mut err| {
let mut buf = String::new();
use std::io::Read;
err.read_to_string(&mut buf).ok()?;
Some(buf)
})
.unwrap_or_default();
// Already exited - immediate crash. Read stderr for details.
let stderr_text = child.stderr.take().map(|mut pipe| {
let mut buf = String::new();
use std::io::Read;
// Read with a size cap to avoid huge allocations
let mut limited = (&mut pipe).take(64 * 1024);
let _ = limited.read_to_string(&mut buf);
buf
}).unwrap_or_default();
LaunchResult::Crashed {
exit_code: status.code(),
stderr,
stderr: stderr_text,
method: method.clone(),
}
}
Ok(None) => {
// Still running - success
LaunchResult::Started {
child,
method: method.clone(),
}
}
Err(_) => {
// Can't check status, assume it's running
_ => {
// Still running after 150ms - drop the stderr pipe so the
// child process won't block if it fills the pipe buffer.
drop(child.stderr.take());
LaunchResult::Started {
child,
method: method.clone(),
@@ -211,11 +221,38 @@ fn execute_appimage(
}
/// Parse a launch_args string from the database into a Vec of individual arguments.
/// Splits on whitespace; returns an empty Vec if the input is None or empty.
#[allow(dead_code)]
/// Parse launch arguments with basic quote support.
/// Splits on whitespace, respecting double-quoted strings.
/// Returns an empty Vec if the input is None or empty.
pub fn parse_launch_args(args: Option<&str>) -> Vec<String> {
args.map(|s| s.split_whitespace().map(String::from).collect())
.unwrap_or_default()
let Some(s) = args else {
return Vec::new();
};
let s = s.trim();
if s.is_empty() {
return Vec::new();
}
let mut result = Vec::new();
let mut current = String::new();
let mut in_quotes = false;
for c in s.chars() {
match c {
'"' => in_quotes = !in_quotes,
' ' | '\t' if !in_quotes => {
if !current.is_empty() {
result.push(std::mem::take(&mut current));
}
}
_ => current.push(c),
}
}
if !current.is_empty() {
result.push(current);
}
result
}
/// Check if firejail is available for sandboxed launches.

View File

@@ -5,7 +5,6 @@ use super::security;
#[derive(Debug, Clone)]
pub struct CveNotification {
pub app_name: String,
#[allow(dead_code)]
pub appimage_id: i64,
pub severity: String,
pub cve_count: usize,
@@ -138,7 +137,6 @@ fn send_desktop_notification(notif: &CveNotification) -> Result<(), Notification
/// Run a security scan and send notifications for any new findings.
/// This is the CLI entry point for `driftwood security --notify`.
#[allow(dead_code)]
pub fn scan_and_notify(db: &Database, threshold: &str) -> Vec<CveNotification> {
// First run a batch scan to get fresh data
let _results = security::batch_scan(db);

View File

@@ -10,7 +10,6 @@ pub enum ReportFormat {
}
impl ReportFormat {
#[allow(dead_code)]
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"json" => Some(Self::Json),
@@ -20,7 +19,6 @@ impl ReportFormat {
}
}
#[allow(dead_code)]
pub fn extension(&self) -> &'static str {
match self {
Self::Json => "json",

View File

@@ -28,7 +28,6 @@ pub struct CveMatch {
/// Result of a security scan for a single AppImage.
#[derive(Debug, Clone)]
pub struct SecurityScanResult {
#[allow(dead_code)]
pub appimage_id: i64,
pub libraries: Vec<BundledLibrary>,
pub cve_matches: Vec<(BundledLibrary, Vec<CveMatch>)>,
@@ -254,10 +253,9 @@ pub fn detect_version_from_binary(
let extract_output = Command::new("unsquashfs")
.args(["-o", &offset, "-f", "-d"])
.arg(temp_dir.path())
.arg("-e")
.arg(lib_file_path.trim_start_matches("squashfs-root/"))
.arg("-no-progress")
.arg(appimage_path)
.arg(lib_file_path.trim_start_matches("squashfs-root/"))
.output()
.ok()?;

View File

@@ -370,27 +370,51 @@ fn parse_elf32_sections(data: &[u8]) -> Option<String> {
}
/// Fallback: run the AppImage with --appimage-updateinformation flag.
/// Uses a 5-second timeout to avoid hanging on apps with custom AppRun scripts.
fn extract_update_info_runtime(path: &Path) -> Option<String> {
let output = std::process::Command::new(path)
let mut child = std::process::Command::new(path)
.arg("--appimage-updateinformation")
.env("APPIMAGE_EXTRACT_AND_RUN", "1")
.output()
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.spawn()
.ok()?;
if output.status.success() {
let info = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !info.is_empty() && info.contains('|') {
return Some(info);
let timeout = std::time::Duration::from_secs(5);
let start = std::time::Instant::now();
loop {
match child.try_wait() {
Ok(Some(status)) => {
if status.success() {
let mut output = String::new();
if let Some(mut stdout) = child.stdout.take() {
use std::io::Read;
stdout.read_to_string(&mut output).ok()?;
}
let info = output.trim().to_string();
if !info.is_empty() && info.contains('|') {
return Some(info);
}
}
return None;
}
Ok(None) => {
if start.elapsed() >= timeout {
let _ = child.kill();
let _ = child.wait();
log::warn!("Timed out reading update info from {}", path.display());
return None;
}
std::thread::sleep(std::time::Duration::from_millis(50));
}
Err(_) => return None,
}
}
None
}
// -- GitHub/GitLab API types for JSON deserialization --
#[derive(Deserialize)]
#[allow(dead_code)]
struct GhRelease {
tag_name: String,
name: Option<String>,
@@ -406,7 +430,6 @@ struct GhAsset {
}
#[derive(Deserialize)]
#[allow(dead_code)]
struct GlRelease {
tag_name: String,
name: Option<String>,
@@ -492,6 +515,12 @@ fn check_github_release(
let release: GhRelease = response.body_mut().read_json().ok()?;
log::info!(
"GitHub release: tag={}, name={:?}",
release.tag_name,
release.name.as_deref().unwrap_or("(none)"),
);
let latest_version = clean_version(&release.tag_name);
// Find matching asset using glob-like pattern
@@ -549,6 +578,12 @@ fn check_gitlab_release(
let release: GlRelease = response.body_mut().read_json().ok()?;
log::info!(
"GitLab release: tag={}, name={:?}",
release.tag_name,
release.name.as_deref().unwrap_or("(none)"),
);
let latest_version = clean_version(&release.tag_name);
let download_url = release.assets.and_then(|assets| {
@@ -669,18 +704,24 @@ fn glob_match(pattern: &str, text: &str) -> bool {
// Last part must match at the end (unless pattern ends with *)
let last = parts[parts.len() - 1];
if !last.is_empty() {
let end_limit = if !last.is_empty() {
if !text.ends_with(last) {
return false;
}
}
text.len() - last.len()
} else {
text.len()
};
// Middle parts must appear in order
// Middle parts must appear in order within the allowed range
for part in &parts[1..parts.len() - 1] {
if part.is_empty() {
continue;
}
if let Some(found) = text[pos..].find(part) {
if pos >= end_limit {
return false;
}
if let Some(found) = text[pos..end_limit].find(part) {
pos += found + part.len();
} else {
return false;
@@ -691,7 +732,7 @@ fn glob_match(pattern: &str, text: &str) -> bool {
}
/// Clean a version string - strip leading 'v' or 'V' prefix.
fn clean_version(version: &str) -> String {
pub(crate) fn clean_version(version: &str) -> String {
let v = version.trim();
v.strip_prefix('v')
.or_else(|| v.strip_prefix('V'))
@@ -949,24 +990,25 @@ fn download_file(
/// Verify that a file is a valid AppImage (has ELF header + AppImage magic bytes).
fn verify_appimage(path: &Path) -> bool {
if let Ok(data) = fs::read(path) {
if data.len() < 12 {
return false;
}
// Check ELF magic
if &data[0..4] != b"\x7FELF" {
return false;
}
// Check AppImage Type 2 magic at offset 8: AI\x02
if data[8] == 0x41 && data[9] == 0x49 && data[10] == 0x02 {
return true;
}
// Check AppImage Type 1 magic at offset 8: AI\x01
if data[8] == 0x41 && data[9] == 0x49 && data[10] == 0x01 {
return true;
}
use std::io::Read;
let mut file = match fs::File::open(path) {
Ok(f) => f,
Err(_) => return false,
};
let mut header = [0u8; 12];
if file.read_exact(&mut header).is_err() {
return false;
}
false
// Check ELF magic
if &header[0..4] != b"\x7FELF" {
return false;
}
// Check AppImage Type 2 magic at offset 8: AI\x02
if header[8] == 0x41 && header[9] == 0x49 && header[10] == 0x02 {
return true;
}
// Check AppImage Type 1 magic at offset 8: AI\x01
header[8] == 0x41 && header[9] == 0x49 && header[10] == 0x01
}
/// Perform an update using the best available method.

View File

@@ -8,7 +8,7 @@ use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watche
#[derive(Debug, Clone)]
pub enum WatchEvent {
/// One or more AppImage files were created, modified, or deleted.
Changed(#[allow(dead_code)] Vec<PathBuf>),
Changed(Vec<PathBuf>),
}
/// Start watching the given directories for AppImage file changes.

View File

@@ -307,11 +307,9 @@ pub fn detect_desktop_environment() -> String {
/// Result of analyzing a running process for Wayland usage.
#[derive(Debug, Clone)]
pub struct RuntimeAnalysis {
#[allow(dead_code)]
pub pid: u32,
pub has_wayland_socket: bool,
pub has_x11_connection: bool,
#[allow(dead_code)]
pub env_vars: Vec<(String, String)>,
}
@@ -391,7 +389,6 @@ pub fn analyze_running_process(pid: u32) -> Result<RuntimeAnalysis, String> {
has_wayland_socket = env_vars.iter().any(|(k, v)| {
(k == "GDK_BACKEND" && v.contains("wayland"))
|| (k == "QT_QPA_PLATFORM" && v.contains("wayland"))
|| (k == "WAYLAND_DISPLAY" && !v.is_empty())
});
}