fix: 自带刷新的弹窗不提高采样频率,过期风扇数据不显示启动中
- AI 用量、显示器弹窗及仅含这两项的合并面板沿用后台采样间隔 - 风扇读取失败沿用旧数据时清除 isStarting,避免“启动中”超出 10 秒窗口 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
8919dabb6c
commit
8244dd15c1
6 files changed
+117
-4
No files matched your search
+5
-1
@@ -167,7 +167,11 @@ its own `NSStatusItem` (created in reverse so they read left to right) and opens
|
||||
popover for that metric; in the *combined* layout a single item opens `CombinedPopoverView`: a status
|
||||
overview of compact metric rows. Clicking a row expands its existing `PopoverDetail` inline;
|
||||
only one row expands at a time. Summary rows remain visible, so their sampling demand is retained
|
||||
alongside the expanded detail demand. The header keeps the main-window and settings
|
||||
alongside the expanded detail demand. Only visible system-metric rows raise the overview's
|
||||
sampling rate; an empty overview or one containing only AI usage and displays retains the
|
||||
configured background interval unless a live window or system-metric detail requires more.
|
||||
Standalone AI usage and display popovers likewise keep the background interval.
|
||||
The header keeps the main-window and settings
|
||||
actions outside the scroll area; no bottom action bar is shown. Header and content contribute to
|
||||
the same measured panel height. Collapsing or switching a row releases its
|
||||
detail-only demand without rebuilding the panel. No timer or continuous animation is added.
|
||||
|
||||
@@ -146,9 +146,14 @@ public final class AppModel {
|
||||
// 仅实时监控页需要每秒采样;历史、工具和设置页沿用用户设置的后台间隔。
|
||||
let liveWindow = window && [.overview, .system, .cpu, .gpu, .memory, .disk,
|
||||
.network, .thermal, .battery].contains(tab)
|
||||
// AI 用量与显示器有各自的刷新机制,不读取采样结果,不应提高全局采样频率。
|
||||
let selfRefreshing: Set<MenuBarItem> = [.aiUsage, .display]
|
||||
let livePopover = popover.map { !selfRefreshing.contains($0) } ?? false
|
||||
// 合并面板的摘要始终可见;只有其中的系统指标需要提高全局采样频率。
|
||||
let liveOverview = !overview.subtracting(selfRefreshing).isEmpty
|
||||
// 进程页要读全系统进程(启动 ps),每 2 秒刷新一次足够,也更省电
|
||||
demand.interval = processPage && popover == nil ? .seconds(2)
|
||||
: liveWindow || popover != nil || isCombinedPopoverOpen ? .seconds(1) : .seconds(settings.refreshSeconds)
|
||||
demand.interval = processPage && !livePopover ? .seconds(2)
|
||||
: liveWindow || livePopover || liveOverview ? .seconds(1) : .seconds(settings.refreshSeconds)
|
||||
demand.memory = true
|
||||
demand.network = true
|
||||
let showing = { (page: PanelTab, item: MenuBarItem) in (window && tab == page) || popover == item }
|
||||
|
||||
@@ -136,7 +136,13 @@ public final class MetricsStore {
|
||||
var byGroup = Dictionary(uniqueKeysWithValues: old.temperatures.map { ($0.group, $0) })
|
||||
for summary in new.temperatures { byGroup[summary.group] = summary }
|
||||
let ordered = TemperatureGroup.allCases.compactMap { byGroup[$0] }
|
||||
sensors = SensorReadings(temperatures: ordered, fans: fanCount == 0 ? [] : (new.fans.isEmpty ? old.fans : new.fans), fanCount: fanCount)
|
||||
// 读取失败时沿用的旧风扇数据不再推进起步窗口,去掉“启动中”以免提示超出时限。
|
||||
let fans = new.fans.isEmpty ? old.fans.map { fan in
|
||||
var fan = fan
|
||||
fan.isStarting = false
|
||||
return fan
|
||||
} : new.fans
|
||||
sensors = SensorReadings(temperatures: ordered, fans: fanCount == 0 ? [] : fans, fanCount: fanCount)
|
||||
}
|
||||
|
||||
var fastestFan: FanState? {
|
||||
|
||||
@@ -55,4 +55,20 @@ struct FanAvailabilityTests {
|
||||
store.apply(snapshot)
|
||||
#expect(store.supportsFans)
|
||||
}
|
||||
|
||||
@Test func staleFansReusedAfterReadFailureDropStartingState() {
|
||||
let store = MetricsStore()
|
||||
var snapshot = MetricsSnapshot()
|
||||
let starting = FanState(id: 0, current: 0, minimum: 0, maximum: 6000, target: 2000, isManual: true,
|
||||
isStarting: true)
|
||||
snapshot.sensors = SensorReadings(temperatures: [], fans: [starting], fanCount: 1)
|
||||
store.apply(snapshot)
|
||||
#expect(store.sensors?.fans.isStartingUp == true)
|
||||
// 读取失败返回空数组:沿用旧读数,但不再显示“启动中”。
|
||||
snapshot.sensors = SensorReadings(temperatures: [], fans: [])
|
||||
store.apply(snapshot)
|
||||
#expect(store.sensors?.fans.count == 1)
|
||||
#expect(store.sensors?.fans.first?.target == 2000)
|
||||
#expect(store.sensors?.fans.isStartingUp == false)
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,74 @@ struct MenuBarLayoutTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test(arguments: [MenuBarLayout.combined, .iconOnly])
|
||||
func independentRefreshItemsPreserveBackgroundSampling(layout: MenuBarLayout) throws {
|
||||
try model { model, _ in
|
||||
model.settings.menuBarLayout = layout
|
||||
model.settings.refreshSeconds = 5
|
||||
model.settings.aiUsageEnabled = true
|
||||
let selections: [Set<MenuBarItem>] = [[], [.display], [.aiUsage], [.display, .aiUsage]]
|
||||
for items in selections {
|
||||
model.settings.menuBarItems = items
|
||||
let idle = model.demand
|
||||
#expect(idle.interval == .seconds(5))
|
||||
model.isCombinedPopoverOpen = true
|
||||
#expect(model.demand == idle)
|
||||
for item in items {
|
||||
model.combinedPopoverTab = item
|
||||
#expect(model.demand == idle)
|
||||
if item == .display { #expect(model.displayControlsVisible) }
|
||||
}
|
||||
model.combinedPopoverTab = nil
|
||||
#expect(model.demand == idle)
|
||||
model.isCombinedPopoverOpen = false
|
||||
#expect(model.demand == idle)
|
||||
#expect(!model.displayControlsVisible)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(arguments: [MenuBarLayout.combined, .iconOnly])
|
||||
func systemMetricsStillRefreshWhileDisplayDetailsAreExpanded(layout: MenuBarLayout) throws {
|
||||
try model { model, _ in
|
||||
model.settings.menuBarLayout = layout
|
||||
model.settings.refreshSeconds = 5
|
||||
var snapshot = MetricsSnapshot()
|
||||
snapshot.sensors = SensorReadings(temperatures: [], fans: [], fanCount: 1)
|
||||
model.store.apply(snapshot)
|
||||
for item: MenuBarItem in [.cpu, .memory, .network, .gpu, .disk, .temperature, .fan, .battery] {
|
||||
model.settings.menuBarItems = [.display, item]
|
||||
let idle = model.demand
|
||||
model.isCombinedPopoverOpen = true
|
||||
#expect(model.demand.interval == .seconds(1))
|
||||
model.combinedPopoverTab = .display
|
||||
#expect(model.demand.interval == .seconds(1))
|
||||
#expect(model.displayControlsVisible)
|
||||
model.isCombinedPopoverOpen = false
|
||||
model.combinedPopoverTab = nil
|
||||
#expect(model.demand == idle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test func mainWindowDemandStillOverridesIndependentPanelRefresh() throws {
|
||||
try model { model, _ in
|
||||
model.settings.menuBarLayout = .iconOnly
|
||||
model.settings.menuBarItems = [.display]
|
||||
model.settings.refreshSeconds = 5
|
||||
model.isCombinedPopoverOpen = true
|
||||
model.combinedPopoverTab = .display
|
||||
model.isMainWindowVisible = true
|
||||
model.settings.panelTab = .cpu
|
||||
#expect(model.demand.interval == .seconds(1))
|
||||
model.settings.panelTab = .settingsGeneral
|
||||
#expect(model.demand.interval == .seconds(5))
|
||||
model.settings.processesEnabled = true
|
||||
model.settings.panelTab = .processes
|
||||
#expect(model.demand.interval == .seconds(2))
|
||||
}
|
||||
}
|
||||
|
||||
@Test func compactOverviewHasNoHalfEmptyRowsAndExpandsInline() throws {
|
||||
try model { model, _ in
|
||||
model.settings.menuBarItems = [.cpu, .memory, .network, .disk, .battery]
|
||||
|
||||
@@ -522,6 +522,20 @@ private func isolatedDefaults() -> UserDefaults {
|
||||
#expect(model.demand.interval == .seconds(1))
|
||||
}
|
||||
|
||||
@Test func popoversWithoutLiveMetricsKeepSamplingInterval() {
|
||||
let model = model()
|
||||
model.settings.refreshSeconds = 5
|
||||
for item: MenuBarItem in [.aiUsage, .display] {
|
||||
model.openPopover = item
|
||||
#expect(model.demand.interval == .seconds(5), "\(item) popover should not boost sampling")
|
||||
}
|
||||
|
||||
model.settings.processesEnabled = true
|
||||
model.isMainWindowVisible = true
|
||||
model.settings.panelTab = .processes
|
||||
#expect(model.demand.interval == .seconds(2))
|
||||
}
|
||||
|
||||
@Test func networkDetailVisibility() {
|
||||
let model = model()
|
||||
#expect(!model.isNetworkDetailVisible)
|
||||
|
||||
Reference in new issue
Block a user