Compare commits
4
Commits
v0.0.2
..
5676279a4c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5676279a4c | ||
|
|
7ba4284be1 | ||
|
|
01e871dc2c | ||
|
|
beabf016b0 |
@@ -12,7 +12,7 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
build-and-test:
|
build-and-test:
|
||||||
name: Build & Test (Intel x86_64)
|
name: Build & Test (Intel x86_64)
|
||||||
runs-on: macos-14
|
runs-on: macos-12
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
@@ -78,7 +78,7 @@ jobs:
|
|||||||
release-package:
|
release-package:
|
||||||
name: Package Release Artifact
|
name: Package Release Artifact
|
||||||
needs: build-and-test
|
needs: build-and-test
|
||||||
runs-on: macos-14
|
runs-on: macos-12
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -25,3 +25,7 @@ xcuserdata/
|
|||||||
*.dmg
|
*.dmg
|
||||||
*.zip
|
*.zip
|
||||||
*.app
|
*.app
|
||||||
|
FIRST-PASS-REFERENCE.md
|
||||||
|
SECOND-PASS-REFERENCE.md
|
||||||
|
Docs/AppleSilicon-plan.md
|
||||||
|
Docs/Monterey-plan.md
|
||||||
|
|||||||
+3
-3
@@ -3,7 +3,7 @@
|
|||||||
## 1. Project Specifications & Architectural Constraints
|
## 1. Project Specifications & Architectural Constraints
|
||||||
|
|
||||||
- **Platform Target:** macOS for Intel (`x86_64`).
|
- **Platform Target:** macOS for Intel (`x86_64`).
|
||||||
- **Minimum OS Version:** macOS 14.0 (Sonoma) or higher.
|
- **Minimum OS Version:** macOS 12.0 (Monterey) or higher.
|
||||||
- **Language Stack:** SwiftUI (Presentation & ViewModels) + Objective-C / C (Low-level Mach, IOKit, SMC, BSD Sysctl APIs).
|
- **Language Stack:** SwiftUI (Presentation & ViewModels) + Objective-C / C (Low-level Mach, IOKit, SMC, BSD Sysctl APIs).
|
||||||
- **Interoperability:** Modular Objective-C bridging layer (`MacMonitor-Bridging-Header.h`) exposing thread-safe C primitives to Swift.
|
- **Interoperability:** Modular Objective-C bridging layer (`MacMonitor-Bridging-Header.h`) exposing thread-safe C primitives to Swift.
|
||||||
- **CI/CD Platform:** Gitea Actions executing on Intel macOS runners (`[macos, intel]`).
|
- **CI/CD Platform:** Gitea Actions executing on Intel macOS runners (`[macos, intel]`).
|
||||||
@@ -144,7 +144,7 @@ graph TD
|
|||||||
2. **Phase 1.2 (Parallel Tracks):**
|
2. **Phase 1.2 (Parallel Tracks):**
|
||||||
- **Track 1.2A:** `#2` AppleSMC Client (`feat/2-smc-client`). Low-level SMC key reader, data parsers (`sp78`, `fpe2`, `flt`).
|
- **Track 1.2A:** `#2` AppleSMC Client (`feat/2-smc-client`). Low-level SMC key reader, data parsers (`sp78`, `fpe2`, `flt`).
|
||||||
- **Track 1.2B:** `#25` Gitea CI/CD Pipeline (`feat/25-gitea-ci`). Create `.gitea/workflows/build.yml` targeting Intel runners.
|
- **Track 1.2B:** `#25` Gitea CI/CD Pipeline (`feat/25-gitea-ci`). Create `.gitea/workflows/build.yml` targeting Intel runners.
|
||||||
- **Quality Gate M1:** Successful build and execution of sample test suite on Gitea Actions runner compiling for `x86_64` macOS 14.
|
- **Quality Gate M1:** Successful build and execution of sample test suite on Gitea Actions runner compiling for `x86_64` macOS 12.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -189,7 +189,7 @@ graph TD
|
|||||||
|
|
||||||
### Milestone 4: Presentation, Visuals & Menu Bar Integration
|
### Milestone 4: Presentation, Visuals & Menu Bar Integration
|
||||||
- **Integration Branch:** `milestone/m4-presentation`
|
- **Integration Branch:** `milestone/m4-presentation`
|
||||||
- **Goal:** User-facing presentation layer: macOS 14 Menu Bar Extra, popover widgets, display brightness management, and Swift Charts rolling graphs.
|
- **Goal:** User-facing presentation layer: macOS 12 Menu Bar Extra, popover widgets, display brightness management, and Swift Charts rolling graphs.
|
||||||
- **Execution Order:**
|
- **Execution Order:**
|
||||||
1. `#13` Screen Brightness & Display Management (`feat/13-display-brightness`)
|
1. `#13` Screen Brightness & Display Management (`feat/13-display-brightness`)
|
||||||
2. `#22` Status Bar Menu Bar Extra & Popover (`feat/22-menubar-popover`)
|
2. `#22` Status Bar Menu Bar Extra & Popover (`feat/22-menubar-popover`)
|
||||||
|
|||||||
@@ -698,7 +698,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.i3omb.MacMonitor;
|
PRODUCT_BUNDLE_IDENTIFIER = com.i3omb.MacMonitor;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Sources/Bridging/MacMonitor-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Sources/Bridging/MacMonitor-Bridging-Header.h";
|
||||||
@@ -720,7 +720,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
"@loader_path/../Frameworks",
|
"@loader_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.i3omb.MacMonitorTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.i3omb.MacMonitorTests;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Sources/Bridging/MacMonitor-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Sources/Bridging/MacMonitor-Bridging-Header.h";
|
||||||
@@ -778,7 +778,7 @@
|
|||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||||
MARKETING_VERSION = 0.0.2;
|
MARKETING_VERSION = 0.0.2;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
@@ -807,7 +807,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
"@loader_path/../Frameworks",
|
"@loader_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.i3omb.MacMonitorTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.i3omb.MacMonitorTests;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Sources/Bridging/MacMonitor-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Sources/Bridging/MacMonitor-Bridging-Header.h";
|
||||||
@@ -871,7 +871,7 @@
|
|||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||||
MARKETING_VERSION = 0.0.2;
|
MARKETING_VERSION = 0.0.2;
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
@@ -902,7 +902,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.i3omb.MacMonitor;
|
PRODUCT_BUNDLE_IDENTIFIER = com.i3omb.MacMonitor;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Sources/Bridging/MacMonitor-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Sources/Bridging/MacMonitor-Bridging-Header.h";
|
||||||
|
|||||||
@@ -1,19 +1,66 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
|
||||||
/// Bridges app-lifecycle events into SwiftUI: requests Notification Center
|
/// Bridges app-lifecycle events into SwiftUI: requests Notification Center
|
||||||
/// authorization once at launch so threshold alerts can post banners.
|
/// authorization once at launch so threshold alerts can post banners.
|
||||||
final class AppDelegate: NSObject, NSApplicationDelegate {
|
final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
|
var statusItem: NSStatusItem?
|
||||||
|
var popover: NSPopover?
|
||||||
|
var cancellable: AnyCancellable?
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||||
if AlertEngine.shared.configuration.alertsEnabled {
|
if AlertEngine.shared.configuration.alertsEnabled {
|
||||||
AlertEngine.shared.requestNotificationAuthorization()
|
AlertEngine.shared.requestNotificationAuthorization()
|
||||||
}
|
}
|
||||||
|
setupMenuBar()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupMenuBar() {
|
||||||
|
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||||
|
|
||||||
|
let popover = NSPopover()
|
||||||
|
popover.contentSize = NSSize(width: 320, height: 400)
|
||||||
|
popover.behavior = .transient
|
||||||
|
popover.contentViewController = NSHostingController(rootView: QuickGlancePopoverView(store: SystemTelemetryStore.shared))
|
||||||
|
self.popover = popover
|
||||||
|
|
||||||
|
if let button = statusItem?.button {
|
||||||
|
button.action = #selector(togglePopover(_:))
|
||||||
|
button.target = self
|
||||||
|
}
|
||||||
|
|
||||||
|
cancellable = SystemTelemetryStore.shared.$latestSnapshot.sink { [weak self] _ in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self?.updateMenuBarLabel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateMenuBarLabel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func togglePopover(_ sender: AnyObject?) {
|
||||||
|
guard let button = statusItem?.button, let popover = popover else { return }
|
||||||
|
if popover.isShown {
|
||||||
|
popover.performClose(sender)
|
||||||
|
} else {
|
||||||
|
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateMenuBarLabel() {
|
||||||
|
guard let button = statusItem?.button else { return }
|
||||||
|
let cpu = SystemTelemetryStore.shared.cpuLoad.totalLoad
|
||||||
|
let mem = SystemTelemetryStore.shared.memory.utilizationPercentage
|
||||||
|
let alerts = AlertEngine.shared.activeAlerts.count
|
||||||
|
|
||||||
|
let alertStr = alerts > 0 ? "⚠️ " : ""
|
||||||
|
button.title = String(format: "%@CPU: %.0f%% • RAM: %.0f%%", alertStr, cpu, mem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct MacMonitorApp: App {
|
struct MacMonitorApp: App {
|
||||||
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
|
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
|
||||||
@State private var store = SystemTelemetryStore.shared
|
@StateObject private var store = SystemTelemetryStore.shared
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
@@ -21,18 +68,6 @@ struct MacMonitorApp: App {
|
|||||||
}
|
}
|
||||||
.windowStyle(.titleBar)
|
.windowStyle(.titleBar)
|
||||||
.windowToolbarStyle(.unified)
|
.windowToolbarStyle(.unified)
|
||||||
.defaultSize(width: 960, height: 640)
|
|
||||||
|
|
||||||
MenuBarExtra {
|
|
||||||
QuickGlancePopoverView(store: store)
|
|
||||||
} label: {
|
|
||||||
MenuBarStatusView(
|
|
||||||
cpuLoad: store.cpuLoad.totalLoad,
|
|
||||||
memoryPercent: store.memory.utilizationPercentage,
|
|
||||||
activeAlertCount: store.alertEngine.activeAlerts.count
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.menuBarExtraStyle(.window)
|
|
||||||
|
|
||||||
Settings {
|
Settings {
|
||||||
AlertsSettingsView()
|
AlertsSettingsView()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Observation
|
import Combine
|
||||||
|
|
||||||
// MARK: - Typed Telemetry Models
|
// MARK: - Typed Telemetry Models
|
||||||
|
|
||||||
@@ -220,39 +220,38 @@ public struct AudioDeviceItem: Identifiable, Sendable {
|
|||||||
|
|
||||||
// MARK: - SystemTelemetryStore
|
// MARK: - SystemTelemetryStore
|
||||||
|
|
||||||
@Observable
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public final class SystemTelemetryStore {
|
public final class SystemTelemetryStore: ObservableObject {
|
||||||
public static let shared = SystemTelemetryStore()
|
public static let shared = SystemTelemetryStore()
|
||||||
|
|
||||||
public private(set) var isRunning: Bool = false
|
@Published public private(set) var isRunning: Bool = false
|
||||||
public private(set) var lastUpdateTimestamp: Date = .now
|
@Published public private(set) var lastUpdateTimestamp: Date = .now
|
||||||
public private(set) var sampleInterval: TimeInterval = 1.0
|
@Published public private(set) var sampleInterval: TimeInterval = 1.0
|
||||||
|
|
||||||
/// Raw snapshots organized by provider identifier
|
/// Raw snapshots organized by provider identifier
|
||||||
public private(set) var latestSnapshot: [String: [String: Any]] = [:]
|
@Published public private(set) var latestSnapshot: [String: [String: Any]] = [:]
|
||||||
|
|
||||||
/// Milestone 2 Structured Metrics
|
/// Milestone 2 Structured Metrics
|
||||||
public private(set) var cpuLoad = CPULoadMetrics()
|
@Published public private(set) var cpuLoad = CPULoadMetrics()
|
||||||
public private(set) var memory = MemoryMetrics()
|
@Published public private(set) var memory = MemoryMetrics()
|
||||||
public private(set) var storageVolumes: [StorageVolumeItem] = []
|
@Published public private(set) var storageVolumes: [StorageVolumeItem] = []
|
||||||
public private(set) var cpuThermal = CPUThermalMetrics()
|
@Published public private(set) var cpuThermal = CPUThermalMetrics()
|
||||||
public private(set) var fans: [FanTelemetryItem] = []
|
@Published public private(set) var fans: [FanTelemetryItem] = []
|
||||||
public private(set) var componentTemps: [ComponentThermalItem] = []
|
@Published public private(set) var componentTemps: [ComponentThermalItem] = []
|
||||||
public private(set) var power = PowerMetrics()
|
@Published public private(set) var power = PowerMetrics()
|
||||||
|
|
||||||
/// Milestone 3 Structured Metrics
|
/// Milestone 3 Structured Metrics
|
||||||
public private(set) var kernelCounters = KernelCountersMetrics()
|
@Published public private(set) var kernelCounters = KernelCountersMetrics()
|
||||||
public private(set) var systemLoad = SystemLoadMetrics()
|
@Published public private(set) var systemLoad = SystemLoadMetrics()
|
||||||
public private(set) var diskIO: [DiskIOItem] = []
|
@Published public private(set) var diskIO: [DiskIOItem] = []
|
||||||
public private(set) var networkBandwidth: [NetworkBandwidthItem] = []
|
@Published public private(set) var networkBandwidth: [NetworkBandwidthItem] = []
|
||||||
public private(set) var networkSockets: [SocketItem] = []
|
@Published public private(set) var networkSockets: [SocketItem] = []
|
||||||
public private(set) var processes: [ProcessItem] = []
|
@Published public private(set) var processes: [ProcessItem] = []
|
||||||
public private(set) var gpus: [GPUCardItem] = []
|
@Published public private(set) var gpus: [GPUCardItem] = []
|
||||||
public private(set) var batteryHealth = BatteryHealthMetrics()
|
@Published public private(set) var batteryHealth = BatteryHealthMetrics()
|
||||||
public private(set) var peripherals: [PeripheralDeviceItem] = []
|
@Published public private(set) var peripherals: [PeripheralDeviceItem] = []
|
||||||
public private(set) var audioDevices: [AudioDeviceItem] = []
|
@Published public private(set) var audioDevices: [AudioDeviceItem] = []
|
||||||
public private(set) var displays: [MMDisplayInfo] = []
|
@Published public private(set) var displays: [MMDisplayInfo] = []
|
||||||
|
|
||||||
/// Historical Telemetry Store
|
/// Historical Telemetry Store
|
||||||
public let historyStore = TelemetryHistoryStore.shared
|
public let historyStore = TelemetryHistoryStore.shared
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
|
||||||
public struct TelemetrySnapshotRecord {
|
public struct TelemetrySnapshotRecord {
|
||||||
public let cpuLoad: Double
|
public let cpuLoad: Double
|
||||||
@@ -35,8 +36,7 @@ public struct TelemetrySnapshotRecord {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Observable
|
public final class TelemetryHistoryStore: ObservableObject {
|
||||||
public final class TelemetryHistoryStore {
|
|
||||||
public static let shared = TelemetryHistoryStore()
|
public static let shared = TelemetryHistoryStore()
|
||||||
|
|
||||||
public let cpuHistory = RollingRingBuffer<Double>(capacity: 60)
|
public let cpuHistory = RollingRingBuffer<Double>(capacity: 60)
|
||||||
@@ -48,7 +48,7 @@ public final class TelemetryHistoryStore {
|
|||||||
public let cpuTempHistory = RollingRingBuffer<Double>(capacity: 60)
|
public let cpuTempHistory = RollingRingBuffer<Double>(capacity: 60)
|
||||||
public let powerHistory = RollingRingBuffer<Double>(capacity: 60)
|
public let powerHistory = RollingRingBuffer<Double>(capacity: 60)
|
||||||
|
|
||||||
public private(set) var sampleCounter: UInt64 = 0
|
@Published public private(set) var sampleCounter: UInt64 = 0
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
|
import Combine
|
||||||
|
|
||||||
// MARK: - Alert Engine
|
// MARK: - Alert Engine
|
||||||
|
|
||||||
@@ -13,18 +14,17 @@ import UserNotifications
|
|||||||
/// persistent -> re-fire only after `cooldownMinutes` elapse
|
/// persistent -> re-fire only after `cooldownMinutes` elapse
|
||||||
/// recovery -> remove from active set, log `.resolved`, clear cooldown so
|
/// recovery -> remove from active set, log `.resolved`, clear cooldown so
|
||||||
/// the next breach alerts immediately
|
/// the next breach alerts immediately
|
||||||
@Observable
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public final class AlertEngine {
|
public final class AlertEngine: ObservableObject {
|
||||||
public static let shared = AlertEngine()
|
public static let shared = AlertEngine()
|
||||||
|
|
||||||
/// Currently breached conditions, refreshed every evaluation pass.
|
/// Currently breached conditions, refreshed every evaluation pass.
|
||||||
public private(set) var activeAlerts: [SystemAlert] = []
|
@Published public private(set) var activeAlerts: [SystemAlert] = []
|
||||||
/// Newest-first audit log of trigger/resolve events (capped).
|
/// Newest-first audit log of trigger/resolve events (capped).
|
||||||
public private(set) var history: [AlertEvent] = []
|
@Published public private(set) var history: [AlertEvent] = []
|
||||||
|
|
||||||
/// Threshold settings. Mutations are persisted immediately.
|
/// Threshold settings. Mutations are persisted immediately.
|
||||||
public var configuration: AlertConfiguration {
|
@Published public var configuration: AlertConfiguration {
|
||||||
didSet { configuration.save(to: defaults) }
|
didSet { configuration.save(to: defaults) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
|
import Combine
|
||||||
|
|
||||||
// MARK: - Navigation Bus
|
// MARK: - Navigation Bus
|
||||||
|
|
||||||
/// Lightweight bus letting notification action handlers steer the main window:
|
/// Lightweight bus letting notification action handlers steer the main window:
|
||||||
/// tapping a notification (or its "View Processes" action) activates the app and
|
/// tapping a notification (or its "View Processes" action) activates the app and
|
||||||
/// selects the requested sidebar tab inside `ContentView`.
|
/// selects the requested sidebar tab inside `ContentView`.
|
||||||
@Observable
|
public final class AppNavigationBus: ObservableObject {
|
||||||
public final class AppNavigationBus {
|
|
||||||
public static let shared = AppNavigationBus()
|
public static let shared = AppNavigationBus()
|
||||||
|
|
||||||
/// Sidebar tag the main window should switch to (e.g. `"Processes"`).
|
/// Sidebar tag the main window should switch to (e.g. `"Processes"`).
|
||||||
/// `nil` means "just bring the window forward".
|
/// `nil` means "just bring the window forward".
|
||||||
public private(set) var requestedTab: String?
|
@Published public private(set) var requestedTab: String?
|
||||||
/// Monotonic counter so repeated taps on the same tab still trigger `onChange`.
|
/// Monotonic counter so repeated taps on the same tab still trigger `onChange`.
|
||||||
public private(set) var requestCounter: Int = 0
|
@Published public private(set) var requestCounter: Int = 0
|
||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
public struct ContentView: View {
|
public struct ContentView: View {
|
||||||
@State private var store = SystemTelemetryStore.shared
|
@StateObject private var store = SystemTelemetryStore.shared
|
||||||
@State private var selectedTab: String = "Dashboard"
|
@State private var selectedTab: String = "Dashboard"
|
||||||
@State private var processSearchText: String = ""
|
@State private var processSearchText: String = ""
|
||||||
@State private var socketSearchText: String = ""
|
@State private var socketSearchText: String = ""
|
||||||
@State private var selectedPID: Int32? = nil
|
@State private var selectedPID: Int32? = nil
|
||||||
@State private var showingProcessDetail: Bool = false
|
@State private var showingProcessDetail: Bool = false
|
||||||
@State private var navBus = AppNavigationBus.shared
|
@StateObject private var navBus = AppNavigationBus.shared
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
NavigationSplitView {
|
NavigationView {
|
||||||
sidebarContent
|
sidebarContent
|
||||||
} detail: {
|
|
||||||
detailContent
|
detailContent
|
||||||
}
|
}
|
||||||
.frame(minWidth: 1000, minHeight: 700)
|
.frame(minWidth: 1000, minHeight: 700)
|
||||||
@@ -26,7 +25,7 @@ public struct ContentView: View {
|
|||||||
ProcessDetailSheet(inspector: store.processInspector, pid: pid)
|
ProcessDetailSheet(inspector: store.processInspector, pid: pid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: navBus.requestCounter) { _, _ in
|
.onChange(of: navBus.requestCounter) { _ in
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
if let window = NSApp.windows.first(where: { $0.canBecomeMain }) {
|
if let window = NSApp.windows.first(where: { $0.canBecomeMain }) {
|
||||||
window.makeKeyAndOrderFront(nil)
|
window.makeKeyAndOrderFront(nil)
|
||||||
@@ -109,7 +108,7 @@ public struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.listStyle(.sidebar)
|
.listStyle(.sidebar)
|
||||||
.navigationSplitViewColumnWidth(min: 210, ideal: 240, max: 300)
|
.frame(minWidth: 210, idealWidth: 240, maxWidth: 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Detail Content Router
|
// MARK: - Detail Content Router
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import UserNotifications
|
|||||||
/// Preferences UI for threshold alert rules. Bound directly to
|
/// Preferences UI for threshold alert rules. Bound directly to
|
||||||
/// `AlertEngine.shared.configuration`, which auto-persists to `UserDefaults`.
|
/// `AlertEngine.shared.configuration`, which auto-persists to `UserDefaults`.
|
||||||
public struct AlertsSettingsView: View {
|
public struct AlertsSettingsView: View {
|
||||||
@Bindable private var engine = AlertEngine.shared
|
@ObservedObject private var engine = AlertEngine.shared
|
||||||
@State private var authorizationStatus: UNAuthorizationStatus = .notDetermined
|
@State private var authorizationStatus: UNAuthorizationStatus = .notDetermined
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|||||||
Reference in New Issue
Block a user