- 创建 Chromium/Electron 应用检测器 - 设计喜报风格的 UI 界面 - 添加应用列表查看功能 - 配置应用图标和颜色资源 - 添加 README 文档 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
159 lines
5.3 KiB
Swift
159 lines
5.3 KiB
Swift
//
|
|
// ChromiumDetector.swift
|
|
// ChromiumCertificate
|
|
//
|
|
// Created by Astrian Zheng on 14/7/2025.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
struct ChromiumApp: Identifiable {
|
|
let id = UUID()
|
|
let name: String
|
|
let type: ChromiumType
|
|
let path: String
|
|
}
|
|
|
|
enum ChromiumType: String, CaseIterable {
|
|
case electron = "Electron"
|
|
case chromium = "Chromium"
|
|
case chromiumLibrary = "Chromium库"
|
|
case electronIdentifier = "Electron标识"
|
|
}
|
|
|
|
class ChromiumDetector {
|
|
|
|
static func detectChromiumApps() -> [ChromiumApp] {
|
|
var chromiumApps: [ChromiumApp] = []
|
|
let fileManager = FileManager.default
|
|
let applicationsURL = URL(fileURLWithPath: "/Applications")
|
|
|
|
do {
|
|
let appURLs = try fileManager.contentsOfDirectory(
|
|
at: applicationsURL,
|
|
includingPropertiesForKeys: [.isDirectoryKey],
|
|
options: [.skipsHiddenFiles]
|
|
)
|
|
|
|
for appURL in appURLs {
|
|
if appURL.pathExtension == "app" {
|
|
if let chromiumApp = analyzeApp(at: appURL) {
|
|
chromiumApps.append(chromiumApp)
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
print("Error reading applications directory: \(error)")
|
|
}
|
|
|
|
return chromiumApps.sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending }
|
|
}
|
|
|
|
private static func analyzeApp(at appURL: URL) -> ChromiumApp? {
|
|
let appName = appURL.deletingPathExtension().lastPathComponent
|
|
let contentsURL = appURL.appendingPathComponent("Contents")
|
|
let frameworksURL = contentsURL.appendingPathComponent("Frameworks")
|
|
let infoPlistURL = contentsURL.appendingPathComponent("Info.plist")
|
|
|
|
// 检查 Electron Framework
|
|
let electronFrameworkURL = frameworksURL.appendingPathComponent("Electron Framework.framework")
|
|
if FileManager.default.fileExists(atPath: electronFrameworkURL.path) {
|
|
return ChromiumApp(name: appName, type: .electron, path: appURL.path)
|
|
}
|
|
|
|
// 检查 Chromium 相关框架
|
|
if hasChromiumFrameworks(at: frameworksURL) {
|
|
return ChromiumApp(name: appName, type: .chromium, path: appURL.path)
|
|
}
|
|
|
|
// 检查可执行文件是否链接到 Chromium 库
|
|
let executableURL = contentsURL.appendingPathComponent("MacOS").appendingPathComponent(appName)
|
|
if hasChromiumLibraries(executablePath: executableURL.path) {
|
|
return ChromiumApp(name: appName, type: .chromiumLibrary, path: appURL.path)
|
|
}
|
|
|
|
// 检查 Info.plist 中的 Electron 标识
|
|
if hasElectronIdentifier(infoPlistPath: infoPlistURL.path) {
|
|
return ChromiumApp(name: appName, type: .electronIdentifier, path: appURL.path)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
private static func hasChromiumFrameworks(at frameworksURL: URL) -> Bool {
|
|
do {
|
|
let frameworks = try FileManager.default.contentsOfDirectory(atPath: frameworksURL.path)
|
|
return frameworks.contains { $0.localizedCaseInsensitiveContains("chromium") }
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
private static func hasChromiumLibraries(executablePath: String) -> Bool {
|
|
guard FileManager.default.fileExists(atPath: executablePath) else { return false }
|
|
|
|
let task = Process()
|
|
task.launchPath = "/usr/bin/otool"
|
|
task.arguments = ["-L", executablePath]
|
|
|
|
let pipe = Pipe()
|
|
task.standardOutput = pipe
|
|
task.standardError = Pipe()
|
|
|
|
do {
|
|
try task.run()
|
|
task.waitUntilExit()
|
|
|
|
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
|
let output = String(data: data, encoding: .utf8) ?? ""
|
|
|
|
return output.localizedCaseInsensitiveContains("chromium")
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
private static func hasElectronIdentifier(infoPlistPath: String) -> Bool {
|
|
guard FileManager.default.fileExists(atPath: infoPlistPath) else { return false }
|
|
|
|
let task = Process()
|
|
task.launchPath = "/usr/libexec/PlistBuddy"
|
|
task.arguments = ["-c", "Print CFBundleIdentifier", infoPlistPath]
|
|
|
|
let pipe = Pipe()
|
|
task.standardOutput = pipe
|
|
task.standardError = Pipe()
|
|
|
|
do {
|
|
try task.run()
|
|
task.waitUntilExit()
|
|
|
|
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
|
let output = String(data: data, encoding: .utf8) ?? ""
|
|
|
|
return output.localizedCaseInsensitiveContains("electron")
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
static func getChromiumAppCount() -> Int {
|
|
return detectChromiumApps().count
|
|
}
|
|
|
|
static func getChromiumAppNames() -> [String] {
|
|
return detectChromiumApps().map { $0.name }
|
|
}
|
|
|
|
static func getChromiumAppsByType() -> [ChromiumType: [ChromiumApp]] {
|
|
let apps = detectChromiumApps()
|
|
var groupedApps: [ChromiumType: [ChromiumApp]] = [:]
|
|
|
|
for type in ChromiumType.allCases {
|
|
groupedApps[type] = apps.filter { $0.type == type }
|
|
}
|
|
|
|
return groupedApps
|
|
}
|
|
}
|