feat: 实现 Chromium 喜报应用

- 创建 Chromium/Electron 应用检测器
- 设计喜报风格的 UI 界面
- 添加应用列表查看功能
- 配置应用图标和颜色资源
- 添加 README 文档

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Astrian Zheng 2025-07-14 14:09:53 +10:00
parent 10794a7aaf
commit e23b60296c
Signed by: Astrian
SSH Key Fingerprint: SHA256:rVnhx3DAKjujCwWE13aDl7uV6+9U1MvydLkNRXJrBiA
22 changed files with 413 additions and 16 deletions

View File

@ -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)",

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
uuid = "3F09E1D6-128F-4462-8367-9B46EA2D3AB6"
type = "1"
version = "2.0">
</Bucket>

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "bg.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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))
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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 {

67
README.md Normal file
View File

@ -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 内存的考验!*