diff --git a/ChromiumCertificate.xcodeproj/project.pbxproj b/ChromiumCertificate.xcodeproj/project.pbxproj index 73ae932..c0e9a67 100644 --- a/ChromiumCertificate.xcodeproj/project.pbxproj +++ b/ChromiumCertificate.xcodeproj/project.pbxproj @@ -86,11 +86,11 @@ }; }; buildConfigurationList = 45F070D32E249A0700B1170B /* Build configuration list for PBXProject "ChromiumCertificate" */; - developmentRegion = en; + developmentRegion = "zh-Hans"; hasScannedForEncodings = 0; knownRegions = ( - en, Base, + "zh-Hans", ); mainGroup = 45F070CF2E249A0700B1170B; minimizedProjectReferenceProxies = 1; @@ -252,8 +252,11 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = U496NXS4BR; + ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "Chromium 喜报"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -277,8 +280,11 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = U496NXS4BR; + ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "Chromium 喜报"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/ChromiumCertificate.xcodeproj/xcuserdata/astrian.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/ChromiumCertificate.xcodeproj/xcuserdata/astrian.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..4af510f --- /dev/null +++ b/ChromiumCertificate.xcodeproj/xcuserdata/astrian.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/ChromiumCertificate/Assets.xcassets/AnnouncementBg.imageset/Contents.json b/ChromiumCertificate/Assets.xcassets/AnnouncementBg.imageset/Contents.json new file mode 100644 index 0000000..8b427b8 --- /dev/null +++ b/ChromiumCertificate/Assets.xcassets/AnnouncementBg.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "bg.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ChromiumCertificate/Assets.xcassets/AnnouncementBg.imageset/bg.png b/ChromiumCertificate/Assets.xcassets/AnnouncementBg.imageset/bg.png new file mode 100644 index 0000000..c2e214a Binary files /dev/null and b/ChromiumCertificate/Assets.xcassets/AnnouncementBg.imageset/bg.png differ diff --git a/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/Contents.json b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/Contents.json index 3f00db4..64dc11e 100644 --- a/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,51 +1,61 @@ { "images" : [ { + "filename" : "icon_16x16.png", "idiom" : "mac", "scale" : "1x", "size" : "16x16" }, { + "filename" : "icon_16x16@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "16x16" }, { + "filename" : "icon_32x32.png", "idiom" : "mac", "scale" : "1x", "size" : "32x32" }, { + "filename" : "icon_32x32@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "32x32" }, { + "filename" : "icon_128x128.png", "idiom" : "mac", "scale" : "1x", "size" : "128x128" }, { + "filename" : "icon_128x128@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "128x128" }, { + "filename" : "icon_256x256.png", "idiom" : "mac", "scale" : "1x", "size" : "256x256" }, { + "filename" : "icon_256x256@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "256x256" }, { + "filename" : "icon_512x512.png", "idiom" : "mac", "scale" : "1x", "size" : "512x512" }, { + "filename" : "icon_512x512@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "512x512" diff --git a/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_128x128.png b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_128x128.png new file mode 100644 index 0000000..480e695 Binary files /dev/null and b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_128x128.png differ diff --git a/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png new file mode 100644 index 0000000..e58b023 Binary files /dev/null and b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png differ diff --git a/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_16x16.png b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_16x16.png new file mode 100644 index 0000000..d05ee00 Binary files /dev/null and b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_16x16.png differ diff --git a/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png new file mode 100644 index 0000000..d212b70 Binary files /dev/null and b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png differ diff --git a/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_256x256.png b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_256x256.png new file mode 100644 index 0000000..48c2ecf Binary files /dev/null and b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_256x256.png differ diff --git a/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png new file mode 100644 index 0000000..6052633 Binary files /dev/null and b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png differ diff --git a/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_32x32.png b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_32x32.png new file mode 100644 index 0000000..762bd76 Binary files /dev/null and b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_32x32.png differ diff --git a/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png new file mode 100644 index 0000000..039c3d1 Binary files /dev/null and b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png differ diff --git a/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_512x512.png b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_512x512.png new file mode 100644 index 0000000..9646aa1 Binary files /dev/null and b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_512x512.png differ diff --git a/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png new file mode 100644 index 0000000..f795f91 Binary files /dev/null and b/ChromiumCertificate/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png differ diff --git a/ChromiumCertificate/Assets.xcassets/TextBorderColor.colorset/Contents.json b/ChromiumCertificate/Assets.xcassets/TextBorderColor.colorset/Contents.json new file mode 100644 index 0000000..4b7c7d1 --- /dev/null +++ b/ChromiumCertificate/Assets.xcassets/TextBorderColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0xFF", + "red" : "0xFE" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ChromiumCertificate/Assets.xcassets/TextColor.colorset/Contents.json b/ChromiumCertificate/Assets.xcassets/TextColor.colorset/Contents.json new file mode 100644 index 0000000..226c66b --- /dev/null +++ b/ChromiumCertificate/Assets.xcassets/TextColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x00", + "red" : "0xD0" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ChromiumCertificate/ChromiumBasedAppListView.swift b/ChromiumCertificate/ChromiumBasedAppListView.swift new file mode 100644 index 0000000..6c023cc --- /dev/null +++ b/ChromiumCertificate/ChromiumBasedAppListView.swift @@ -0,0 +1,56 @@ +// +// ChromiumBasedAppListView.swift +// ChromiumCertificate +// +// Created by Astrian Zheng on 14/7/2025. +// + +import SwiftUI + +struct ChromiumBasedAppListView: View { + @Binding var isPresented: Bool + + let chromiumAppsList: [ChromiumApp] = ChromiumDetector.detectChromiumApps() + + var body: some View { + VStack(spacing: 0) { + HStack { + Text("所有带有 Chromium 的应用程序").font(.headline) + Spacer() + Button { + self.isPresented.toggle() + } label: { + Text("关闭") + } + }.padding() + + Divider() + + ScrollView { + if chromiumAppsList.isEmpty { + Text("你的电脑没有遭受 Chromium 的荼毒!望君继续努力。").multilineTextAlignment(.center).padding() + } + VStack(spacing: 8) { + ForEach(Array(chromiumAppsList.enumerated()), id: \.element.id) { index, chromiumApp in + HStack { + VStack(alignment: .leading) { + Text(chromiumApp.name).bold() + Text(chromiumApp.path) + .font(.system(.caption, design: .monospaced)) + } + Spacer() + } + + if index < chromiumAppsList.count - 1 { + Divider() + } + } + }.padding() + } + }.frame(width: 300).frame(minHeight: 0, maxHeight: 300) + } +} + +#Preview { + ChromiumBasedAppListView(isPresented: .constant(true)) +} diff --git a/ChromiumCertificate/ChromiumCertificateApp.swift b/ChromiumCertificate/ChromiumCertificateApp.swift index 7dfd0ef..48b487d 100644 --- a/ChromiumCertificate/ChromiumCertificateApp.swift +++ b/ChromiumCertificate/ChromiumCertificateApp.swift @@ -9,9 +9,12 @@ import SwiftUI @main struct ChromiumCertificateApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } + var body: some Scene { + WindowGroup { + ContentView() + } + .windowResizability(.contentSize) + .defaultSize(width: 640, height: 480) + .windowStyle(.hiddenTitleBar) + } } diff --git a/ChromiumCertificate/ChromiumDetector.swift b/ChromiumCertificate/ChromiumDetector.swift new file mode 100644 index 0000000..c406c24 --- /dev/null +++ b/ChromiumCertificate/ChromiumDetector.swift @@ -0,0 +1,158 @@ +// +// 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 + } +} diff --git a/ChromiumCertificate/ContentView.swift b/ChromiumCertificate/ContentView.swift index a239978..41da608 100644 --- a/ChromiumCertificate/ContentView.swift +++ b/ChromiumCertificate/ContentView.swift @@ -7,16 +7,55 @@ import SwiftUI +struct StrokeText: ViewModifier { + var strokeSize: CGFloat = 1 + var strokeColor: Color = .black + + func body(content: Content) -> some View { + content + .background( + Rectangle() + .foregroundColor(strokeColor) + .mask(content) + .blur(radius: strokeSize) + ) + } +} + +extension View { + func stroke(color: Color = .black, width: CGFloat = 1) -> some View { + modifier(StrokeText(strokeSize: width, strokeColor: color)) + } +} + struct ContentView: View { - var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") - } - .padding() - } + let count = ChromiumDetector.getChromiumAppCount() + + @State private var presentSheet: Bool = false + + var body: some View { + ZStack { + Image("AnnouncementBg").resizable().frame(width: 640, height: 480) + VStack { + Text(count != 0 ? "这台 Mac 上一共有 \(count) 个 Chromium" : "这台 Mac 一个 Chromium 都没有!") + .font(.system(size: 35, weight: .semibold)) + .foregroundColor(Color("TextColor")) + .stroke(color: Color("TextBorderColor"), width: 5) + + Button { + self.presentSheet.toggle() + } label: { + Text("查看列表").font(.system(size: 20)).padding(.horizontal).padding(.vertical, 8) + } + .buttonStyle(.borderedProminent) + .tint(.red) + .sheet(isPresented: self.$presentSheet) { + ChromiumBasedAppListView(isPresented: self.$presentSheet) + } + } + } + .frame(width: 640, height: 420) + } } #Preview { diff --git a/README.md b/README.md new file mode 100644 index 0000000..464a7cc --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# 🎉 Chromium 喜报 - 您的 Mac 健康检测工具 + +> 热烈祝贺您的 Mac 电脑成功安装了 N 个基于 Chromium 的应用程序! + +## 🏆 这是什么? + +一个能够帮您统计 Mac 上到底有多少个 Chromium/Electron 应用的"健康检测"工具。运行后会以喜报的形式展示检测结果,让您充分认识到自己的电脑已经被 Chromium 占领到何种程度。 + +## 🎯 功能特色 + +- **精准检测**:扫描您的 /Applications 目录,识别所有基于 Chromium/Electron 的应用 +- **喜报展示**:采用传统喜报设计,红底金字,喜气洋洋 +- **详细列表**:查看具体是哪些应用在"助力"您的内存消耗 +- **一键查看**:简洁明了的界面,让您快速了解"战况" + +## 📸 效果预览 + +想象一下,打开应用后看到: + +``` +🎊 喜报 🎊 +这台 Mac 上一共有 23 个 Chromium +[查看列表] +``` + +## 🤔 为什么要做这个? + +在这个美好的时代,我们的电脑里充斥着各种 Electron 应用: +- VS Code(写代码) +- Slack(聊天) +- Discord(游戏聊天) +- Notion(笔记) +- Figma(设计) +- ... + +每个都是独立的 Chromium 实例,每个都在努力地消耗您的内存。是时候让我们正视这个"喜人"的现实了! + +## 🚀 使用方法 + +1. 下载 DMG 文件 +2. 拖拽到 Applications 文件夹 +3. 运行应用 +4. 欣赏您的"成就" +5. 分享到朋友圈,让大家一起"喜悦" + +## 💭 检测原理 + +应用会检查以下特征来识别 Chromium/Electron 应用: +- Electron Framework 存在 +- Chromium 相关框架 +- 动态链接库中的 Chromium 依赖 +- Info.plist 中的 Electron 标识 + +## 🎈 温馨提示 + +- 检测结果仅供娱乐参考 +- Chromium 应用并非都是坏事(真的吗?) +- 如果数字太大,请保持冷静 +- 建议定期检测,见证数字的增长 + +## 📝 开源协议 + +本项目采用 MIT 协议开源,欢迎 fork、star、提 issue。 + +--- + +*记住:每一个 Chromium 应用都是对您 16GB 内存的考验!* \ No newline at end of file