192 lines
6.7 KiB
Swift
192 lines
6.7 KiB
Swift
import Foundation
|
|
import SwiftUI
|
|
import MacToolsPluginKit
|
|
|
|
public final class LaunchControlPluginFactory: NSObject, MacToolsPluginBundleFactory {
|
|
public static func makeProvider(context: PluginRuntimeContext) throws -> any PluginProvider {
|
|
LaunchControlPluginProvider(context: context)
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private struct LaunchControlPluginProvider: PluginProvider {
|
|
let context: PluginRuntimeContext
|
|
|
|
func makePlugins() -> [any MacToolsPlugin] {
|
|
let localization = PluginLocalization(bundle: context.resourceBundle)
|
|
let controller = LaunchControlController(context: context, localization: localization)
|
|
return [LaunchControlPlugin(context: context, controller: controller, localization: localization)]
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
final class LaunchControlPlugin: MacToolsPlugin, PluginPrimaryPanel {
|
|
enum ControlID {
|
|
static let refresh = "launch-control-refresh"
|
|
static let openManager = "launch-control-open-manager"
|
|
}
|
|
|
|
let metadata: PluginMetadata
|
|
|
|
let primaryPanelDescriptor = PluginPrimaryPanelDescriptor(
|
|
controlStyle: .disclosure,
|
|
menuActionBehavior: .keepPresented
|
|
)
|
|
|
|
var onStateChange: (() -> Void)?
|
|
var requestPermissionGuidance: ((String) -> Void)?
|
|
var shortcutBindingResolver: ((String) -> ShortcutBinding?)?
|
|
|
|
private let controller: LaunchControlController
|
|
private let localization: PluginLocalization
|
|
private var isExpanded = false
|
|
|
|
init(
|
|
context: PluginRuntimeContext = PluginRuntimeContext(pluginID: "launch-control"),
|
|
controller: LaunchControlController? = nil,
|
|
localization: PluginLocalization? = nil
|
|
) {
|
|
let localization = localization ?? PluginLocalization(bundle: context.resourceBundle)
|
|
self.localization = localization
|
|
self.controller = controller ?? LaunchControlController(context: context, localization: localization)
|
|
self.metadata = PluginMetadata(
|
|
id: "launch-control",
|
|
title: localization.string("metadata.title", defaultValue: "启动项"),
|
|
iconName: "powerplug",
|
|
iconTint: Color(nsColor: .systemOrange),
|
|
order: 95,
|
|
defaultDescription: localization.string(
|
|
"metadata.description",
|
|
defaultValue: "查看和管理 launchctl 启动项"
|
|
)
|
|
)
|
|
self.controller.onStateChange = { [weak self] in
|
|
self?.onStateChange?()
|
|
}
|
|
}
|
|
|
|
var primaryPanelState: PluginPanelState {
|
|
let snapshot = controller.snapshot
|
|
return PluginPanelState(
|
|
subtitle: subtitle(for: snapshot),
|
|
isOn: snapshot.isRefreshing,
|
|
isExpanded: isExpanded,
|
|
isEnabled: true,
|
|
isVisible: true,
|
|
detail: isExpanded ? buildDetail(for: snapshot) : nil,
|
|
errorMessage: snapshot.errorMessage
|
|
)
|
|
}
|
|
|
|
var permissionRequirements: [PluginPermissionRequirement] { [] }
|
|
var settingsSections: [PluginSettingsSection] { [] }
|
|
var shortcutDefinitions: [PluginShortcutDefinition] { [] }
|
|
var configuration: PluginConfiguration? {
|
|
let localization = localization
|
|
return PluginConfiguration(description: metadata.defaultDescription) { _ in
|
|
LaunchControlManagerView(controller: self.controller, localization: localization)
|
|
}
|
|
}
|
|
|
|
func refresh() {
|
|
if controller.snapshot.items.isEmpty {
|
|
controller.refresh()
|
|
}
|
|
}
|
|
|
|
func deactivate(reason: PluginDeactivationReason) {
|
|
controller.cancelRefresh()
|
|
}
|
|
|
|
func handleAction(_ action: PluginPanelAction) {
|
|
switch action {
|
|
case let .setDisclosureExpanded(value):
|
|
isExpanded = value
|
|
onStateChange?()
|
|
case let .invokeAction(controlID):
|
|
if controlID == ControlID.refresh {
|
|
controller.refresh()
|
|
}
|
|
case .setSwitch,
|
|
.setSelection,
|
|
.setNavigationSelection,
|
|
.clearNavigationSelection,
|
|
.setDate,
|
|
.setSlider:
|
|
break
|
|
}
|
|
}
|
|
|
|
func permissionState(for permissionID: String) -> PluginPermissionState {
|
|
PluginPermissionState(isGranted: true, footnote: nil)
|
|
}
|
|
|
|
func handlePermissionAction(id: String) {}
|
|
func handleSettingsAction(id: String) {}
|
|
func handleShortcutAction(id: String) {}
|
|
|
|
private func buildDetail(for snapshot: LaunchControlSnapshot) -> PluginPanelDetail {
|
|
let refreshControl = PluginPanelControl(
|
|
id: ControlID.refresh,
|
|
kind: .actionRow,
|
|
options: [],
|
|
selectedOptionID: nil,
|
|
dateValue: nil,
|
|
minimumDate: nil,
|
|
displayedComponents: nil,
|
|
datePickerStyle: nil,
|
|
sectionTitle: nil,
|
|
actionTitle: snapshot.isRefreshing
|
|
? localization.string("panel.action.refreshing", defaultValue: "正在刷新")
|
|
: localization.string("panel.action.refresh", defaultValue: "刷新列表"),
|
|
actionIconSystemName: "arrow.clockwise",
|
|
isEnabled: !snapshot.isRefreshing
|
|
)
|
|
|
|
let openManagerControl = PluginPanelControl(
|
|
id: ControlID.openManager,
|
|
kind: .actionRow,
|
|
options: [],
|
|
selectedOptionID: nil,
|
|
dateValue: nil,
|
|
minimumDate: nil,
|
|
displayedComponents: nil,
|
|
datePickerStyle: nil,
|
|
sectionTitle: nil,
|
|
actionTitle: localization.string("panel.action.openManager", defaultValue: "打开管理器"),
|
|
actionIconSystemName: "arrow.up.right.square",
|
|
actionBehavior: .dismissBeforeHandling,
|
|
showsLeadingDivider: true,
|
|
isEnabled: true
|
|
)
|
|
|
|
return PluginPanelDetail(primaryControls: [refreshControl, openManagerControl], secondaryPanel: nil)
|
|
}
|
|
|
|
private func subtitle(for snapshot: LaunchControlSnapshot) -> String {
|
|
if snapshot.isRefreshing {
|
|
return localization.string(
|
|
"panel.subtitle.refreshing",
|
|
defaultValue: "正在扫描 LaunchAgent 与 LaunchDaemon"
|
|
)
|
|
}
|
|
|
|
if snapshot.items.isEmpty {
|
|
return localization.string(
|
|
"panel.subtitle.empty",
|
|
defaultValue: "打开管理器或刷新后查看启动项"
|
|
)
|
|
}
|
|
|
|
let userCreatedCount = snapshot.items.filter { $0.origin == .userCreated }.count
|
|
let runningCount = snapshot.items.filter { $0.state == .running }.count
|
|
return localization.format(
|
|
"panel.subtitle.summary",
|
|
defaultValue: "%d 项 · %d 运行中 · %d 用户创建",
|
|
snapshot.items.count,
|
|
runningCount,
|
|
userCreatedCount
|
|
)
|
|
}
|
|
}
|