Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions SystemProfilerExplorer/Core/Explanations/NetworkExplanations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,22 @@ func bluetoothExplanation(path: [String], reportedValue: String) -> FieldExplana
interpretation: "A recorded address shows the accessory was paired at some point. It doesn't show when it last connected, and privacy features can make the address it uses over the air differ.",
privacy: "This address can persistently identify the accessory and link it to its owner. Redact it from public reports."
)
case "device_batteryLevelMain", "device_batteryLevelLeft", "device_batteryLevelRight", "device_batteryLevelCase":
FieldExplanation(
title: "Accessory Battery",
meaning: "This is the battery level a connected accessory reported, for the accessory itself or for one earbud or the charging case.",
significance: "It shows which part needs charging before it stops working.",
interpretation: "The level is a snapshot from when the scan ran. Accessories that aren't connected don't report one.",
privacy: nil
)
case "device_services":
FieldExplanation(
title: "Accessory Services",
meaning: "These are the Bluetooth services (profiles) the accessory supports, such as stereo audio or keyboard input.",
significance: "They show what the accessory can do with this Mac.",
interpretation: "A listed service is supported, not necessarily in use.",
privacy: nil
)
case "device_caseVersion":
FieldExplanation(
title: "Charging Case Firmware",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -886,3 +886,226 @@ private func cardReaderLinkExplanation(_ value: String) -> ValueExplanation? {
])
)
}

// MARK: - Disc burning

// Sources: the macOS samples in https://github.com/glpi-project/glpi-agent
// (resources/macos/system_profiler) show burn_support (DRDeviceSupportLevelAppleShipping),
// device_media (media_none), device_readdvd (yes), interconnect (ATAPI), device_cdwrite
// (-R, -RW), device_dvdwrite (-R, -R DL, -RW, +R, +R DL, +RW), and device_strategies
// (CD-TAO, CD-SAO, CD-Raw, DVD-DAO). The other support levels and interconnects are
// constants of Apple's Disc Recording framework (DRDeviceSupportLevel… and
// DRDevicePhysicalInterconnect…), listed in
// https://developer.apple.com/library/archive/releasenotes/General/APIDiffsMacOSX10_10_3/modules/DiscRecording.html,
// and their exact spelling in system_profiler output is unconfirmed.

let discBurningValueRules: [ValueRule] = [
ValueRule(.discBurning, field: "burn_support") { context in
discSupportLevelExplanation(context.reportedValue)
},

ValueRule(.discBurning, field: "device_media") { context in
switch context.reportedValue {
case "media_none":
.info(
"There was no disc in the drive when the scan ran.",
detail: "The drive is empty.",
why: "Details about a disc appear here only while one is inserted.",
action: "Nothing to do.",
confidence: .observed
)
default:
nil
}
},

ValueRule(.discBurning, field: "device_readdvd") { context in
switch decodeBooleanLike(context.reportedValue) {
case true?:
.info(
"The drive can read DVDs as well as CDs.",
detail: "It can play and copy from DVD discs.",
why: "A CD-only drive can't read DVDs at all.",
action: "Nothing to do.",
confidence: .observed
)
case false?:
.info(
"The drive can't read DVDs, only CDs.",
detail: "It's a CD-only drive.",
why: "DVD discs won't work in it.",
action: "Nothing to do. Use a DVD drive for DVDs.",
confidence: .observed
)
case nil:
nil
}
},

ValueRule(.discBurning, field: "interconnect") { context in
discInterconnectExplanation(context.reportedValue)
},

ValueRule(.discBurning, field: "device_cdwrite") { context in
discWriteFormats(context.reportedValue, names: cdWriteFormatNames).map { formats in
.info(
"The drive can write \(formats).",
detail: "R discs can be written once. RW discs can be erased and written again. These are reported capabilities, not a successful burn or a check of an inserted disc.",
why: "It tells you which blank CDs to buy for this drive.",
action: "Nothing to do.",
confidence: .observed
)
}
},

ValueRule(.discBurning, field: "device_dvdwrite") { context in
discWriteFormats(context.reportedValue, names: dvdWriteFormatNames).map { formats in
.info(
"The drive can write \(formats).",
detail: "R discs can be written once; RW and RAM discs can be written again. DL means dual-layer, which holds about twice as much. The minus and plus formats are two competing standards. These are reported capabilities, not a successful burn or a check of an inserted disc.",
why: "It tells you which blank DVDs to buy for this drive.",
action: "Nothing to do.",
confidence: .observed
)
}
},

ValueRule(.discBurning, field: "device_strategies") { context in
discBurnStrategies(context.reportedValue).map { strategies in
.info(
"Ways the drive can write a disc: \(strategies).",
detail: "Burning apps pick one of these. Writing a whole disc in one pass makes audio CDs without gaps between tracks.",
why: "It only matters if a burning app asks you to choose how to write.",
action: "Nothing to do.",
confidence: .observed
)
}
}
]

private func discSupportLevelExplanation(_ value: String) -> ValueExplanation? {
let why: String = "It describes Apple's Disc Recording framework support. A successful burn also depends on the drive, blank media, and burning software."

return switch value {
case "DRDeviceSupportLevelAppleShipping":
.normal(
"This drive type shipped in an Apple Mac.",
detail: "Apple's Disc Recording framework identifies the drive as a type shipping in an Apple machine. This doesn't establish where this particular drive came from.",
why: why,
action: "Nothing to do.",
confidence: .documented
)
case "DRDeviceSupportLevelAppleSupported":
.normal(
"Apple tested this drive type for Disc Recording support.",
detail: "This support level means Apple tested the drive for use with its Disc Recording framework. It doesn't guarantee that every disc will burn successfully.",
why: why,
action: "Nothing to do.",
confidence: .documented
)
case "DRDeviceSupportLevelVendorSupported":
.info(
"A third party tested this drive type for Disc Recording support.",
detail: "This support level records third-party testing. It doesn't identify the tester or imply that software from the drive's maker is installed.",
why: why,
action: "Nothing to do. If burning fails, check the drive maker's site for a macOS update.",
confidence: .documented
)
case "DRDeviceSupportLevelUnsupported":
.info(
"macOS will still try to burn with this drive despite its unsupported status.",
detail: "Apple's Disc Recording engine attempts to use the drive even though it lacks declared support. A successful burn is not confirmed by this label.",
why: why,
action: "If you need to burn discs, use a drive macOS supports or the drive maker's software.",
confidence: .documented
)
case "DRDeviceSupportLevelNone":
.info(
"Apple's Disc Recording framework has no support for this drive.",
detail: "The drive cannot be used by that engine. This label alone doesn't establish whether the hardware is read-only or whether other software can write with it.",
why: why,
action: "Nothing to do unless you need to burn discs.",
confidence: .documented
)
default:
nil
}
}

private func discInterconnectExplanation(_ value: String) -> ValueExplanation? {
let summary: String
let detail: String

switch value.uppercased() {
case "ATAPI":
summary = "A drive connected over ATAPI."
detail = "ATAPI is an interface for devices such as optical drives, over an ATA or SATA link. It doesn't by itself establish the drive's physical location."
case "USB":
summary = "A drive connected over USB."
detail = "USB is the reported interface. This field alone doesn't establish whether the drive is built in or external."
case "FIREWIRE":
summary = "A drive connected over FireWire."
detail = "FireWire was common on Macs before Thunderbolt."
case "SCSI":
summary = "A drive connected over SCSI, an older connection."
detail = "SCSI drives are rare on current Macs and usually need an adapter."
default:
return nil
}

return .info(
summary,
detail: detail,
why: "It identifies the reported connection interface, which helps choose where to investigate if the drive stops appearing. Physical location is a separate property.",
action: "Nothing to do. If an external drive doesn't appear, connect it directly to the Mac.",
confidence: .documented
)
}

private let cdWriteFormatNames: [String: String] = [
"-R": "CD-R", "-RW": "CD-RW"
]

// Apple Disc Recording write-capability constants define these DVD media types.
// The suffixes also appear in SPDiscBurningReporter's format strings.
private let dvdWriteFormatNames: [String: String] = [
"-R": "DVD-R", "-RW": "DVD-RW", "+R": "DVD+R", "+RW": "DVD+RW",
"-R DL": "DVD-R DL", "-RW DL": "DVD-RW DL", "+R DL": "DVD+R DL",
"-RAM": "DVD-RAM", "+RW DL": "DVD+RW DL"
]

/// Names a complete format list; unfamiliar or empty entries leave the entire value unexplained.
private func discWriteFormats(_ value: String, names: [String: String]) -> String? {
let formats: [String] = value
.split(separator: ",", omittingEmptySubsequences: false)
.map { $0.trimmingCharacters(in: .whitespaces) }
let named: [String] = formats.compactMap { names[$0] }

guard !formats.isEmpty, named.count == formats.count else {
return nil
}

return englishList(named)
}

/// Names each burn strategy in a list such as `CD-TAO, CD-SAO, CD-Raw, DVD-DAO`, or nil
/// when one is unfamiliar.
private func discBurnStrategies(_ value: String) -> String? {
let names: [String: String] = [
"CD-TAO": "CD track at once",
"CD-SAO": "CD session at once",
"CD-Raw": "CD raw mode",
"DVD-DAO": "DVD disc at once",
"BD-DAO": "Blu-ray disc at once"
]
let strategies: [String] = value
.split(separator: ",", omittingEmptySubsequences: false)
.map { $0.trimmingCharacters(in: .whitespaces) }
let named: [String] = strategies.compactMap { names[$0] }

guard !strategies.isEmpty, named.count == strategies.count else {
return nil
}

return englishList(named)
}
Loading
Loading