From 3243570d53d5997948a6290ea49ccb270b098ef4 Mon Sep 17 00:00:00 2001 From: gentpan Date: Sun, 13 Sep 2026 06:31:46 +0500 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=9C=AC=E6=9C=BA=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E9=A1=B5=EF=BC=9A=E6=9C=BA=E5=9E=8B=E3=80=81=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E3=80=81=E8=8A=AF=E7=89=87=E3=80=81=E5=86=85=E5=AD=98?= =?UTF-8?q?=E4=B8=8E=E5=9B=BE=E5=BD=A2=E3=80=81=E5=AD=98=E5=82=A8=E3=80=81?= =?UTF-8?q?=E7=94=B5=E6=B1=A0=E3=80=81=E6=98=BE=E7=A4=BA=E5=99=A8=E4=B8=8E?= =?UTF-8?q?=E6=A0=87=E8=AF=86=EF=BC=8C=E5=BA=8F=E5=88=97=E5=8F=B7=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E9=81=AE=E4=BD=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + .../Sources/Metrics/HardwareSamplers.swift | 15 +- .../OpenStatsKit/Sources/Metrics/Models.swift | 5 + .../OpenStatsUI/Panel/MainWindowView.swift | 1 + .../OpenStatsUI/Panel/SystemInfoPage.swift | 204 ++++++++++++++++++ .../Sources/OpenStatsUI/State/AppModel.swift | 6 +- .../OpenStatsUI/State/AppSettings.swift | 6 +- README.md | 5 +- README.zh-CN.md | 5 +- web/index.html | 4 +- 10 files changed, 238 insertions(+), 14 deletions(-) create mode 100644 Packages/OpenStatsKit/Sources/OpenStatsUI/Panel/SystemInfoPage.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index d87ad75..0ff9977 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - CPU 详情:顶部是占用、状态(空闲 / 适中 / 繁忙 / 满载)、与 30 秒前相比的变化和温度余量,下面是走势线与用户 / 系统 / 空闲构成条;核心热力图(每行一个核心、每列一次采样);核心分工(各类核心平均占用与最忙的核心);排队程度(平均负载折算到每个核心,并提示负载在上升还是下降);按应用汇总 CPU。 - 内存详情:顶部是还可用多少、压力状态与最近 60 秒的压力走势条;内存水位条(App / 联动 / 压缩 / 缓存 / 空闲);压缩与交换(压缩省下的内存、压缩比、交换区用量与实时换入换出速率,持续写盘时提示);按应用汇总内存(合并辅助进程,显示占已用内存的比例)。 - 用 Apple 智能解释进程:在进程上右键或点进程页的星形按钮,由系统自带的本机大模型说明它是什么、占用是否正常、能否退出;把路径、所属应用和签名方一起交给模型以减少猜测,全程不联网(需要 macOS 26 并开启 Apple 智能)。 +- 本机信息页:机型图与名称、系统版本与版号、芯片与各类核心、内存与图形、存储用量、电池健康与循环次数、每台显示器的尺寸 / 原生分辨率 / 显示分辨率 / 刷新率、机型标识符、序列号(默认遮住,点眼睛图标显示,截图时始终遮住)与启动时间。 - 进程管理器(主窗口“进程”页):包括 root 与其他用户的系统进程(通过系统自带的 ps 读取,不需要辅助工具);列出 CPU、CPU 时间、内存、线程、唤醒次数、磁盘读写与用户,点列标题排序;可搜索名称、PID 或用户,筛选全部 / 我的 / 系统,切换按进程或按应用合并;选中后显示路径与详情,可在访达中显示、用 Apple 智能解释、退出或强制退出(二次确认,系统进程与 loginwindow 等禁止结束);底部汇总用户 / 系统 / 空闲与全系统进程数、线程数。 - 进程显示应用的本地化名称(如“微信”);按应用汇总时,同一应用的主进程与辅助进程合并计算。 - 仪表盘顶部是健康评分和芯片、内存、系统版本、运行时长、机型徽章,下面是 CPU 柱状历史、GPU 折线、内存面积图、磁盘、网络双线、风扇三列卡片,以及核心负载、电池环形图、高占用进程和快捷开关。 diff --git a/Packages/OpenStatsKit/Sources/Metrics/HardwareSamplers.swift b/Packages/OpenStatsKit/Sources/Metrics/HardwareSamplers.swift index a7dfd83..59d627c 100644 --- a/Packages/OpenStatsKit/Sources/Metrics/HardwareSamplers.swift +++ b/Packages/OpenStatsKit/Sources/Metrics/HardwareSamplers.swift @@ -107,8 +107,17 @@ public enum SystemInfoReader { let boot = sysctlbyname("kern.boottime", &bootTime, &size, nil, 0) == 0 ? Date(timeIntervalSince1970: TimeInterval(bootTime.tv_sec)) : nil - return SystemInfo(modelName: modelName, - osVersion: "macOS \(version.majorVersion).\(version.minorVersion)\(patch)", - bootDate: boot) + var info = SystemInfo(modelName: modelName, + osVersion: "macOS \(version.majorVersion).\(version.minorVersion)\(patch)", + bootDate: boot) + info.modelIdentifier = Sysctl.string("hw.model") ?? "" + info.osBuild = Sysctl.string("kern.osversion") ?? "" + let platform = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("IOPlatformExpertDevice")) + if platform != 0 { + info.serialNumber = IORegistryEntryCreateCFProperty(platform, kIOPlatformSerialNumberKey as CFString, kCFAllocatorDefault, 0)? + .takeRetainedValue() as? String + IOObjectRelease(platform) + } + return info } } diff --git a/Packages/OpenStatsKit/Sources/Metrics/Models.swift b/Packages/OpenStatsKit/Sources/Metrics/Models.swift index 2afb28e..8de9459 100644 --- a/Packages/OpenStatsKit/Sources/Metrics/Models.swift +++ b/Packages/OpenStatsKit/Sources/Metrics/Models.swift @@ -173,6 +173,11 @@ public struct SystemInfo: Sendable, Equatable { public var modelName: String public var osVersion: String public var bootDate: Date? + /// 例如 “Mac17,6” + public var modelIdentifier: String = "" + /// 系统版号,例如 “26A428” + public var osBuild: String = "" + public var serialNumber: String? } public struct MetricsSnapshot: Sendable { diff --git a/Packages/OpenStatsKit/Sources/OpenStatsUI/Panel/MainWindowView.swift b/Packages/OpenStatsKit/Sources/OpenStatsUI/Panel/MainWindowView.swift index 277457d..32bdd51 100644 --- a/Packages/OpenStatsKit/Sources/OpenStatsUI/Panel/MainWindowView.swift +++ b/Packages/OpenStatsKit/Sources/OpenStatsUI/Panel/MainWindowView.swift @@ -24,6 +24,7 @@ public struct MainWindowView: View { Group { switch model.settings.panelTab { case .overview: OverviewPage() + case .system: SystemInfoPage() case .cpu: DetailPage { CPUPopover() } case .gpu: DetailPage { GPUPopover() } case .memory: DetailPage { MemoryPopover() } diff --git a/Packages/OpenStatsKit/Sources/OpenStatsUI/Panel/SystemInfoPage.swift b/Packages/OpenStatsKit/Sources/OpenStatsUI/Panel/SystemInfoPage.swift new file mode 100644 index 0000000..5772339 --- /dev/null +++ b/Packages/OpenStatsKit/Sources/OpenStatsUI/Panel/SystemInfoPage.swift @@ -0,0 +1,204 @@ +import AppKit +import Metrics +import SwiftUI + +/// 本机信息:机型、芯片、内存、图形、存储、显示器、电池与标识 +struct SystemInfoPage: View { + @Environment(AppModel.self) private var model + @Environment(\.isSnapshot) private var isSnapshot + @State private var revealSerial = false + + var body: some View { + let store = model.store + let system = store.system + let topology = store.topology + + PageScroll { + Card(padding: DS.Space.s4, spacing: DS.Space.s3) { + HStack(spacing: DS.Space.s4) { + Image(nsImage: NSImage(named: NSImage.computerName) ?? NSApp.applicationIconImage) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: DS.Space.s16 + DS.Space.s8, height: DS.Space.s16 + DS.Space.s8) + .accessibilityHidden(true) + VStack(alignment: .leading, spacing: DS.Space.s2) { + Text(verbatim: localizedModel(system.modelName)) + .dsFont(.xl, weight: .semibold) + .foregroundStyle(DS.Palette.textPrimary) + Text(verbatim: "\(system.osVersion)(\(system.osBuild))") + .dsFont(.sm) + .foregroundStyle(DS.Palette.textSecondary) + FlowLayout(spacing: DS.Space.s2) { + Chip(text: chipName(topology.brand), icon: "apple.logo") + Chip(text: Format.bytes(ProcessInfo.processInfo.physicalMemory).replacingOccurrences(of: ".0 ", with: " ")) + if let disk = store.disk { + Chip(text: Format.bytes(disk.total, base: .decimal).replacingOccurrences(of: ".0 ", with: " ")) + } + if let boot = system.bootDate { + Chip(text: "已运行 " + Format.uptime(since: boot)) + } + } + } + Spacer(minLength: 0) + } + } + + WeightedRow(weights: [1, 1]) { + InfoCard(icon: "cpu", title: "处理器") { + InfoRow(label: "芯片", text: topology.brand) + InfoRow(label: "核心", text: "\(topology.logicalCores) 核") + ForEach(topology.clusters) { cluster in + InfoRow(label: cluster.name, text: "\(cluster.coreIndices.count) 核") + } + } + InfoCard(icon: "memorychip", title: "内存与图形") { + InfoRow(label: "内存", text: Format.bytes(ProcessInfo.processInfo.physicalMemory)) + InfoRow(label: "图形", text: store.gpu?.name ?? topology.brand) + InfoRow(label: "图形核心", text: store.gpu?.coreCount.map { "\($0) 核" } ?? "—") + InfoRow(label: "内存类型", text: "统一内存(CPU 与 GPU 共享)") + } + } + + WeightedRow(weights: [1, 1]) { + InfoCard(icon: "internaldrive", title: "存储") { + if let disk = store.disk { + InfoRow(label: "卷", text: disk.volumeName) + InfoRow(label: "容量", text: Format.bytes(disk.total, base: .decimal)) + InfoRow(label: "可用", text: Format.bytes(disk.available, base: .decimal)) + ProgressTrack(fraction: disk.usedFraction, + color: disk.usedFraction > 0.9 ? DS.Palette.warning : DS.Palette.primary) + } else { + Text("正在读取…").dsFont(.xs).foregroundStyle(DS.Palette.textTertiary) + } + } + InfoCard(icon: "battery.75", title: "电池") { + if let battery = store.battery { + InfoRow(label: "电量", text: Format.percent(battery.level)) + InfoRow(label: "健康度", text: battery.health.map { Format.percent($0) } ?? "—") + InfoRow(label: "循环次数", text: battery.cycleCount.map(String.init) ?? "—") + InfoRow(label: "电源", text: battery.isPluggedIn ? "电源适配器\(battery.adapterWatts.map { " · \($0)W" } ?? "")" : "电池供电") + } else { + Text("这台 Mac 没有电池").dsFont(.xs).foregroundStyle(DS.Palette.textTertiary) + } + } + } + + InfoCard(icon: "display.2", title: "显示器") { + let displays = DisplayInfo.all() + if displays.isEmpty { + Text("没有检测到显示器").dsFont(.xs).foregroundStyle(DS.Palette.textTertiary) + } + ForEach(Array(displays.enumerated()), id: \.offset) { index, display in + if index > 0 { HairlineDivider() } + HStack(spacing: DS.Space.s3) { + Image(systemName: display.isBuiltIn ? "laptopcomputer" : "display") + .font(.system(size: DS.TextSize.base.rawValue)) + .foregroundStyle(DS.Palette.textSecondary) + .frame(width: DS.Size.iconStandalone) + VStack(alignment: .leading, spacing: 0) { + Text(verbatim: display.name).dsFont(.sm, weight: .medium).foregroundStyle(DS.Palette.textPrimary) + Text(verbatim: display.summary).dsFont(.xs).monospacedDigit().foregroundStyle(DS.Palette.textSecondary) + } + Spacer(minLength: 0) + if display.isMain { Chip(text: "主显示器", tone: .primary) } + } + } + } + + InfoCard(icon: "number", title: "标识") { + InfoRow(label: "机型标识符") { CopyableText(text: system.modelIdentifier.isEmpty ? "—" : system.modelIdentifier) } + InfoRow(label: "系统版号") { CopyableText(text: system.osBuild.isEmpty ? "—" : system.osBuild) } + InfoRow(label: "序列号") { + HStack(spacing: DS.Space.s1) { + if revealSerial, !isSnapshot, let serial = system.serialNumber { + CopyableText(text: serial) + } else { + Text(verbatim: system.serialNumber.map { String(repeating: "•", count: min(12, $0.count)) } ?? "—") + } + if system.serialNumber != nil, !isSnapshot { + MiniIconButton(systemName: revealSerial ? "eye.slash" : "eye", help: revealSerial ? "隐藏序列号" : "显示序列号") { + revealSerial.toggle() + } + } + } + } + if let boot = system.bootDate { + InfoRow(label: "启动于", text: boot.formatted(date: .abbreviated, time: .shortened)) + } + } + } + } + + /// “MacBook Pro (16-inch, M5 Max)” → “MacBook Pro 16 英寸(M5 Max)” + private func localizedModel(_ name: String) -> String { + guard let open = name.firstIndex(of: "("), name.hasSuffix(")") else { return name } + let base = name[.. String { + brand.hasPrefix("Apple ") ? String(brand.dropFirst("Apple ".count)) : brand + } +} + +private struct InfoCard: View { + let icon: String + let title: String + @ViewBuilder var content: Content + + var body: some View { + Card(padding: DS.Space.s3, spacing: DS.Space.s2) { + HStack(spacing: DS.Space.s1) { + Image(systemName: icon).font(.system(size: DS.TextSize.xs.rawValue, weight: .semibold)) + Text(title).dsFont(.xs, weight: .semibold) + } + .foregroundStyle(DS.Palette.textSecondary) + content + } + } +} + +/// 显示器信息:名称、尺寸、原生分辨率与最高刷新率 +struct DisplayInfo { + let name: String + let isBuiltIn: Bool + let isMain: Bool + let summary: String + + @MainActor + static func all() -> [DisplayInfo] { + NSScreen.screens.map { screen in + let id = (screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? NSNumber)?.uint32Value ?? 0 + var parts: [String] = [] + let millimeters = CGDisplayScreenSize(id) + if millimeters.width > 0 { + let inches = (millimeters.width * millimeters.width + millimeters.height * millimeters.height).squareRoot() / 25.4 + parts.append("\(Int(inches.rounded())) 英寸") + } + if let native = nativeResolution(id) { parts.append("\(native.width)×\(native.height)") } + let points = screen.frame.size + parts.append("显示为 \(Int(points.width))×\(Int(points.height))") + if screen.maximumFramesPerSecond > 0 { parts.append("\(screen.maximumFramesPerSecond)Hz") } + return DisplayInfo(name: screen.localizedName, isBuiltIn: CGDisplayIsBuiltin(id) != 0, + isMain: CGDisplayIsMain(id) != 0, summary: parts.joined(separator: " · ")) + } + } + + /// 面板的原生分辨率:优先取标记为原生的显示模式;缩放模式的像素可能比面板还多(如 6400×3600) + private static func nativeResolution(_ id: CGDirectDisplayID) -> (width: Int, height: Int)? { + let options = [kCGDisplayShowDuplicateLowResolutionModes: true] as CFDictionary + guard let modes = CGDisplayCopyAllDisplayModes(id, options) as? [CGDisplayMode], !modes.isEmpty else { return nil } + let nativeFlag: UInt32 = 0x0200_0000 // kDisplayModeNativeFlag + let native = modes.filter { $0.ioFlags & nativeFlag != 0 } + guard let best = (native.isEmpty ? modes : native) + .max(by: { $0.pixelWidth * $0.pixelHeight < $1.pixelWidth * $1.pixelHeight }) else { return nil } + return (best.pixelWidth, best.pixelHeight) + } +} diff --git a/Packages/OpenStatsKit/Sources/OpenStatsUI/State/AppModel.swift b/Packages/OpenStatsKit/Sources/OpenStatsUI/State/AppModel.swift index ac3e642..58ff7aa 100644 --- a/Packages/OpenStatsKit/Sources/OpenStatsUI/State/AppModel.swift +++ b/Packages/OpenStatsKit/Sources/OpenStatsUI/State/AppModel.swift @@ -55,9 +55,9 @@ public final class AppModel { demand.memory = true demand.network = true let showing = { (page: PanelTab, item: MenuBarItem) in (window && tab == page) || popover == item } - demand.gpu = (window && tab == .overview) || menu.contains(.gpu) || showing(.gpu, .gpu) - demand.disk = window && (tab == .overview || tab == .cleaner) - demand.battery = (window && (tab == .overview || tab == .keepAwake)) || keepAwake.lidClosedActive + demand.gpu = (window && [.overview, .system].contains(tab)) || menu.contains(.gpu) || showing(.gpu, .gpu) + demand.disk = window && [.overview, .system, .cleaner].contains(tab) + demand.battery = (window && [.overview, .system, .keepAwake].contains(tab)) || keepAwake.lidClosedActive demand.processes = (window && [.processes, .overview].contains(tab)) || showing(.cpu, .cpu) || showing(.memory, .memory) demand.systemProcesses = window && tab == .processes diff --git a/Packages/OpenStatsKit/Sources/OpenStatsUI/State/AppSettings.swift b/Packages/OpenStatsKit/Sources/OpenStatsUI/State/AppSettings.swift index dab9560..fb9083d 100644 --- a/Packages/OpenStatsKit/Sources/OpenStatsUI/State/AppSettings.swift +++ b/Packages/OpenStatsKit/Sources/OpenStatsUI/State/AppSettings.swift @@ -219,16 +219,17 @@ public enum AppearanceMode: String, CaseIterable, Identifiable, Sendable { /// 主窗口侧边栏的页面 public enum PanelTab: String, CaseIterable, Identifiable, Sendable { - case overview, cpu, gpu, memory, network, thermal, processes, keepAwake, cleaner + case overview, system, cpu, gpu, memory, network, thermal, processes, keepAwake, cleaner public var id: String { rawValue } - static let monitors: [PanelTab] = [.overview, .cpu, .gpu, .memory, .network, .thermal] + static let monitors: [PanelTab] = [.overview, .system, .cpu, .gpu, .memory, .network, .thermal] static let tools: [PanelTab] = [.processes, .keepAwake, .cleaner] var title: String { switch self { case .overview: "仪表盘" + case .system: "本机信息" case .cpu: "CPU" case .gpu: "GPU" case .memory: "内存" @@ -243,6 +244,7 @@ public enum PanelTab: String, CaseIterable, Identifiable, Sendable { var symbol: String { switch self { case .overview: "square.grid.2x2" + case .system: "laptopcomputer" case .cpu: "cpu" case .gpu: "square.3.layers.3d" case .memory: "memorychip" diff --git a/README.md b/README.md index 61d02cb..db6555b 100644 --- a/README.md +++ b/README.md @@ -46,10 +46,10 @@ Developed and tested on Apple Silicon. The interface is in Simplified Chinese. -Not released yet · **43** changes in development · [full changelog](CHANGELOG.md) (kept in Chinese) +Not released yet · **44** changes in development · [full changelog](CHANGELOG.md) (kept in Chinese)
-2026-09-13 · Unreleased · 28 added · 9 style · 6 fixed +2026-09-13 · Unreleased · 29 added · 9 style · 6 fixed **Added** @@ -65,6 +65,7 @@ Not released yet · **43** changes in development · [full changelog](CHANGELOG. - CPU 详情:顶部是占用、状态(空闲 / 适中 / 繁忙 / 满载)、与 30 秒前相比的变化和温度余量,下面是走势线与用户 / 系统 / 空闲构成条;核心热力图(每行一个核心、每列一次采样);核心分工(各类核心平均占用与最忙的核心);排队程度(平均负载折算到每个核心,并提示负载在上升还是下降);按应用汇总 CPU。 - 内存详情:顶部是还可用多少、压力状态与最近 60 秒的压力走势条;内存水位条(App / 联动 / 压缩 / 缓存 / 空闲);压缩与交换(压缩省下的内存、压缩比、交换区用量与实时换入换出速率,持续写盘时提示);按应用汇总内存(合并辅助进程,显示占已用内存的比例)。 - 用 Apple 智能解释进程:在进程上右键或点进程页的星形按钮,由系统自带的本机大模型说明它是什么、占用是否正常、能否退出;把路径、所属应用和签名方一起交给模型以减少猜测,全程不联网(需要 macOS 26 并开启 Apple 智能)。 +- 本机信息页:机型图与名称、系统版本与版号、芯片与各类核心、内存与图形、存储用量、电池健康与循环次数、每台显示器的尺寸 / 原生分辨率 / 显示分辨率 / 刷新率、机型标识符、序列号(默认遮住,点眼睛图标显示,截图时始终遮住)与启动时间。 - 进程管理器(主窗口“进程”页):包括 root 与其他用户的系统进程(通过系统自带的 ps 读取,不需要辅助工具);列出 CPU、CPU 时间、内存、线程、唤醒次数、磁盘读写与用户,点列标题排序;可搜索名称、PID 或用户,筛选全部 / 我的 / 系统,切换按进程或按应用合并;选中后显示路径与详情,可在访达中显示、用 Apple 智能解释、退出或强制退出(二次确认,系统进程与 loginwindow 等禁止结束);底部汇总用户 / 系统 / 空闲与全系统进程数、线程数。 - 进程显示应用的本地化名称(如“微信”);按应用汇总时,同一应用的主进程与辅助进程合并计算。 - 仪表盘顶部是健康评分和芯片、内存、系统版本、运行时长、机型徽章,下面是 CPU 柱状历史、GPU 折线、内存面积图、磁盘、网络双线、风扇三列卡片,以及核心负载、电池环形图、高占用进程和快捷开关。 diff --git a/README.zh-CN.md b/README.zh-CN.md index c41fd9a..97621fa 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -44,10 +44,10 @@ OpenStats 还没有发布正式版本。第一个版本会在 [Releases](https:/ -尚未发布正式版本 · 开发中 **43** 项改动尚未发布 · [完整更新日志](CHANGELOG.md) +尚未发布正式版本 · 开发中 **44** 项改动尚未发布 · [完整更新日志](CHANGELOG.md)
-2026-09-13 · 未发布 · 新增 28 · 样式 9 · 修复 6 +2026-09-13 · 未发布 · 新增 29 · 样式 9 · 修复 6 **新增** @@ -63,6 +63,7 @@ OpenStats 还没有发布正式版本。第一个版本会在 [Releases](https:/ - CPU 详情:顶部是占用、状态(空闲 / 适中 / 繁忙 / 满载)、与 30 秒前相比的变化和温度余量,下面是走势线与用户 / 系统 / 空闲构成条;核心热力图(每行一个核心、每列一次采样);核心分工(各类核心平均占用与最忙的核心);排队程度(平均负载折算到每个核心,并提示负载在上升还是下降);按应用汇总 CPU。 - 内存详情:顶部是还可用多少、压力状态与最近 60 秒的压力走势条;内存水位条(App / 联动 / 压缩 / 缓存 / 空闲);压缩与交换(压缩省下的内存、压缩比、交换区用量与实时换入换出速率,持续写盘时提示);按应用汇总内存(合并辅助进程,显示占已用内存的比例)。 - 用 Apple 智能解释进程:在进程上右键或点进程页的星形按钮,由系统自带的本机大模型说明它是什么、占用是否正常、能否退出;把路径、所属应用和签名方一起交给模型以减少猜测,全程不联网(需要 macOS 26 并开启 Apple 智能)。 +- 本机信息页:机型图与名称、系统版本与版号、芯片与各类核心、内存与图形、存储用量、电池健康与循环次数、每台显示器的尺寸 / 原生分辨率 / 显示分辨率 / 刷新率、机型标识符、序列号(默认遮住,点眼睛图标显示,截图时始终遮住)与启动时间。 - 进程管理器(主窗口“进程”页):包括 root 与其他用户的系统进程(通过系统自带的 ps 读取,不需要辅助工具);列出 CPU、CPU 时间、内存、线程、唤醒次数、磁盘读写与用户,点列标题排序;可搜索名称、PID 或用户,筛选全部 / 我的 / 系统,切换按进程或按应用合并;选中后显示路径与详情,可在访达中显示、用 Apple 智能解释、退出或强制退出(二次确认,系统进程与 loginwindow 等禁止结束);底部汇总用户 / 系统 / 空闲与全系统进程数、线程数。 - 进程显示应用的本地化名称(如“微信”);按应用汇总时,同一应用的主进程与辅助进程合并计算。 - 仪表盘顶部是健康评分和芯片、内存、系统版本、运行时长、机型徽章,下面是 CPU 柱状历史、GPU 折线、内存面积图、磁盘、网络双线、风扇三列卡片,以及核心负载、电池环形图、高占用进程和快捷开关。 diff --git a/web/index.html b/web/index.html index 634392b..94fb3c6 100644 --- a/web/index.html +++ b/web/index.html @@ -347,7 +347,7 @@
未发布
-

新增 28 · 样式 9 · 修复 6

+

新增 29 · 样式 9 · 修复 6

  • 新增菜单栏显示 CPU、GPU、内存、网速、CPU 温度和风扇转速。
  • 新增菜单栏风格:双行文字、单行文字、图标、圆环、饼图、柱状历史、电量条、状态圆点八种,整体选择一种统一套用,也可以给个别指标单独指定。设置里每种风格都有用示例数据画出的预览和说明,柱状历史注明是最近 10 次采样的变化。
  • @@ -358,7 +358,7 @@
  • 新增网络详情:上下镜像的流量历史;连接探测格子(每 1 / 2 / 5 秒 ping 一次 Cloudflare、Google、阿里云、腾讯或路由器,显示延迟、抖动、丢包);接口、物理地址、Wi-Fi 信号与速率、VPN / 代理;本地 IPv4 / IPv6、路由器、公网 IPv4 / IPv6,点击即可拷贝;归属地显示 SVG 国旗、城市、ASN 与网络运营方;各进程上传下载速率。
  • 新增DNS:显示正在使用的 DNS 与配置方式,一键刷新 DNS 缓存,一键切换为自动、Cloudflare、Google、腾讯 DNSPod、阿里云,或手动填写地址(逐个校验为 IPv4 / IPv6)。流量经过 VPN / 代理时提示 DNS 可能被接管。
- 还有 35 项 → + 还有 36 项 →