bundle third-party licenses in app and add Licenses button to About

Add THIRD_PARTY_LICENSES.md as a bundled resource so it ships inside
the .app bundle. Add a "Licenses" button to the About panel that opens
a scrollable window displaying the file contents.
This commit is contained in:
Lawrence Chen 2026-02-14 21:15:35 -08:00
parent e2b8f824b3
commit 5a1cc7bc8d
2 changed files with 60 additions and 3 deletions

View file

@ -56,6 +56,7 @@
B9000002A1B2C3D4E5F60719 /* cmux.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000001A1B2C3D4E5F60719 /* cmux.swift */; };
B900000BA1B2C3D4E5F60719 /* cmux in Copy CLI */ = {isa = PBXBuildFile; fileRef = B9000004A1B2C3D4E5F60719 /* cmux */; };
84E00D47E4584162AE53BC8D /* xterm-ghostty in Resources */ = {isa = PBXBuildFile; fileRef = B2E7294509CC42FE9191870E /* xterm-ghostty */; };
A5002000 /* THIRD_PARTY_LICENSES.md in Resources */ = {isa = PBXBuildFile; fileRef = A5002001 /* THIRD_PARTY_LICENSES.md */; };
B9000012A1B2C3D4E5F60719 /* AutomationSocketUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000011A1B2C3D4E5F60719 /* AutomationSocketUITests.swift */; };
B8F266236A1A3D9A45BD840F /* SidebarResizeUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 818DBCD4AB69EB72573E8138 /* SidebarResizeUITests.swift */; };
C0B4D9B0A1B2C3D4E5F60718 /* UpdatePillUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0B4D9B1A1B2C3D4E5F60718 /* UpdatePillUITests.swift */; };
@ -169,6 +170,7 @@
C0B4D9B1A1B2C3D4E5F60718 /* UpdatePillUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdatePillUITests.swift; sourceTree = "<group>"; };
A5001101 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
B2E7294509CC42FE9191870E /* xterm-ghostty */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ghostty/terminfo/78/xterm-ghostty"; sourceTree = "<group>"; };
A5002001 /* THIRD_PARTY_LICENSES.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = THIRD_PARTY_LICENSES.md; sourceTree = SOURCE_ROOT; };
B9000001A1B2C3D4E5F60719 /* cmux.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = cmux.swift; sourceTree = "<group>"; };
B9000004A1B2C3D4E5F60719 /* cmux */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = cmux; sourceTree = BUILT_PRODUCTS_DIR; };
B9000011A1B2C3D4E5F60719 /* AutomationSocketUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationSocketUITests.swift; sourceTree = "<group>"; };
@ -224,6 +226,7 @@
files = (
A5001100 /* Assets.xcassets in Resources */,
84E00D47E4584162AE53BC8D /* xterm-ghostty in Resources */,
A5002000 /* THIRD_PARTY_LICENSES.md in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -340,6 +343,7 @@
isa = PBXGroup;
children = (
B2E7294509CC42FE9191870E /* xterm-ghostty */,
A5002001 /* THIRD_PARTY_LICENSES.md */,
);
path = Resources;
sourceTree = "<group>";

View file

@ -1093,7 +1093,7 @@ private enum DebugWindowConfigSnapshot {
bgGlassEnabled=\(boolValue(defaults, key: "bgGlassEnabled", fallback: true))
bgGlassMaterial=\(stringValue(defaults, key: "bgGlassMaterial", fallback: "hudWindow"))
bgGlassTintHex=\(stringValue(defaults, key: "bgGlassTintHex", fallback: "#000000"))
bgGlassTintOpacity=\(String(format: "%.2f", doubleValue(defaults, key: "bgGlassTintOpacity", fallback: 0.05)))
bgGlassTintOpacity=\(String(format: "%.2f", doubleValue(defaults, key: "bgGlassTintOpacity", fallback: 0.03)))
"""
let menuBarPayload = MenuBarIconDebugSettings.copyPayload(defaults: defaults)
@ -1339,6 +1339,56 @@ private final class AboutWindowController: NSWindowController, NSWindowDelegate
}
}
private final class AcknowledgmentsWindowController: NSWindowController, NSWindowDelegate {
static let shared = AcknowledgmentsWindowController()
private init() {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 500, height: 480),
styleMask: [.titled, .closable, .miniaturizable, .resizable],
backing: .buffered,
defer: false
)
window.isReleasedWhenClosed = false
window.title = "Third-Party Licenses"
window.identifier = NSUserInterfaceItemIdentifier("cmux.licenses")
window.center()
window.contentView = NSHostingView(rootView: AcknowledgmentsView())
super.init(window: window)
window.delegate = self
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func show() {
guard let window else { return }
window.makeKeyAndOrderFront(nil)
}
}
private struct AcknowledgmentsView: View {
private let content: String = {
if let url = Bundle.main.url(forResource: "THIRD_PARTY_LICENSES", withExtension: "md"),
let text = try? String(contentsOf: url) {
return text
}
return "Licenses file not found."
}()
var body: some View {
ScrollView {
Text(content)
.font(.system(.body, design: .monospaced))
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
}
}
}
private final class SettingsWindowController: NSWindowController, NSWindowDelegate {
static let shared = SettingsWindowController()
@ -1473,6 +1523,9 @@ private struct AboutPanelView: View {
openURL(url)
}
}
Button("Licenses") {
AcknowledgmentsWindowController.shared.show()
}
}
if let copy = copyright, !copy.isEmpty {
@ -1927,7 +1980,7 @@ private final class BackgroundDebugWindowController: NSWindowController, NSWindo
private struct BackgroundDebugView: View {
@AppStorage("bgGlassTintHex") private var bgGlassTintHex = "#000000"
@AppStorage("bgGlassTintOpacity") private var bgGlassTintOpacity = 0.05
@AppStorage("bgGlassTintOpacity") private var bgGlassTintOpacity = 0.03
@AppStorage("bgGlassMaterial") private var bgGlassMaterial = "hudWindow"
@AppStorage("bgGlassEnabled") private var bgGlassEnabled = true
@ -1973,7 +2026,7 @@ private struct BackgroundDebugView: View {
HStack(spacing: 12) {
Button("Reset") {
bgGlassTintHex = "#000000"
bgGlassTintOpacity = 0.05
bgGlassTintOpacity = 0.03
bgGlassMaterial = "hudWindow"
bgGlassEnabled = true
updateWindowGlassTint()