diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/macos/MacosClipboardService.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/macos/MacosClipboardService.kt index 133cdafd5..2e05c8369 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/macos/MacosClipboardService.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/macos/MacosClipboardService.kt @@ -1,7 +1,7 @@ package com.clipevery.macos import com.clipevery.clip.ClipboardService -import com.clipevery.macos.api.MacClipboard +import com.clipevery.macos.api.MacosApi import java.awt.Toolkit import java.awt.datatransfer.Clipboard import java.awt.datatransfer.Transferable @@ -22,7 +22,7 @@ class MacosClipboardService override fun run() { try { - MacClipboard.INSTANCE.clipboardChangeCount.let { currentChangeCount -> + MacosApi.INSTANCE.getClipboardChangeCount().let { currentChangeCount -> if (changeCount == currentChangeCount) { return } diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/macos/MacosKeychainHelper.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/macos/MacosKeychainHelper.kt new file mode 100644 index 000000000..1d7f389cd --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/macos/MacosKeychainHelper.kt @@ -0,0 +1,22 @@ +package com.clipevery.macos + +import com.clipevery.macos.api.MacosApi + +object MacosKeychainHelper { + + fun getPassword(service: String, account: String): String? { + return MacosApi.INSTANCE.getPassword(service, account) + } + + fun setPassword(service: String, account: String, password: String): Boolean { + return MacosApi.INSTANCE.setPassword(service, account, password) + } + + fun updatePassword(service: String, account: String, password: String): Boolean { + return MacosApi.INSTANCE.updatePassword(service, account, password) + } + + fun deletePassword(service: String, account: String): Boolean { + return MacosApi.INSTANCE.deletePassword(service, account) + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/macos/api/MacClipboardApi.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/macos/api/MacClipboardApi.kt deleted file mode 100644 index f194412a8..000000000 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/macos/api/MacClipboardApi.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.clipevery.macos.api - -import com.sun.jna.Library -import com.sun.jna.Native - - -interface MacClipboard : Library { - val clipboardChangeCount: Int - - companion object { - val INSTANCE: MacClipboard = Native.load("ClipboardHelper", MacClipboard::class.java) - } -} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/macos/api/MacosApi.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/macos/api/MacosApi.kt new file mode 100644 index 000000000..adbdbeb78 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/macos/api/MacosApi.kt @@ -0,0 +1,21 @@ +package com.clipevery.macos.api + +import com.sun.jna.Library +import com.sun.jna.Native + +interface MacosApi : Library { + + fun getClipboardChangeCount(): Int + + fun getPassword(service: String, account: String): String? + + fun setPassword(service: String, account: String, password: String): Boolean + + fun updatePassword(service: String, account: String, password: String): Boolean + + fun deletePassword(service: String, account: String): Boolean + + companion object { + val INSTANCE: MacosApi = Native.load("MacosApi", MacosApi::class.java) + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/resources/darwin-x86-64/libMacosApi.dylib b/composeApp/src/desktopMain/resources/darwin-x86-64/libMacosApi.dylib new file mode 100755 index 000000000..9ab0371f2 Binary files /dev/null and b/composeApp/src/desktopMain/resources/darwin-x86-64/libMacosApi.dylib differ diff --git a/composeApp/src/desktopMain/swift/MacosApi.swift b/composeApp/src/desktopMain/swift/MacosApi.swift new file mode 100644 index 000000000..4c0c1665e --- /dev/null +++ b/composeApp/src/desktopMain/swift/MacosApi.swift @@ -0,0 +1,93 @@ +import Cocoa +import Foundation +import Security + +@_cdecl("getClipboardChangeCount") +public func getClipboardChangeCount() -> Int { + let pasteboard = NSPasteboard.general + return pasteboard.changeCount +} + +@_cdecl("getPassword") +public func getPassword(service: UnsafePointer<CChar>, account: UnsafePointer<CChar>) -> UnsafePointer<CChar>? { + let serviceString = String(cString: service) + let accountString = String(cString: account) + + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: serviceString, + kSecAttrAccount as String: accountString, + kSecReturnData as String: kCFBooleanTrue!, + kSecMatchLimit as String: kSecMatchLimitOne + ] + + var item: CFTypeRef? + let status = SecItemCopyMatching(query as CFDictionary, &item) + + guard status == errSecSuccess else { return nil } + + if let data = item as? Data, let password = String(data: data, encoding: .utf8) { + return UnsafePointer<CChar>(strdup(password)) + } + + return nil +} + +@_cdecl("setPassword") +public func setPassword(service: UnsafePointer<CChar>, account: UnsafePointer<CChar>, password: UnsafePointer<CChar>) -> Bool { + let serviceString = String(cString: service) + let accountString = String(cString: account) + let passwordString = String(cString: password) + + let passwordData = passwordString.data(using: .utf8)! + + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: serviceString, + kSecAttrAccount as String: accountString, + kSecValueData as String: passwordData + ] + + // Try to add the item to the keychain + let status = SecItemAdd(query as CFDictionary, nil) + + // Check the result + return status == errSecSuccess +} + +@_cdecl("updatePassword") +public func updatePassword(service: UnsafePointer<CChar>, account: UnsafePointer<CChar>, newPassword: UnsafePointer<CChar>) -> Bool { + let serviceString = String(cString: service) + let accountString = String(cString: account) + let newPasswordString = String(cString: newPassword) + + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: serviceString, + kSecAttrAccount as String: accountString + ] + + let updateFields: [String: Any] = [ + kSecValueData as String: newPasswordString.data(using: .utf8)! + ] + + let status = SecItemUpdate(query as CFDictionary, updateFields as CFDictionary) + + return status == errSecSuccess +} + +@_cdecl("deletePassword") +public func deletePassword(service: UnsafePointer<CChar>, account: UnsafePointer<CChar>) -> Bool { + let serviceString = String(cString: service) + let accountString = String(cString: account) + + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: serviceString, + kSecAttrAccount as String: accountString + ] + + let status = SecItemDelete(query as CFDictionary) + + return status == errSecSuccess +} \ No newline at end of file diff --git a/composeApp/src/desktopTest/kotlin/com/clipevery/macos/MacosKeychainHelperTest.kt b/composeApp/src/desktopTest/kotlin/com/clipevery/macos/MacosKeychainHelperTest.kt new file mode 100644 index 000000000..259520de0 --- /dev/null +++ b/composeApp/src/desktopTest/kotlin/com/clipevery/macos/MacosKeychainHelperTest.kt @@ -0,0 +1,24 @@ +package com.clipevery.macos + +import com.clipevery.platform.currentPlatform +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class MacosKeychainHelperTest { + + @Test + fun testKeychain() { + if (currentPlatform().isMacos()) { + MacosKeychainHelper.setPassword("com.clipevery", "test", "test") + var password = MacosKeychainHelper.getPassword("com.clipevery", "test") + assertEquals("test", password) + MacosKeychainHelper.updatePassword("com.clipevery", "test", "test1") + password = MacosKeychainHelper.getPassword("com.clipevery", "test") + assertEquals("test1", password) + assertTrue(MacosKeychainHelper.deletePassword("com.clipevery", "test")) + val password1 = MacosKeychainHelper.getPassword("com.clipevery", "test") + assertTrue(password1 == null) + } + } +} \ No newline at end of file