新增本机信息页:机型、系统、芯片、内存与图形、存储、电池、显示器与标识,序列号默认遮住
This commit is contained in:
1 parent
63c37b0e22
commit
3243570d53
10 files changed
+238
-14
No files matched your search
@@ -21,6 +21,7 @@
|
||||
- CPU 详情:顶部是占用、状态(空闲 / 适中 / 繁忙 / 满载)、与 30 秒前相比的变化和温度余量,下面是走势线与用户 / 系统 / 空闲构成条;核心热力图(每行一个核心、每列一次采样);核心分工(各类核心平均占用与最忙的核心);排队程度(平均负载折算到每个核心,并提示负载在上升还是下降);按应用汇总 CPU。
|
||||
- 内存详情:顶部是还可用多少、压力状态与最近 60 秒的压力走势条;内存水位条(App / 联动 / 压缩 / 缓存 / 空闲);压缩与交换(压缩省下的内存、压缩比、交换区用量与实时换入换出速率,持续写盘时提示);按应用汇总内存(合并辅助进程,显示占已用内存的比例)。
|
||||
- 用 Apple 智能解释进程:在进程上右键或点进程页的星形按钮,由系统自带的本机大模型说明它是什么、占用是否正常、能否退出;把路径、所属应用和签名方一起交给模型以减少猜测,全程不联网(需要 macOS 26 并开启 Apple 智能)。
|
||||
- 本机信息页:机型图与名称、系统版本与版号、芯片与各类核心、内存与图形、存储用量、电池健康与循环次数、每台显示器的尺寸 / 原生分辨率 / 显示分辨率 / 刷新率、机型标识符、序列号(默认遮住,点眼睛图标显示,截图时始终遮住)与启动时间。
|
||||
- 进程管理器(主窗口“进程”页):包括 root 与其他用户的系统进程(通过系统自带的 ps 读取,不需要辅助工具);列出 CPU、CPU 时间、内存、线程、唤醒次数、磁盘读写与用户,点列标题排序;可搜索名称、PID 或用户,筛选全部 / 我的 / 系统,切换按进程或按应用合并;选中后显示路径与详情,可在访达中显示、用 Apple 智能解释、退出或强制退出(二次确认,系统进程与 loginwindow 等禁止结束);底部汇总用户 / 系统 / 空闲与全系统进程数、线程数。
|
||||
- 进程显示应用的本地化名称(如“微信”);按应用汇总时,同一应用的主进程与辅助进程合并计算。
|
||||
- 仪表盘顶部是健康评分和芯片、内存、系统版本、运行时长、机型徽章,下面是 CPU 柱状历史、GPU 折线、内存面积图、磁盘、网络双线、风扇三列卡片,以及核心负载、电池环形图、高占用进程和快捷开关。
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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[..<open].trimmingCharacters(in: .whitespaces)
|
||||
var details = name[name.index(after: open)..<name.index(before: name.endIndex)]
|
||||
.split(separator: ",").map { $0.trimmingCharacters(in: .whitespaces) }
|
||||
var size = ""
|
||||
if let index = details.firstIndex(where: { $0.hasSuffix("-inch") }) {
|
||||
size = " \(details[index].dropLast("-inch".count)) 英寸"
|
||||
details.remove(at: index)
|
||||
}
|
||||
return details.isEmpty ? base + size : "\(base)\(size)(\(details.joined(separator: ",")))"
|
||||
}
|
||||
|
||||
private func chipName(_ brand: String) -> String {
|
||||
brand.hasPrefix("Apple ") ? String(brand.dropFirst("Apple ".count)) : brand
|
||||
}
|
||||
}
|
||||
|
||||
private struct InfoCard<Content: View>: 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -46,10 +46,10 @@ Developed and tested on Apple Silicon. The interface is in Simplified Chinese.
|
||||
<!-- changelog:start -->
|
||||
<!-- Generated from CHANGELOG.md by Scripts/sync_changelog.py. Do not edit by hand. -->
|
||||
|
||||
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)
|
||||
|
||||
<details open>
|
||||
<summary><b>2026-09-13</b> · Unreleased · 28 added · 9 style · 6 fixed</summary>
|
||||
<summary><b>2026-09-13</b> · Unreleased · 29 added · 9 style · 6 fixed</summary>
|
||||
|
||||
**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 折线、内存面积图、磁盘、网络双线、风扇三列卡片,以及核心负载、电池环形图、高占用进程和快捷开关。
|
||||
|
||||
+3
-2
@@ -44,10 +44,10 @@ OpenStats 还没有发布正式版本。第一个版本会在 [Releases](https:/
|
||||
<!-- changelog:start -->
|
||||
<!-- 由 Scripts/sync_changelog.py 从 CHANGELOG.md 生成,请勿手改。 -->
|
||||
|
||||
尚未发布正式版本 · 开发中 **43** 项改动尚未发布 · [完整更新日志](CHANGELOG.md)
|
||||
尚未发布正式版本 · 开发中 **44** 项改动尚未发布 · [完整更新日志](CHANGELOG.md)
|
||||
|
||||
<details open>
|
||||
<summary><b>2026-09-13</b> · 未发布 · 新增 28 · 样式 9 · 修复 6</summary>
|
||||
<summary><b>2026-09-13</b> · 未发布 · 新增 29 · 样式 9 · 修复 6</summary>
|
||||
|
||||
**新增**
|
||||
|
||||
@@ -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 折线、内存面积图、磁盘、网络双线、风扇三列卡片,以及核心负载、电池环形图、高占用进程和快捷开关。
|
||||
|
||||
+2
-2
@@ -347,7 +347,7 @@
|
||||
<div class="log">
|
||||
<article class="log__day">
|
||||
<div class="log__head"><time datetime="2026-09-13">2026-09-13</time><span class="log__tag">未发布</span></div>
|
||||
<p class="log__counts">新增 28 · 样式 9 · 修复 6</p>
|
||||
<p class="log__counts">新增 29 · 样式 9 · 修复 6</p>
|
||||
<ul class="log__list">
|
||||
<li><span class="log__kind">新增</span>菜单栏显示 CPU、GPU、内存、网速、CPU 温度和风扇转速。</li>
|
||||
<li><span class="log__kind">新增</span>菜单栏风格:双行文字、单行文字、图标、圆环、饼图、柱状历史、电量条、状态圆点八种,整体选择一种统一套用,也可以给个别指标单独指定。设置里每种风格都有用示例数据画出的预览和说明,柱状历史注明是最近 10 次采样的变化。</li>
|
||||
@@ -358,7 +358,7 @@
|
||||
<li><span class="log__kind">新增</span>网络详情:上下镜像的流量历史;连接探测格子(每 1 / 2 / 5 秒 ping 一次 Cloudflare、Google、阿里云、腾讯或路由器,显示延迟、抖动、丢包);接口、物理地址、Wi-Fi 信号与速率、VPN / 代理;本地 IPv4 / IPv6、路由器、公网 IPv4 / IPv6,点击即可拷贝;归属地显示 SVG 国旗、城市、ASN 与网络运营方;各进程上传下载速率。</li>
|
||||
<li><span class="log__kind">新增</span>DNS:显示正在使用的 DNS 与配置方式,一键刷新 DNS 缓存,一键切换为自动、Cloudflare、Google、腾讯 DNSPod、阿里云,或手动填写地址(逐个校验为 IPv4 / IPv6)。流量经过 VPN / 代理时提示 DNS 可能被接管。</li>
|
||||
</ul>
|
||||
<a class="log__more" href="https://github.com/gentpan/OpenStats/blob/main/CHANGELOG.md">还有 35 项 →</a>
|
||||
<a class="log__more" href="https://github.com/gentpan/OpenStats/blob/main/CHANGELOG.md">还有 36 项 →</a>
|
||||
</article>
|
||||
</div>
|
||||
<!-- changelog:end -->
|
||||
|
||||
Reference in new issue
Block a user