diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index dc61369..16b8178 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -7,7 +7,6 @@ on: jobs: build: -# runs-on: macOS-latest runs-on: macos-15 permissions: id-token: write diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index da81788..9d69dfa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,8 @@ on: - '*' jobs: test: -# runs-on: macOS-latest + permissions: + contents: read runs-on: macos-15 timeout-minutes: 10 steps: @@ -25,12 +26,11 @@ jobs: - name: Test run: swift test --build-system swiftbuild --package-path Sources/Packages build: -# runs-on: macOS-latest - runs-on: macos-15 permissions: id-token: write contents: write attestations: write + runs-on: macos-15 timeout-minutes: 10 steps: - uses: actions/checkout@v5 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e790e2f..2fb5150 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,7 +3,8 @@ name: Test on: [push, pull_request] jobs: test: -# runs-on: macOS-latest + permissions: + contents: read runs-on: macos-15 timeout-minutes: 10 steps: diff --git a/Sources/Packages/Sources/SecretAgentKit/SocketController.swift b/Sources/Packages/Sources/SecretAgentKit/SocketController.swift index 19b040a..892324f 100644 --- a/Sources/Packages/Sources/SecretAgentKit/SocketController.swift +++ b/Sources/Packages/Sources/SecretAgentKit/SocketController.swift @@ -133,20 +133,22 @@ private extension SocketPort { convenience init(path: String) { var addr = sockaddr_un() - addr.sun_family = sa_family_t(AF_UNIX) - var len: Int = 0 - withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in + let length = withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in path.withCString { cstring in - len = strlen(cstring) + let len = strlen(cstring) strncpy(pointer, cstring, len) + return len } } - addr.sun_len = UInt8(len+2) + // This doesn't seem to be _strictly_ neccessary with SocketPort. + // but just for good form. + addr.sun_family = sa_family_t(AF_UNIX) + // This mirrors the SUN_LEN macro format. + addr.sun_len = UInt8(MemoryLayout.size - MemoryLayout.size(ofValue: addr.sun_path) + length) - var data: Data! - withUnsafePointer(to: &addr) { pointer in - data = Data(bytes: pointer, count: MemoryLayout.size) + let data = withUnsafePointer(to: &addr) { pointer in + Data(bytes: pointer, count: MemoryLayout.size) } self.init(protocolFamily: AF_UNIX, socketType: SOCK_STREAM, protocol: 0, address: data)! diff --git a/Sources/Secretive/Controllers/URLs.swift b/Sources/Secretive/Controllers/URLs.swift index 7b63ea1..3ea1fe5 100644 --- a/Sources/Secretive/Controllers/URLs.swift +++ b/Sources/Secretive/Controllers/URLs.swift @@ -9,4 +9,17 @@ extension URL { static var socketPath: String { URL.agentHomeURL.appendingPathComponent("socket.ssh").path() } + +} + +extension String { + + var normalizedPathAndFolder: (String, String) { + // All foundation-based normalization methods replace this with the container directly. + let processedPath = replacingOccurrences(of: "~", with: "/Users/\(NSUserName())") + let url = URL(filePath: processedPath) + let folder = url.deletingLastPathComponent().path() + return (processedPath, folder) + } + } diff --git a/Sources/Secretive/Views/Configuration/ConfigurationItemView.swift b/Sources/Secretive/Views/Configuration/ConfigurationItemView.swift index 2c8520b..77e6a2e 100644 --- a/Sources/Secretive/Views/Configuration/ConfigurationItemView.swift +++ b/Sources/Secretive/Views/Configuration/ConfigurationItemView.swift @@ -40,10 +40,7 @@ struct ConfigurationItemView: View { .buttonStyle(.borderless) case .revealInFinder(let rawPath): Button(.revealInFinderButton, systemImage: "folder") { - // All foundation-based normalization methods replace this with the container directly. - let processedPath = rawPath.replacingOccurrences(of: "~", with: "/Users/\(NSUserName())") - let url = URL(filePath: processedPath) - let folder = url.deletingLastPathComponent().path() + let (processedPath, folder) = rawPath.normalizedPathAndFolder NSWorkspace.shared.selectFile(processedPath, inFileViewerRootedAtPath: folder) } .labelStyle(.iconOnly) diff --git a/Sources/Secretive/Views/Secrets/SecretDetailView.swift b/Sources/Secretive/Views/Secrets/SecretDetailView.swift index 679ed70..b3940ff 100644 --- a/Sources/Secretive/Views/Secrets/SecretDetailView.swift +++ b/Sources/Secretive/Views/Secrets/SecretDetailView.swift @@ -21,7 +21,7 @@ struct SecretDetailView: View { CopyableView(title: .secretDetailPublicKeyLabel, image: Image(systemName: "key"), text: keyString) Spacer() .frame(height: 20) - CopyableView(title: .secretDetailPublicKeyPathLabel, image: Image(systemName: "lock.doc"), text: publicKeyFileStoreController.publicKeyPath(for: secret)) + CopyableView(title: .secretDetailPublicKeyPathLabel, image: Image(systemName: "lock.doc"), text: publicKeyFileStoreController.publicKeyPath(for: secret), showRevealInFinder: true) Spacer() } } diff --git a/Sources/Secretive/Views/Views/CopyableView.swift b/Sources/Secretive/Views/Views/CopyableView.swift index c36af4b..5d5b431 100644 --- a/Sources/Secretive/Views/Views/CopyableView.swift +++ b/Sources/Secretive/Views/Views/CopyableView.swift @@ -6,6 +6,7 @@ struct CopyableView: View { var title: LocalizedStringResource var image: Image var text: String + var showRevealInFinder = false @State private var interactionState: InteractionState = .normal @@ -21,9 +22,12 @@ struct CopyableView: View { .foregroundColor(primaryTextColor) Spacer() if interactionState != .normal { - hoverIcon - .bold() - .textCase(.uppercase) + HStack { + if showRevealInFinder { + revealInFinderButton + } + copyButton + } .foregroundColor(secondaryTextColor) .transition(.opacity) } @@ -72,11 +76,18 @@ struct CopyableView: View { } @ViewBuilder - var hoverIcon: some View { + var copyButton: some View { switch interactionState { case .hovering: - Image(systemName: "document.on.document") - .accessibilityLabel(String(localized: .copyableClickToCopyButton)) + Button(.copyableClickToCopyButton, systemImage: "document.on.document") { + withAnimation { + // Button will eat the click, so we set interaction state manually. + interactionState = .clicking + } + copy() + } + .labelStyle(.iconOnly) + .buttonStyle(.borderless) case .clicking: Image(systemName: "checkmark.circle.fill") .accessibilityLabel(String(localized: .copyableCopied)) @@ -85,6 +96,15 @@ struct CopyableView: View { } } + var revealInFinderButton: some View { + Button(.revealInFinderButton, systemImage: "folder") { + let (processedPath, folder) = text.normalizedPathAndFolder + NSWorkspace.shared.selectFile(processedPath, inFileViewerRootedAtPath: folder) + } + .labelStyle(.iconOnly) + .buttonStyle(.borderless) + } + var primaryTextColor: Color { switch interactionState { case .normal, .hovering, .dragging: