diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/ClipeveryApp.kt b/composeApp/src/commonMain/kotlin/com/clipevery/ClipeveryApp.kt index 7ab42eeb1..d3c0151bb 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/ClipeveryApp.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/ClipeveryApp.kt @@ -23,6 +23,7 @@ import org.jetbrains.compose.resources.painterResource @Composable fun ClipeveryApp(clipboard: AbstractClipboard, copyText: MutableState) { MaterialTheme { + val pid: Long = ProcessHandle.current().pid() var showImage by remember { mutableStateOf(false) } var start by remember { mutableStateOf(true) } Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { @@ -39,7 +40,7 @@ fun ClipeveryApp(clipboard: AbstractClipboard, copyText: MutableState) { } start = !start }) { - Text(start.toString()) + Text(start.toString() + " " + pid) } AnimatedVisibility(showImage) { Image( diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/clip/AbstractClipboard.kt b/composeApp/src/commonMain/kotlin/com/clipevery/clip/AbstractClipboard.kt index 59b84ee7c..95b15449a 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/clip/AbstractClipboard.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/clip/AbstractClipboard.kt @@ -1,4 +1,10 @@ package com.clipevery.clip - interface AbstractClipboard: Runnable, ClipboardMonitor { + +import java.awt.datatransfer.Transferable +import java.util.function.Consumer + +interface AbstractClipboard: Runnable, ClipboardMonitor { + + val clipConsumer: Consumer } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/clip/ClipboardMonitor.kt b/composeApp/src/commonMain/kotlin/com/clipevery/clip/ClipboardMonitor.kt index 2eee4a539..e1bf4e553 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/clip/ClipboardMonitor.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/clip/ClipboardMonitor.kt @@ -4,6 +4,4 @@ interface ClipboardMonitor { fun start() fun stop() - - fun onChange(event: ClipboardEvent?) } \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/PlatformClipboard.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/PlatformClipboard.kt new file mode 100644 index 000000000..fd3196c6b --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/PlatformClipboard.kt @@ -0,0 +1,19 @@ +package com.clipevery + +import com.clipevery.clip.AbstractClipboard +import com.clipevery.macos.MacosClipboard +import com.clipevery.platform.currentPlatform +import com.clipevery.windows.WindowsClipboard +import java.awt.datatransfer.Transferable +import java.util.function.Consumer + +fun getClipboard(clipConsumer: Consumer): AbstractClipboard { + val platform = currentPlatform() + return if (platform.name == "Macos") { + MacosClipboard(clipConsumer) + } else if (platform.name == "Windows") { + WindowsClipboard(clipConsumer) + } else { + throw Exception("Unknown platform: ${platform.name}") + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/macos/MacosClipboard.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/macos/MacosClipboard.kt new file mode 100644 index 000000000..06f95eeff --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/macos/MacosClipboard.kt @@ -0,0 +1,65 @@ +package com.clipevery.macos + +import com.clipevery.clip.AbstractClipboard +import com.clipevery.macos.api.MacClipboard +import java.awt.Toolkit +import java.awt.datatransfer.Clipboard +import java.awt.datatransfer.Transferable +import java.util.concurrent.Executors +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.TimeUnit +import java.util.function.Consumer + + +class MacosClipboard + (override val clipConsumer: Consumer) : AbstractClipboard { + + private var executor: ScheduledExecutorService? = null + + private var changeCount = 0 + + private var systemClipboard: Clipboard = Toolkit.getDefaultToolkit().systemClipboard + + override fun run() { + try { + MacClipboard.INSTANCE.clipboardChangeCount.let { currentChangeCount -> + if (changeCount == currentChangeCount) { + return + } + changeCount = currentChangeCount + val contents: Transferable? = systemClipboard.getContents(null) + contents?.let { + clipConsumer.accept(it) + } + } + } catch (e: java.lang.Exception) { + e.printStackTrace() + } + } + + override fun start() { + if (executor?.isShutdown != false) { + executor = Executors.newScheduledThreadPool(2) { r -> Thread(r, "Clipboard Monitor") } + } + executor?.scheduleAtFixedRate(this, 0, 300, TimeUnit.MILLISECONDS) + } + + override fun stop() { + executor?.let { + it.shutdown() + + try { + if (!it.awaitTermination(600, TimeUnit.MICROSECONDS)) { + it.shutdownNow() + if (!it.awaitTermination(600, TimeUnit.MICROSECONDS)) { + println("task did not terminate") + } + } + println("stop ") + } catch (ie: InterruptedException) { + Thread.currentThread().interrupt() + it.shutdownNow() + } + } + } +} \ 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 new file mode 100644 index 000000000..f194412a8 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/macos/api/MacClipboardApi.kt @@ -0,0 +1,13 @@ +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/windows/WindowsClipboard.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/windows/WindowsClipboard.kt index b5575ffa7..f53fa5db9 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/windows/WindowsClipboard.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/windows/WindowsClipboard.kt @@ -1,8 +1,6 @@ package com.clipevery.windows -import androidx.compose.runtime.MutableState import com.clipevery.clip.AbstractClipboard -import com.clipevery.clip.ClipboardEvent import com.clipevery.windows.api.User32 import com.sun.jna.Pointer import com.sun.jna.platform.win32.Kernel32 @@ -12,17 +10,17 @@ import com.sun.jna.platform.win32.WinDef.WPARAM import com.sun.jna.platform.win32.WinUser.MSG import java.awt.Toolkit import java.awt.datatransfer.Clipboard -import java.awt.datatransfer.DataFlavor import java.awt.datatransfer.Transferable import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.TimeUnit +import java.util.function.Consumer class WindowsClipboard - (private val copyText: MutableState) : AbstractClipboard, User32.WNDPROC { + (override val clipConsumer: Consumer) : AbstractClipboard, User32.WNDPROC { - private var systemClipboard: Clipboard = Toolkit.getDefaultToolkit().getSystemClipboard() + private var systemClipboard: Clipboard = Toolkit.getDefaultToolkit().systemClipboard private var executor: ExecutorService? = null private var viewer: HWND? = null @@ -91,17 +89,10 @@ class WindowsClipboard } } - override fun onChange(event: ClipboardEvent?) { + private fun onChange() { val contents: Transferable? = systemClipboard.getContents(null) contents?.let { - if (it.isDataFlavorSupported(DataFlavor.stringFlavor)) { - try { - copyText.value = contents.getTransferData(DataFlavor.stringFlavor).toString() - println(contents.getTransferData(DataFlavor.stringFlavor)) - } catch (e: Exception) { - e.printStackTrace() - } - } + clipConsumer.accept(it) } } @@ -122,7 +113,7 @@ class WindowsClipboard User32.WM_DRAWCLIPBOARD -> { try { - onChange(ClipboardEvent(this)) + onChange() } finally { User32.INSTANCE.SendMessage(nextViewer, uMsg, uParam, lParam) } diff --git a/composeApp/src/desktopMain/kotlin/main.kt b/composeApp/src/desktopMain/kotlin/main.kt index 72c1ab11f..30ad58144 100644 --- a/composeApp/src/desktopMain/kotlin/main.kt +++ b/composeApp/src/desktopMain/kotlin/main.kt @@ -5,14 +5,28 @@ import androidx.compose.runtime.remember import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import com.clipevery.ClipeveryApp -import com.clipevery.windows.WindowsClipboard +import com.clipevery.getClipboard +import java.awt.datatransfer.DataFlavor +import java.awt.datatransfer.Transferable +import java.util.function.Consumer + fun main() = application { val copyText = remember { mutableStateOf("Hello World!") } - val windowsClipboard = WindowsClipboard(copyText) - windowsClipboard.start() + val consumer = Consumer { + if (it.isDataFlavorSupported(DataFlavor.stringFlavor)) { + try { + copyText.value = it.getTransferData(DataFlavor.stringFlavor).toString() + println(it.getTransferData(DataFlavor.stringFlavor)) + } catch (e: Exception) { + e.printStackTrace() + } + } + } + val clipboard = getClipboard(consumer) + clipboard.start() Window(onCloseRequest = ::exitApplication) { - ClipeveryApp(windowsClipboard, copyText) + ClipeveryApp(clipboard, copyText) } } @@ -20,6 +34,16 @@ fun main() = application { @Composable fun AppDesktopPreview() { val copyText = remember { mutableStateOf("Hello World!") } - val windowsClipboard = WindowsClipboard(copyText) - ClipeveryApp(windowsClipboard, copyText) + val consumer = Consumer { + if (it.isDataFlavorSupported(DataFlavor.stringFlavor)) { + try { + copyText.value = it.getTransferData(DataFlavor.stringFlavor).toString() + println(it.getTransferData(DataFlavor.stringFlavor)) + } catch (e: Exception) { + e.printStackTrace() + } + } + } + val clipboard = getClipboard(consumer) + ClipeveryApp(clipboard, copyText) } \ No newline at end of file diff --git a/composeApp/src/desktopMain/resources/darwin-x86-64/libClipboardHelper.dylib b/composeApp/src/desktopMain/resources/darwin-x86-64/libClipboardHelper.dylib new file mode 100755 index 000000000..c5d7b2c27 Binary files /dev/null and b/composeApp/src/desktopMain/resources/darwin-x86-64/libClipboardHelper.dylib differ